The outbox, not the broker

Nov 4, 2026 · also on LinkedIn

Invoice grades were going stale after a user linked lines to a purchase order. The debate that followed was Service Bus versus Storage Queue. It was the wrong debate.

The endpoint changed the line items, then fired a "document changed" message from inside the request, best effort. Sometimes the message was lost on the commit-or-rollback race. Sometimes it arrived before the commit, and the worker regraded the old state.

Switching brokers would not have fixed that. A send from inside the request can be lost on that race whatever queue is on the other end.

What fixed it was a transactional outbox. The mutation and the "document changed" row are written in the same database transaction. If the outbox write fails, the mutation rolls back. A timer relay drains the outbox to the queue and tracks attempts and poison messages. The consumer is idempotent and coalesces repeated messages for the same document, because the message is a signal to recompute from current state, not a payload.

Once the outbox carried the durability, the broker choice got easy. The message is tiny. Ordering doesn't matter for an idempotent recompute. Dedup happens at the data layer. There are two consumers. Nothing Service Bus adds was being used, and it would have added a paid namespace and connection strings to ten environments. Storage Queue won. The decision record lists the three things that would make me switch, fan-out to three or more consumers, a side effect that isn't idempotent, or debounce by scheduled delivery.

Pick the mechanism that solves the failure you actually have. Then pick the cheapest transport that carries it.

#SoftwareArchitecture #DistributedSystems #AIEngineering