Practice-inspired educational scenario. An order has been saved, but its informational email was not sent. This is not one failed operation. It is one completed business outcome and one incomplete auxiliary effect.

Name the outcome first

If order acceptance ends when the order record commits, that is the boundary of the business outcome. Email, SMS, a search index update or an analytics record can be effects of that outcome. Failure of an add-on must not turn a truthful “order accepted” into a misleading “please try again”. That is how duplicates are born.

Asynchronous does not mean “put something on a queue and hope”. Saving the order and publishing a message are two writes. If the process dies between them, the order exists and the message does not.

A durable intent to perform the work

The simplest design can insert a pending-notification record in the same local transaction as the order. A worker reads it, sends the notification and records the result. When more consumers need events, the transactional outbox applies the same rule: the domain change and an outbox record commit atomically, while a separate relay publishes the record.

The outbox closes the gap between the database and broker, but it does not make the system magically exactly-once. A relay can publish the same record again. The consumer needs a stable operation identifier and idempotency or deduplication. For email, decide whether a duplicate message is tolerable or must be suppressed.

Retry is a decision, not a reflex

  • retry only the failed task, not the whole business operation;
  • use bounded attempts, backoff and jitter so a dependency outage is not amplified;
  • after attempts are exhausted, retain the task for intervention and reconciliation rather than deleting the evidence;
  • measure the age of the oldest pending task, attempt counts and permanent failures;
  • tell the user the truth: the order was accepted and confirmation may arrive later.

How to verify the decision

Exercise three failures: mail is unavailable, the process terminates after the database commit, and the relay publishes twice. The outcome should be boring: one order, a durable task, controlled retries and at most one user-visible notification. Boring resilience is a compliment.

Boundary: if notification is a legal or business condition of accepting the operation, it is not an auxiliary effect. Establish that contract before choosing the pattern.

Further reading: AWS Prescriptive Guidance: Transactional outbox pattern. The guidance describes atomic event recording and the need for an idempotent consumer.