Separating notification intent from delivery
coreintermediateA notification has two halves that want to live apart. The intent is the business fact: "order 8814 shipped, and this customer should be told." It is produced by the code that knows the business event, it is true regardless of any channel, and it should be recorded once. The delivery is one attempt to get that fact to a person over one channel: this email to this address through this provider, that push to that device token. One intent can produce several deliveries — email plus push — and each delivery can fail, retry and succeed independently, or be suppressed entirely because the person turned that channel off. Collapsing the two halves means the code that ships an order also formats HTML, looks up an SMTP provider and handles a bounce, which couples business logic to an unrelated failure domain: the shipping transaction now fails when the email provider is slow. Splitting them gives the order service one job — record the intent — and gives a separate notification service every job after that. It also makes the two questions people actually ask answerable separately: "did we decide to tell this customer" and "did the message reach them" are different questions with different answers, and a design with one record cannot distinguish them.
Think of it as
A newsroom decides a story is worth publishing; the press decides how many copies go out and where. The decision is recorded once and stays true even if the delivery van breaks down; the van breaking down is a delivery problem, not a reason the story stops being news. Your order service is the newsroom — it decides something is worth telling someone — and your notification service is the press, the vans and the routes. Confusing the two means a broken van retracts the story.
What we're doing: Compare a coupled design and a split design when the email provider has a 40-minute outage.
- 5
- This is the whole argument in one line: a synchronous side effect inside a business transaction makes the transaction inherit that side effect's availability. The order database was healthy the entire time.
- 13
- Writing the intent in the same transaction as the business change is what makes the split safe. If the intent were written after the commit, a crash in between would ship an order that nobody is ever told about — see the Transactional Outbox pattern for the full mechanism.
- 20
- The degraded outcome is now proportionate: late emails during a mail outage. Nothing about the outage touched order processing, because nothing about order processing depended on mail.
Why this works: The split does not make the email provider more reliable — it changes which part of your system is exposed to that provider's reliability. Coupled, an email outage is an order outage; split, an email outage is late email. The failure that matters is the one that reaches a customer, and only one of these two designs keeps it small.
Writing the intent outside the business transaction
Wrong
Better
What you see: A small, steady fraction of customers never receive a notification for an event that definitely happened, with no failed delivery row to explain it — because no delivery was ever attempted, and no intent was ever recorded.
Why: Two writes to two places cannot both be guaranteed unless they are in one transaction. Recording the intent alongside the business change makes "shipped but nobody was told" a state the database will not produce, which is a stronger guarantee than any amount of retrying around a call that was never made.
- Order shipped — business transaction commits
- leads to Record intent
- Record intent — one row, channel-independent
- leads to Resolve preferences
- Resolve preferences — which channels is this recipient open to?
- leads to Email delivery
- leads to Push delivery
- on error, leads to SMS delivery (opted out)
- Email delivery — own retries, own failure states
- Push delivery — own retries, own failure states
- SMS delivery — suppressed — recipient opted out
Intent and delivery: two records, two owners, two lifecycles
Remember: Intent is the business fact — "this recipient should learn this happened" — recorded once, in the same transaction as the business change, with no channel in it. Delivery is one attempt over one channel, with its own retries, its own failure states and its own suppression rules. Keeping them apart stops a mail provider outage from becoming an order outage, and keeps channel and preference knowledge in the one service that owns it.
See also: queueing delivery state and preferences · outbox table design · decoupling with queues · preventing cascading failures via decoupling

