feat(marketing-pages): sendWelcomeEmail job + handler enqueues it

sendWelcomeEmailJob takes IMailerService, validates the typed input
(userId + email), and delegates to mailer.sendWelcome. The
onAuthUserSignedUpHandler now takes IJobQueue and enqueues
"marketing-pages.send-welcome-email" with the event payload.

Both binders bind a RecordingMailerService at the IMailerService
symbol (production placeholder until a real adapter ships) and pass
mailer / queue into the wrapped factories. Dev-seed additionally
queue.register()s the slug so the in-memory queue dispatches via
the wrapped job; production relies on the generated Payload event-task
to resolve the wrapped handler from the container.
This commit is contained in:
2026-05-08 16:35:45 +02:00
parent 6b57a34c0c
commit 7e844c646d
9 changed files with 177 additions and 22 deletions

View File

@@ -0,0 +1,37 @@
// packages/marketing-pages/src/jobs/send-welcome-email.job.test.ts
import { describe, it, expect } from "vitest";
import { sendWelcomeEmailJob, sendWelcomeEmailInputSchema } from "@/jobs/send-welcome-email.job";
import { RecordingMailerService } from "@/infrastructure/services/recording-mailer.service";
describe("sendWelcomeEmailJob", () => {
it("rejects unknown input fields", async () => {
const mailer = new RecordingMailerService();
const job = sendWelcomeEmailJob(mailer);
await expect(job({ unexpectedField: 1 } as never)).rejects.toThrow();
});
it("rejects input missing required fields", async () => {
const mailer = new RecordingMailerService();
const job = sendWelcomeEmailJob(mailer);
await expect(job({ userId: "u1" } as never)).rejects.toThrow();
});
it("rejects invalid email", async () => {
const mailer = new RecordingMailerService();
const job = sendWelcomeEmailJob(mailer);
await expect(
job({ userId: "u1", email: "not-an-email" }),
).rejects.toThrow();
});
it("sends a welcome email via the mailer on valid input", async () => {
const mailer = new RecordingMailerService();
const job = sendWelcomeEmailJob(mailer);
await job({ userId: "u1", email: "u1@example.com" });
expect(mailer.sent).toEqual([{ userId: "u1", email: "u1@example.com" }]);
});
it("references its input schema", () => {
expect(sendWelcomeEmailInputSchema).toBeDefined();
});
});

View File

@@ -0,0 +1,20 @@
// packages/marketing-pages/src/jobs/send-welcome-email.job.ts
import { z } from "zod";
import type { IMailerService } from "../application/services/mailer.service.interface";
export const sendWelcomeEmailInputSchema = z
.object({
userId: z.string(),
email: z.string().email(),
})
.strict();
export type SendWelcomeEmailInput = z.infer<typeof sendWelcomeEmailInputSchema>;
export type ISendWelcomeEmailJob = ReturnType<typeof sendWelcomeEmailJob>;
export const sendWelcomeEmailJob =
(mailer: IMailerService) =>
async (input: SendWelcomeEmailInput): Promise<void> => {
sendWelcomeEmailInputSchema.parse(input);
await mailer.sendWelcome(input.userId, input.email);
};