Notification systems are often designed as “send an email here.” That works until the product needs digests, multi-channel delivery, user preferences, retries, and quiet hours—while traffic is uneven and providers fail in creative ways.
This is a design essay. It separates what must be true for reliability from implementation preferences. No stack is mandatory; the model is.
Model the intent, not the channel
Start with a notification intent: what happened, who should know, how urgent it is, and what action (if any) the user should take. Channels—email, push, SMS, in-app—are delivery strategies for that intent.
If your domain events call a mailer directly, you have coupled product meaning to a vendor API. That makes preferences, digests, and multi-channel fan-out harder later.
A durable pipeline usually has stages:
- Capture: record the intent from a domain event with enough context to render later.
- Policy: apply user preferences, mute rules, quiet hours, and frequency caps.
- Plan: choose channels and templates; decide immediate vs digest.
- Deliver: hand off to providers with retries and idempotency keys.
- Observe: track accepted, deferred, failed, and suppressed reasons.
Fan-out and backpressure
High fan-out is where naive designs break. One product event can become thousands of deliveries. Synchronous fan-out inside a web request is a reliability smell.
Queue the work, bound concurrency, and make delivery workers idempotent. Provider rate limits are part of the design, not an ops surprise.