title: "Reliable ERP Events: Where the Transactional Outbox Fits"
date: 2026-06-26
readingTime: 3 min read
tags: ["ERP", "Architecture", "Database", "Microservices"]
Imagine an invoice is saved successfully, but the process stops before notifying the reporting service. Finance sees the invoice. The dashboard does not. Retrying the entire invoice request could create another problem if it creates a second invoice.
This is the boundary I would examine before adding more messaging infrastructure to an ERP application.
The transactional outbox records the business change and the event to publish in the same database transaction. A separate worker publishes pending events and records delivery progress. This closes the gap between committing a record and remembering that another system needs to hear about it. Microsoft's outbox architecture guide describes the pattern and a Cosmos DB implementation.
For a relational ERP design, I would start with an invoice table and an outbox table sharing a transaction:
Begin transaction
Save invoice
Save InvoicePosted event with a stable event ID
Commit transaction
Publisher worker
Claim a pending event
Send it to the broker
Record successful publication
The request can then report that the invoice was committed without claiming every downstream system has finished processing it.
Publication can succeed immediately before a worker crashes. On recovery, the same event may be sent again. An outbox does not make every downstream effect happen exactly once.
For an illustrative reporting consumer, I would store the processed event ID and update the report projection in one local transaction, with a uniqueness constraint protecting concurrent deliveries. An external email or payment requires its own duplicate-handling strategy because that effect is outside the local transaction.
For this invoice workflow, my support view would show the invoice ID, event ID, creation time, attempt count, last failure category, and current delivery state. Sensitive payload fields would stay out of ordinary logs.
The most useful alert would be the age of the oldest pending event. A queue of ten items could be harmless during normal processing or a serious problem if those items have been waiting since yesterday.
I would also define who can replay an event, how replay is audited, and how incompatible payload versions are handled. If invoice changes must arrive in sequence, that ordering requirement needs an explicit design; parallel workers should not be assumed to preserve it.
My first candidate would be a committed invoice feeding a reporting projection. I would deliberately stop the worker after publication, deliver a duplicate, and make the broker unavailable temporarily. The release criterion would be a recoverable, explainable state after each failure.
An outbox is useful when losing the notification matters. It also creates a queue that someone must operate. Both belong in the design discussion.