vishal patel
UnderstoodAdvancedUpdated 2026-09-23

Transactional Outbox

Write the business change and the outgoing event in the same local transaction, then relay the event to the broker — no more "saved to DB but message lost".

dual-writereliabilitymessagingcdc

The problem: dual writes

await db.orders.insertOne(order);         // ✅ committed
await broker.publish("OrderPlaced", ...); // 💥 process crashes → event lost forever

Flip the order and you can publish an event for data that never got saved. Two systems can't be updated atomically without 2PC.

How it works

diagram

Two relay options:

  • Polling publisher: query outbox WHERE sent = false. Simple, but adds DB load and latency.
  • Change Data Capture (CDC): tail the DB log (Debezium, MongoDB Change Streams, DynamoDB Streams). Lower latency, more infrastructure.

MongoDB note: multi-document transactions (replica set required) let you write the entity and the outbox doc atomically. Alternatively, embed pending events in the same document and use Change Streams.

Guarantees and consequences

  • Delivery is at-least-once. The relay may publish, crash, and publish again, so consumers must be idempotent (dedupe on event ID).
  • Ordering per aggregate is preserved if the relay publishes in order and the broker partitions by aggregate ID.
Use it when
  • A DB change must reliably produce an event
  • Implementing sagas or event-driven integration
  • You can't tolerate lost events
Avoid it when
  • Fire-and-forget telemetry where loss is acceptable
  • You already use event sourcing (the event store is the outbox)

Sources & further learning

Videos, courses, docs and books I recommend for this topic.

Related topics