This article explores the architectural considerations for building a notification service capable of fanning out messages (push, email, SMS) at massive scale, addressing challenges like high fan-out factors, diverse external API requirements, retries, and exactly-once delivery. It details a pipeline approach with queues to decouple stages and discusses the trade-offs between fan-out on write and fan-out on read strategies.
Read original on Dev.to #systemdesignDesigning a notification service, especially one handling millions of recipients, presents several non-trivial challenges. These include managing a variable fan-out factor (from 1 to 2 million messages per event), integrating with multiple external providers (each with unique rate limits, error models, and protocols), ensuring reliable delivery with retry mechanisms, and preventing duplicate notifications (exactly-once semantics).
A fundamental architectural decision is choosing between fan-out on write and fan-out on read. Each approach has distinct trade-offs:
The article proposes a decoupled, queue-based pipeline to handle the varying speeds of different stages and external dependencies. This modular design helps prevent slow components from blocking faster ones.
event -> [ingest] -> [fan-out] -> [per-channel queues] -> [channel workers] -> providersAdapter Pattern for External Providers
Implementing a `ChannelAdapter` interface for each external provider (e.g., EmailAdapter, SMSAdapter) abstracts away provider-specific logic, allowing channel workers to interact with a unified interface and simplifying provider switching or adding new channels.