The visible problem may look like one API or tuning value, but reliability is decided by state ownership, resource bounds, and recovery after failure. Publishing before commit can announce nonexistent state; committing first can lose the event during a crash. An outbox row commits with business data.
Distributed correctness comes from idempotency keys, leases, monotonic versions, and reconcilable facts—not an assumption of one delivery. A timeout means unknown outcome, not confirmed failure.
Make the implementation decisions explicit
Turn the important choices into durable contracts: validate inputs, assign state ownership, define cleanup, and specify fallback for older peers. Later optimization must not change those semantics.
- Events carry eventId, aggregateId, aggregateVersion, and minimal payload; dispatchers claim rows by skip-locked queries or leases, while consumers deduplicate eventId.
- Separate protocol facts, user intent, and automatic recovery; automation may restore facts but never overturn an explicit choice.
- Retries need an idempotency key, backoff, and deadline; after the deadline create a new task instead of reviving old callbacks.
The delivery standard for Transactional Outbox So Events Survive Successful Database Writes is a usable normal path, convergent failures, bounded resources, and a state users can understand. The result is a production capability that can be explained, degraded safely, and rolled back—not a demo that works once.
Failure paths that are easy to miss
Boundaries turn hidden assumptions into incidents. Weak networks, refresh, concurrency, and capacity need combined coverage because retries can hide each one in isolation.
- A crash after publish but before marking causes duplicates, so outbox is at-least-once rather than exactly-once; retaining rows forever slows scans and backups.
- Fixing only the UI leaves queues, locks, or expired credentials for the next operation to inherit and fail again.
- An untested fallback receives all traffic during a primary failure and becomes the slower, more expensive bottleneck.
How to verify it before release
Observe both endpoints, persisted records, and operational signals during verification. One button state or one successful response cannot prove the complete loop.
- Kill processes at commit, claim, publish, and mark boundaries; no event may disappear, duplicates must dedupe, and aggregate versions cannot regress.
- Disconnect, change networks, and recover mid-operation; reconcile endpoint state, persistence, and resource counts.
- Cover direct, relayed, weak-network, background-tab, and mobile paths; do not rely on averages or one successful screenshot.
The result must be correct, recoverable, and explainable. If any part depends on refreshing the page or an engineer guessing, the protocol loop remains incomplete.