Introduces a CF Queue binding (kk-email-queue) to decouple email delivery from request handlers, preventing slow responses and providing automatic retries. All send*Email calls now go through the queue when the binding is available, with direct-send fallbacks for local dev. Reminder fan-outs mark DB rows optimistically before enqueueing to prevent re-enqueue on subsequent cron ticks.
148 lines
3.5 KiB
TypeScript
148 lines
3.5 KiB
TypeScript
/**
|
|
* Email queue types and dispatcher.
|
|
*
|
|
* All email sends are modelled as a discriminated union so the Cloudflare Queue
|
|
* consumer can pattern-match on `msg.type` and call the right send*Email().
|
|
*
|
|
* The `Queue<EmailMessage>` type used in context.ts / server.ts refers to the
|
|
* CF runtime binding type (`import type { Queue } from "@cloudflare/workers-types"`).
|
|
*/
|
|
|
|
import {
|
|
sendCancellationEmail,
|
|
sendConfirmationEmail,
|
|
sendPaymentConfirmationEmail,
|
|
sendPaymentReminderEmail,
|
|
sendReminder24hEmail,
|
|
sendReminderEmail,
|
|
sendSubscriptionConfirmationEmail,
|
|
sendUpdateEmail,
|
|
} from "./email";
|
|
import { sendDeductionEmail } from "./lib/drinkkaart-email";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Message types — one variant per send*Email function
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export type ConfirmationMessage = {
|
|
type: "registrationConfirmation";
|
|
to: string;
|
|
firstName: string;
|
|
managementToken: string;
|
|
wantsToPerform: boolean;
|
|
artForm?: string | null;
|
|
giftAmount?: number;
|
|
drinkCardValue?: number;
|
|
signupUrl?: string;
|
|
};
|
|
|
|
export type UpdateMessage = {
|
|
type: "updateConfirmation";
|
|
to: string;
|
|
firstName: string;
|
|
managementToken: string;
|
|
wantsToPerform: boolean;
|
|
artForm?: string | null;
|
|
giftAmount?: number;
|
|
drinkCardValue?: number;
|
|
};
|
|
|
|
export type CancellationMessage = {
|
|
type: "cancellation";
|
|
to: string;
|
|
firstName: string;
|
|
};
|
|
|
|
export type SubscriptionConfirmationMessage = {
|
|
type: "subscriptionConfirmation";
|
|
to: string;
|
|
};
|
|
|
|
export type Reminder24hMessage = {
|
|
type: "reminder24h";
|
|
to: string;
|
|
firstName?: string | null;
|
|
};
|
|
|
|
export type Reminder1hMessage = {
|
|
type: "reminder1h";
|
|
to: string;
|
|
firstName?: string | null;
|
|
};
|
|
|
|
export type PaymentReminderMessage = {
|
|
type: "paymentReminder";
|
|
to: string;
|
|
firstName: string;
|
|
managementToken: string;
|
|
drinkCardValue?: number;
|
|
giftAmount?: number;
|
|
};
|
|
|
|
export type PaymentConfirmationMessage = {
|
|
type: "paymentConfirmation";
|
|
to: string;
|
|
firstName: string;
|
|
managementToken: string;
|
|
drinkCardValue?: number;
|
|
giftAmount?: number;
|
|
};
|
|
|
|
export type DeductionMessage = {
|
|
type: "deduction";
|
|
to: string;
|
|
firstName: string;
|
|
amountCents: number;
|
|
newBalanceCents: number;
|
|
};
|
|
|
|
export type EmailMessage =
|
|
| ConfirmationMessage
|
|
| UpdateMessage
|
|
| CancellationMessage
|
|
| SubscriptionConfirmationMessage
|
|
| Reminder24hMessage
|
|
| Reminder1hMessage
|
|
| PaymentReminderMessage
|
|
| PaymentConfirmationMessage
|
|
| DeductionMessage;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Consumer-side dispatcher — called once per queue message
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function dispatchEmailMessage(msg: EmailMessage): Promise<void> {
|
|
switch (msg.type) {
|
|
case "registrationConfirmation":
|
|
await sendConfirmationEmail(msg);
|
|
break;
|
|
case "updateConfirmation":
|
|
await sendUpdateEmail(msg);
|
|
break;
|
|
case "cancellation":
|
|
await sendCancellationEmail(msg);
|
|
break;
|
|
case "subscriptionConfirmation":
|
|
await sendSubscriptionConfirmationEmail({ to: msg.to });
|
|
break;
|
|
case "reminder24h":
|
|
await sendReminder24hEmail(msg);
|
|
break;
|
|
case "reminder1h":
|
|
await sendReminderEmail(msg);
|
|
break;
|
|
case "paymentReminder":
|
|
await sendPaymentReminderEmail(msg);
|
|
break;
|
|
case "paymentConfirmation":
|
|
await sendPaymentConfirmationEmail(msg);
|
|
break;
|
|
case "deduction":
|
|
await sendDeductionEmail(msg);
|
|
break;
|
|
default:
|
|
// Exhaustiveness check — TypeScript will catch unhandled variants at compile time
|
|
msg satisfies never;
|
|
}
|
|
}
|