diff --git a/.changeset/email-cr-fixes.md b/.changeset/email-cr-fixes.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/email-cr-fixes.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/backend/src/api/__tests__/EmailApi.test.ts b/packages/backend/src/api/__tests__/EmailApi.test.ts index f78154917e0..c02a8db753b 100644 --- a/packages/backend/src/api/__tests__/EmailApi.test.ts +++ b/packages/backend/src/api/__tests__/EmailApi.test.ts @@ -59,6 +59,40 @@ describe('EmailApi', () => { expect(response.deliveredByClerk).toBe(true); }); + it('sends a transactional email with a text body', async () => { + server.use( + http.post( + 'https://api.clerk.test/v1/email', + validateHeaders(async ({ request }) => { + const body = await request.json(); + expect(body).toEqual({ + to: { address: 'admin@acme.com' }, + from: { address: 'noreply@acme.com' }, + subject: 'Hello', + text: 'hi', + }); + return HttpResponse.json({ + ...mockEmail, + body: null, + body_plain: 'hi', + }); + }), + ), + ); + + const response = await apiClient.emails.create({ + to: { address: 'admin@acme.com' }, + from: { address: 'noreply@acme.com' }, + subject: 'Hello', + text: 'hi', + }); + + expect(response.id).toBe('ema_123'); + expect(response.body).toBeNull(); + expect(response.bodyPlain).toBe('hi'); + expect(response.status).toBe('queued'); + }); + it('sends a transactional email addressed by userId', async () => { server.use( http.post( diff --git a/packages/backend/src/api/endpoints/EmailApi.ts b/packages/backend/src/api/endpoints/EmailApi.ts index a4f8d35d18b..16a7960effb 100644 --- a/packages/backend/src/api/endpoints/EmailApi.ts +++ b/packages/backend/src/api/endpoints/EmailApi.ts @@ -55,6 +55,35 @@ type EmailRecipient = name?: never; }; +/** + * The body of the email. At least one of `html` and `text` must be provided; if + * both are provided, the `html` version takes precedence. Encoded as a union so + * that omitting both is a compile-time error rather than a server-side one. + */ +type EmailContent = + | { + /** + * The HTML body of the email. Takes precedence over `text` when both are + * provided. + */ + html: string; + /** + * (Optional) The plain text body of the email. + */ + text?: string; + } + | { + /** + * (Optional) The HTML body of the email. Takes precedence over `text` + * when both are provided. + */ + html?: string; + /** + * The plain text body of the email. + */ + text: string; + }; + export type CreateEmailParams = { /** * The recipient of the email. Currently only a single recipient is supported. @@ -75,19 +104,7 @@ export type CreateEmailParams = { replyTo?: Mailbox; subject: string; - - /** - * The HTML body of the email. At least one of `html` and `text` must be - * provided. If both are provided, the `html` version will take precedence. - */ - html?: string; - - /** - * The plain text body of the email. At least one of `html` and `text` must be - * provided. If both are provided, the `html` version will take precedence. - */ - text?: string; -}; +} & EmailContent; export class EmailApi extends AbstractAPI { /**