← Stackzilla Blog
The Transactional Outbox: Connecting Database Changes to Background Work
Published October 28, 2026
· 3 min read
· distributed systems, PostgreSQL, messaging, backend
Learn why saving a record and sending a message can drift apart, and how an outbox makes that handoff recoverable.
An application saves an order and then publishes an event so a worker can send a receipt. What happens if the process crashes between those two steps? The order exists, but the worker never hears about it.
Reversing the order is no better. The message might be delivered even though the database transaction later fails. The transactional outbox addresses this gap by recording the business change and the intent to send a message in the same database transaction.
## Put the handoff inside the transaction
When creating the order, insert an outbox record containing a stable event identifier, event type, payload, and relevant timestamps. Commit both records together. If the transaction rolls back, neither the order nor its event survives.
A separate relay reads pending outbox records and sends them to a broker or downstream service. The relay can recover after a restart because the intent is durable. It does not depend on a timer or an in-memory list surviving the original request.
Keep the payload purposeful. Including an entire customer record can leak unnecessary personal information and make schema changes harder. Include identifiers and the fields the event contract genuinely requires.
## Expect delivery more than once
The relay can publish a message successfully and crash before recording that success. After restarting, it sends the same event again. An outbox therefore does not automatically provide exactly-once processing.
Consumers should recognize duplicate event identifiers or make their business operations idempotent. A uniqueness constraint on a processed-event record can help when the consumer's effect occurs in the same database transaction.
Ordering also requires a decision. Events for one order may need a sequence even when unrelated orders can process concurrently. Do not assume creation timestamps alone establish a reliable global order across workers.
## Operate the relay like a real service
Record attempts, last errors, and the age of the oldest undelivered event. A growing backlog is an operational issue even if the main API still returns successful responses.
Use bounded batches and safe claiming so concurrent relays do not continually process the same rows. Poison messages need a visible recovery path rather than infinite retries that block every later event. Define retention and cleanup once delivery is confirmed.
The outbox adds a table, relay logic, and monitoring. For a small application with no cross-system effects, that may be unnecessary. It becomes valuable when missing an event means lost fulfillment, incorrect inventory, or a business process that never starts.
The central benefit is a recoverable handoff: the database contains evidence of what must happen next, even when the original server disappears.
## Further reading
- [AWS: Transactional outbox pattern](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/transactional-outbox.html)
- [Microservices.io: Transactional outbox](https://microservices.io/patterns/data/transactional-outbox.html)
Read the full article on Stackzilla →