The problem: a creator with a million followers posts
A creator on Snip Bio posts a new link. Their 1.2 million followers who have notifications on should hear about it: a push notification on their phone, an update in the app if it's open, maybe a line in tomorrow's email digest. It should arrive within a minute or two, respect each person's settings and time zone, and never arrive twice.
Doing this inside the "publish link" request is impossible. It's a pipeline.
The pipeline
- 01
Event
link.published is written to the outbox and published (07.06).
- 02
Expand
A worker pages through the creator's followers, 1,000 at a time, emitting one job per batch.
- 03
Decide
For each recipient: preferences, quiet hours, rate limits, deduplication. Most are filtered or deferred here.
- 04
Route
Per channel: in-app via the user's connection, push via the providers, email into tomorrow's digest.
- 05
Deliver
Channel workers send to Apple and Google push services within their limits, retrying and recording results.
Expand in batches. A million recipients is a million small jobs. Paging through followers in batches of 1,000 creates 1,200 jobs, which spread across workers and can't overwhelm anything at once.
Decide per recipient. The most important step is often deciding not to send:
- Preferences: has this person muted this creator, or turned off "new link" notifications?
- Quiet hours: it's 03:00 where they are; hold it until morning, or move it to the digest.
- Rate limits: they follow 400 creators; ten posts in an hour should become one "10 new links from creators you follow", not ten buzzes.
- Deduplication: a key per (user, event), so a retried batch never notifies anyone twice (07.03 Delivery guarantees and idempotent consumers).
The channels
- In-app: if the user's app is connected (12.01 Millions of WebSocket connections), push the update over their connection. Cheap and instant, and it can replace a phone notification entirely.
- Mobile push: the app's servers send to Apple's and Google's push services, which deliver to the device. They have their own rules and limits, so a channel worker paces sends, retries with backoff, and removes device tokens the service reports as invalid.
- Email: batch into digests; individual emails for each event are rarely welcome.
Collapse what replaces itself
Some notifications supersede earlier ones: "your link has 1,000 clicks", then "2,000 clicks". The push services support this directly. With Google's Firebase Cloud Messaging, a collapsible message carries a collapse key, and a newer message with the same key replaces one not yet delivered: a phone that was offline gets only the latest count. FCM stores up to four different collapse keys per device at a time. Non-collapsible messages are each delivered individually.
Spread the spike
A million notifications released at once also means a million people opening the app at once. Stagger delivery over a couple of minutes, send to recently active users first (they're the likeliest to open it), and give the "notification opened" traffic path enough headroom.
Seen in production
Firebase Cloud Messaging
A collapsible message may be replaced by a newer message if it hasn't been delivered yet, while each non-collapsible message is delivered individually. FCM can store four different collapsible messages per device, each with a different collapse key, and allows at most four collapse keys per registration token at a time. By default the collapse key is the app's package name, notification messages are always collapsible, delivery order isn't guaranteed, and the maximum payload is 4,096 bytes.
What we foundMessages that supersede each other share a collapse key, so an offline device receives only the newest.
Collapsible and non-collapsible messages (Firebase documentation)firebase.google.com ↗A batch job sending 1,000 push notifications crashes after sending 600, and the batch is retried. What prevents 600 people getting the notification twice?
Recap
Interview questions
Design a notification system that can notify millions of followers when someone posts.