Back to docs
Recipe
Outbox Pattern
Guarantee exactly-once side effects by atomically writing events alongside your domain state — then process them asynchronously.
Why
When a database write must also trigger an email, webhook, or cache invalidation, doing both in the same transaction is impossible across heterogeneous systems. The outbox pattern solves this by persisting the intent to act inside the same atomic commit as the state change.
How
- Write — your domain aggregate and an
outboxrow in a single database transaction. - Poll — a lightweight relay process reads unprocessed outbox rows.
- Publish — the relay dispatches each event to the message broker or external service.
- Mark — on success, the row is marked processed; on failure, retry with backoff.
Schema sketch
CREATE TABLE outbox (
id UUID PRIMARY KEY,
aggregate TEXT NOT NULL,
event_type TEXT NOT NULL,
payload JSONB NOT NULL,
created_at TIMESTAMPTZ DEFAULT now(),
processed BOOLEAN DEFAULT FALSE
);When to use
- Order confirmation → email + inventory update
- User signup → CRM sync + welcome sequence
- Any scenario where the side effect must not fire unless the primary write succeeds
⚡ Meridian tip
Pair the outbox relay with a dead-letter queue. After N retries, move the row to a dead_letters table so the relay keeps making forward progress on healthy events.