feat(email): route all email sends through Cloudflare Queue
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.
This commit is contained in:
@@ -9,6 +9,7 @@ import { user } from "@kk/db/schema/auth";
|
||||
import { ORPCError } from "@orpc/server";
|
||||
import { and, desc, eq, gte, lte, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import type { EmailMessage } from "../email-queue";
|
||||
import { adminProcedure, protectedProcedure } from "../index";
|
||||
import { sendDeductionEmail } from "../lib/drinkkaart-email";
|
||||
import {
|
||||
@@ -339,7 +340,7 @@ export const drinkkaartRouter = {
|
||||
createdAt: now,
|
||||
});
|
||||
|
||||
// Fire-and-forget deduction email
|
||||
// Fire-and-forget deduction email (via queue when available)
|
||||
const cardUser = await db
|
||||
.select({ email: user.email, name: user.name })
|
||||
.from(user)
|
||||
@@ -348,14 +349,21 @@ export const drinkkaartRouter = {
|
||||
.then((r) => r[0]);
|
||||
|
||||
if (cardUser) {
|
||||
await sendDeductionEmail({
|
||||
const deductionMsg: EmailMessage = {
|
||||
type: "deduction",
|
||||
to: cardUser.email,
|
||||
firstName: cardUser.name.split(" ")[0] ?? cardUser.name,
|
||||
amountCents: input.amountCents,
|
||||
newBalanceCents: balanceAfter,
|
||||
}).catch((err) =>
|
||||
console.error("Failed to send deduction email:", err),
|
||||
);
|
||||
};
|
||||
|
||||
if (context.emailQueue) {
|
||||
await context.emailQueue.send(deductionMsg);
|
||||
} else {
|
||||
await sendDeductionEmail(deductionMsg).catch((err) =>
|
||||
console.error("Failed to send deduction email:", err),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user