fix(email): include course name in student welcome subject (#739)
* fix(email): include course name in student welcome subject The studentCourseWelcome template used a static subject, so bulk course assignments produced identical inbox titles. Resolve the subject from courseName at send time and sanitize it like other email headers. Co-authored-by: Rotimi Best <[email protected]> * fix(email): use course-only subject for student welcome email Co-authored-by: Rotimi Best <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
co-authored by
Rotimi Best
Cursor Agent
parent
51f90bf450
commit
8288f157b3
@@ -3,6 +3,7 @@ import * as z from 'zod';
|
|||||||
import type { EmailResponse } from '../utils/types';
|
import type { EmailResponse } from '../utils/types';
|
||||||
import type { SendTemplateConfig } from './types';
|
import type { SendTemplateConfig } from './types';
|
||||||
import { deliverEmail } from '../send';
|
import { deliverEmail } from '../send';
|
||||||
|
import { sanitizeEmailSubject } from '../utils/functions/email-helpers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a single email using a template
|
* Send a single email using a template
|
||||||
@@ -13,10 +14,14 @@ export async function sendTemplateEmail<TSchema extends z.ZodType>(
|
|||||||
): Promise<EmailResponse[]> {
|
): Promise<EmailResponse[]> {
|
||||||
const content = config.template.render(config.fields);
|
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([
|
return deliverEmail([
|
||||||
{
|
{
|
||||||
to: config.to,
|
to: config.to,
|
||||||
subject: config.subject ?? config.template.subject,
|
subject: sanitizeEmailSubject(resolvedSubject),
|
||||||
content,
|
content,
|
||||||
from: config.from ?? config.template.from,
|
from: config.from ?? config.template.from,
|
||||||
replyTo: config.replyTo ?? config.template.replyTo,
|
replyTo: config.replyTo ?? config.template.replyTo,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { EmailId } from '../utils/types';
|
|||||||
* Base email template interface
|
* Base email template interface
|
||||||
*/
|
*/
|
||||||
export interface EmailTemplate<TSchema extends z.ZodType = z.ZodType> {
|
export interface EmailTemplate<TSchema extends z.ZodType = z.ZodType> {
|
||||||
subject: string;
|
subject: string | ((fields: z.infer<TSchema>) => string);
|
||||||
schema: TSchema;
|
schema: TSchema;
|
||||||
render: (fields: z.infer<TSchema>) => string;
|
render: (fields: z.infer<TSchema>) => string;
|
||||||
from?: string;
|
from?: string;
|
||||||
@@ -47,7 +47,7 @@ export interface SendTemplateConfig<TSchema extends z.ZodType = z.ZodType> exten
|
|||||||
*/
|
*/
|
||||||
export interface DefineEmailConfig<TSchema extends z.ZodType = z.ZodType> {
|
export interface DefineEmailConfig<TSchema extends z.ZodType = z.ZodType> {
|
||||||
id: EmailId;
|
id: EmailId;
|
||||||
subject: string;
|
subject: string | ((fields: z.infer<TSchema>) => string);
|
||||||
schema: TSchema;
|
schema: TSchema;
|
||||||
render: (fields: z.infer<TSchema>) => string;
|
render: (fields: z.infer<TSchema>) => string;
|
||||||
from?: string;
|
from?: string;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { ZEmailBranding } from '../core/branding';
|
|||||||
|
|
||||||
export const studentCourseWelcomeEmail = defineEmail({
|
export const studentCourseWelcomeEmail = defineEmail({
|
||||||
id: 'studentCourseWelcome',
|
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({
|
schema: z.object({
|
||||||
orgName: z.string().min(1),
|
orgName: z.string().min(1),
|
||||||
courseName: z.string().min(1),
|
courseName: z.string().min(1),
|
||||||
|
|||||||
@@ -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'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user