The Dual-Write Problem in Transactional Email
If you create a user in your database and then immediately call an email API, a network failure or server crash leaves your system in an inconsistent state: the user exists, but the verification email was never sent.
The Transactional Outbox pattern solves this by writing the email job into an outbox_messages database table inside the exact same atomic database transaction.
Prisma Implementation with Interactive Transactions
Write user records and outbound email jobs atomically using prisma.$transaction().
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export async function registerUserWithOutbox(email: string, name: string) {
return await prisma.$transaction(async (tx) => {
// 1. Create user record
const user = await tx.user.create({
data: { email, name },
});
// 2. Atomically insert email job into outbox
await tx.outboxMessage.create({
data: {
to: email,
subject: 'Verify your email address',
body: `Hello ${name}, please click here to verify your account.`,
status: 'PENDING',
},
});
return user;
});
}
// Background poller picks up pending messages with SKIP LOCKED
export async function pollOutboxQueue() {
const pending = await prisma.outboxMessage.findMany({
where: { status: 'PENDING' },
take: 10,
});
for (const msg of pending) {
const res = await fetch('https://api.sadasend.com/v1/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SADASEND_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ to: msg.to, subject: msg.subject, text: msg.body }),
});
if (res.ok) {
await prisma.outboxMessage.update({
where: { id: msg.id },
data: { status: 'SENT', sentAt: new Date() },
});
}
}
}Key Benefits of the Outbox Pattern
- 100% Guaranteed Delivery: Even if the email API is down, messages are safely queued in Postgres.
- Zero Phantom Emails: If the database transaction rolls back, no email is ever sent.
- Idempotent Processing: Safe retries using Postgres FOR UPDATE SKIP LOCKED locks.
Building AI agents that send email?
Join the SadaSend early access waitlist to get scoped API keys, recipient allowlists, and Model Context Protocol (MCP) servers upon launch.