Context
Multiple product services sent notifications independently. That duplicated templates, made retry behavior inconsistent, and provided no single view of what had been delivered.
Constraints
- A channel outage could not slow down unrelated delivery paths.
- Users needed preference and quiet-hour controls.
- Every delivery attempt needed an auditable history.
Architecture
Domain events enter a normalization layer, then a policy engine resolves recipient preferences and produces channel-specific commands. Independent workers deliver email and webhook messages, while a shared audit log records outcomes.
Technical decisions
- Partitioned events by recipient to preserve meaningful ordering.
- Used an outbox at the producer boundary to avoid missed events.
- Isolated providers with adapters and channel-specific circuit breakers.
- Modeled delivery attempts separately from logical notifications.
Trade-offs
The central service adds another operational dependency, but removes notification-specific complexity from product domains and makes delivery policy consistent.
Outcome
Product services publish intent instead of owning providers, while operations gain a single place to inspect failures and replay safe deliveries.