From fa261fecfd69017ca01e3be6ecd7f9488dd37f93 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Mon, 18 May 2026 16:36:45 +0200 Subject: [PATCH] fix: omit empty cc/bcc from Email/set so server does not emit bare Cc: header #301 --- lib/__tests__/jmap-send-threading.test.ts | 19 +++++++++++++++++++ lib/demo/demo-client.ts | 8 ++++---- lib/jmap/client.ts | 11 +++++++---- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/lib/__tests__/jmap-send-threading.test.ts b/lib/__tests__/jmap-send-threading.test.ts index 725200e1..1abc8824 100644 --- a/lib/__tests__/jmap-send-threading.test.ts +++ b/lib/__tests__/jmap-send-threading.test.ts @@ -127,6 +127,25 @@ describe('JMAPClient.sendEmail threading headers', () => { expect(draft.references).toBeUndefined(); }); + it('omits cc/bcc when arrays are empty so the server does not emit a bare Cc: header', async () => { + const client = createClient(); + const captured = mockSendEmailFlow(); + + await client.sendEmail( + ['recipient@example.com'], + 'No copies', + 'body', + [], [], 'identity-1', 'user@example.com', + ); + + const setCall = captured[2].methodCalls[0]; + const create = setCall[1].create as Record>; + const draft = Object.values(create)[0]; + + expect(draft.cc).toBeUndefined(); + expect(draft.bcc).toBeUndefined(); + }); + it('drops empty / whitespace-only ids rather than sending blank entries', async () => { const client = createClient(); const captured = mockSendEmailFlow(); diff --git a/lib/demo/demo-client.ts b/lib/demo/demo-client.ts index 1fd0bdbb..cac0344e 100644 --- a/lib/demo/demo-client.ts +++ b/lib/demo/demo-client.ts @@ -409,8 +409,8 @@ export class DemoJMAPClient implements IJMAPClient { receivedAt: new Date().toISOString(), from: [{ name: 'Demo User', email: 'demo@example.com' }], to: to.map(e => ({ email: e })), - cc: cc?.map(e => ({ email: e })), - bcc: bcc?.map(e => ({ email: e })), + cc: cc?.length ? cc.map(e => ({ email: e })) : undefined, + bcc: bcc?.length ? bcc.map(e => ({ email: e })) : undefined, subject, sentAt: new Date().toISOString(), preview: body.substring(0, 200), @@ -463,8 +463,8 @@ export class DemoJMAPClient implements IJMAPClient { receivedAt: new Date().toISOString(), from: [{ name: 'Demo User', email: 'demo@example.com' }], to: to.map(e => ({ email: e })), - cc: cc?.map(e => ({ email: e })), - bcc: bcc?.map(e => ({ email: e })), + cc: cc?.length ? cc.map(e => ({ email: e })) : undefined, + bcc: bcc?.length ? bcc.map(e => ({ email: e })) : undefined, subject, sentAt: new Date().toISOString(), preview: body.substring(0, 200), diff --git a/lib/jmap/client.ts b/lib/jmap/client.ts index f32a95db..f3d32425 100644 --- a/lib/jmap/client.ts +++ b/lib/jmap/client.ts @@ -2014,8 +2014,8 @@ export class JMAPClient implements IJMAPClient { const emailData: EmailDraft = { from: [{ ...(sanitizedFromName ? { name: sanitizedFromName } : {}), email: fromEmail || this.username }], to: to.map(email => ({ email })), - cc: cc?.map(email => ({ email })), - bcc: bcc?.map(email => ({ email })), + cc: cc?.length ? cc.map(email => ({ email })) : undefined, + bcc: bcc?.length ? bcc.map(email => ({ email })) : undefined, subject, keywords: { "$draft": true }, mailboxIds: { [draftsMailbox.id]: true }, @@ -2141,8 +2141,11 @@ export class JMAPClient implements IJMAPClient { from: [{ ...(sanitizedFromName ? { name: sanitizedFromName } : {}), email: fromEmail || this.username }], replyTo: identityReplyTo?.length ? identityReplyTo : undefined, to: to.map(email => ({ email })), - cc: cc?.map(email => ({ email })), - bcc: bcc?.map(email => ({ email })), + // RFC 5322 ยง3.6.3: To/Cc carry an address-list (non-empty). Sending + // cc:[] makes the server emit a literal `Cc:` header with no addresses, + // which is malformed and a spam signal. Omit the field when empty. + cc: cc?.length ? cc.map(email => ({ email })) : undefined, + bcc: bcc?.length ? bcc.map(email => ({ email })) : undefined, subject, inReplyTo: normalizedInReplyTo?.length ? normalizedInReplyTo : undefined, references: normalizedReferences?.length ? normalizedReferences : undefined,