diff --git a/packages/email/src/core/base.ts b/packages/email/src/core/base.ts index f0e24e2e..54dd4070 100644 --- a/packages/email/src/core/base.ts +++ b/packages/email/src/core/base.ts @@ -3,6 +3,7 @@ import * as z from 'zod'; import type { EmailResponse } from '../utils/types'; import type { SendTemplateConfig } from './types'; import { deliverEmail } from '../send'; +import { sanitizeEmailSubject } from '../utils/functions/email-helpers'; /** * Send a single email using a template @@ -13,10 +14,14 @@ export async function sendTemplateEmail( ): Promise { const content = config.template.render(config.fields); + const resolvedSubject = + config.subject ?? + (typeof config.template.subject === 'function' ? config.template.subject(config.fields) : config.template.subject); + return deliverEmail([ { to: config.to, - subject: config.subject ?? config.template.subject, + subject: sanitizeEmailSubject(resolvedSubject), content, from: config.from ?? config.template.from, replyTo: config.replyTo ?? config.template.replyTo, diff --git a/packages/email/src/core/types.ts b/packages/email/src/core/types.ts index e4442ecc..221ae642 100644 --- a/packages/email/src/core/types.ts +++ b/packages/email/src/core/types.ts @@ -6,7 +6,7 @@ import type { EmailId } from '../utils/types'; * Base email template interface */ export interface EmailTemplate { - subject: string; + subject: string | ((fields: z.infer) => string); schema: TSchema; render: (fields: z.infer) => string; from?: string; @@ -47,7 +47,7 @@ export interface SendTemplateConfig exten */ export interface DefineEmailConfig { id: EmailId; - subject: string; + subject: string | ((fields: z.infer) => string); schema: TSchema; render: (fields: z.infer) => string; from?: string; diff --git a/packages/email/src/emails/student-course-welcome.ts b/packages/email/src/emails/student-course-welcome.ts index fd25fb65..21d02107 100644 --- a/packages/email/src/emails/student-course-welcome.ts +++ b/packages/email/src/emails/student-course-welcome.ts @@ -6,7 +6,7 @@ import { ZEmailBranding } from '../core/branding'; export const studentCourseWelcomeEmail = defineEmail({ id: 'studentCourseWelcome', - subject: 'You have access to a course — sign in to get started', + subject: (fields) => `You have access to ${fields.courseName} course`, schema: z.object({ orgName: z.string().min(1), courseName: z.string().min(1), diff --git a/packages/email/tests/student-course-welcome.test.ts b/packages/email/tests/student-course-welcome.test.ts new file mode 100644 index 00000000..9a8df4d5 --- /dev/null +++ b/packages/email/tests/student-course-welcome.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from 'vitest'; + +import { studentCourseWelcomeEmail } from '../src/emails/student-course-welcome'; + +describe('studentCourseWelcomeEmail', () => { + it('includes the course name in the subject', () => { + const resolveSubject = studentCourseWelcomeEmail.template.subject; + + expect(typeof resolveSubject).toBe('function'); + expect((resolveSubject as (fields: { courseName: string }) => string)({ courseName: 'React Basics' })).toBe( + 'You have access to React Basics course' + ); + }); +});