Skip to content
Writing
PrismaDrizzlePostgresArchitecture

Prisma & Drizzle Outbox Pattern: Atomic Database Transactions for Email

Never lose a confirmation email when your API crashes midway through a transaction. Learn how to implement the Outbox pattern with Prisma and Drizzle ORM.

Tayyab MughalFounder & AI Chief2 min read

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().

TYPESCRIPT
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.
Early Access

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.

Rolling out in developer batches·No credit card needed
Social Hashtags & Share
#EmailAPI#DeveloperTools#Prisma#Drizzle#Postgres#SoftwareArchitecture
Tayyab MughalFounder & AI Chief

Building SadaSend — transactional email with an MCP server that has a ceiling. Writes about deliverability, email infrastructure, and what happens when you hand an autonomous agent a sending credential.

Keep reading