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

@@ -1,15 +1,25 @@
// packages/marketing-pages/src/events/handlers/on-auth-user-signed-up.handler.test.ts
import { describe, it, expect } from "vitest";
import { RecordingJobQueue } from "@repo/core-testing/instrumentation";
import { onAuthUserSignedUpHandler } from "@/events/handlers/on-auth-user-signed-up.handler";
describe("onAuthUserSignedUpHandler", () => {
it("returns a function (factory shape)", () => {
const handler = onAuthUserSignedUpHandler();
expect(typeof handler).toBe("function");
});
it("enqueues marketing-pages.send-welcome-email with userId + email", async () => {
const queue = new RecordingJobQueue();
const handler = onAuthUserSignedUpHandler(queue);
it("does not throw on a valid stub event", async () => {
const handler = onAuthUserSignedUpHandler();
await expect(handler({} as never)).resolves.toBeUndefined();
await handler({
userId: "u-42",
email: "alice@example.com",
signedUpAt: "2026-05-08T12:00:00.000Z",
});
expect(queue.enqueued).toHaveLength(1);
expect(queue.enqueued[0]).toEqual(
expect.objectContaining({
taskSlug: "marketing-pages.send-welcome-email",
input: { userId: "u-42", email: "alice@example.com" },
}),
);
});
});

View File

@@ -1,14 +1,16 @@
// packages/marketing-pages/src/events/handlers/on-auth-user-signed-up.handler.ts
import type { UserSignedUpEvent } from "@repo/auth";
import type { IJobQueue } from "@repo/core-shared/jobs";
export type IOnAuthUserSignedUpHandler = ReturnType<
typeof onAuthUserSignedUpHandler
>;
export const onAuthUserSignedUpHandler =
() =>
async (_event: UserSignedUpEvent): Promise<void> => {
// TODO: implement the reaction. Inject dependencies via the factory's
// constructor and use them here. The handler runs inside the consumer's
// span+capture sandwich, so just throwing on failure is the right shape.
(queue: IJobQueue) =>
async (event: UserSignedUpEvent): Promise<void> => {
await queue.enqueue("marketing-pages.send-welcome-email", {
userId: event.userId,
email: event.email,
});
};