Library / Case Studies

Scaling Push Messaging for Millions of Devices at Netflix

Susheel Aroskar talks about Zuul Push, a scalable push notification service that handles millions of "always-on" persistent connections from all the Netflix apps running. He covers the design of the Zuul Push server and reviews the design details of the back-end message routing infrastructure that lets any Netflix…

By Netflix engineers · Netflix · Added 2026-10-12

Open original source

All content belongs to Netflix engineers / Netflix. We only link to it and give credit. Report a problem

Learn this case study

About 37 min left. Your work is saved as you go.

What you are about to study

Netflix explains a real system. You should know the basics: caching, queues and replication.

Ideas you will meet

Event Driven Architecture
Loosely coupled services reacting to events.
Distributed Systems
Consensus, time, failure and the fundamentals of scale-out.

What we already know about this

Our own lessons on these ideas. Read them here before you start, so it makes sense.

  • Classic System Designs · 10 minDesign a notification systemSend push, SMS and email notifications reliably at scale: queues per channel, user preferences, retries and avoiding spam.Read here

    The problem

    Applications tell users things: "your order shipped", "someone replied", "your code is 482913". A notification system delivers such messages over several channels:

    • Mobile push through Apple (APNs) and Google (FCM) services.
    • SMS through a telecom provider.
    • Email through an email provider.
    • Sometimes in-app messages or webhooks.

    Requirements: soft real time (seconds, not hours), high volume with spikes, no lost notifications, no duplicate floods, user control over what they receive.

    High-level design

    Figure SDA-CSD-06-1 · Notification pipeline with a queue per channel · © forcsguy.com

    1. Other services call the notification service (an API or an event) with who and what.
    2. It validates, applies preferences, renders the message from a template and picks the channel.
    3. It places a job on a per-channel queue.
    4. Workers call the third-party provider and record the result.

    Putting a queue between each stage absorbs spikes and lets you scale the channels independently.

    Figure SDA-CSD-06-2 · Should this event become a notification? · © forcsguy.com

    A person using a smartphone: the notification system's job is to reach this screen at the right moment, and no more often than wantedImage: Océanos y dados · CC0 · Source · Wikimedia Commons: Smartphone Use

    Channels and their providers

    Figure SDA-CSD-06-3 · One provider per channel · © forcsguy.com

    Collecting contact details

    Figure SDA-CSD-06-4 · Contact information flow and tables · © forcsguy.com

    Before: one shared queue

    After: separate lanes

    What changed

    • A marketing blast no longer delays login codes
    • Each channel scales and fails on its own
    • Priorities can be set per lane

    Supporting data

    DataPurpose
    Contact info and device tokenswhere to send
    User preferenceschannels allowed, topics, quiet hours, language
    Templatesconsistent text, translations
    Notification logstate per notification (queued, sent, failed), for debugging and dedupe

    Reliability

    • Persist first: write the notification to the log before queueing, so a crash does not lose it.
    • At-least-once delivery from the queue, plus a dedupe check (notification_id already sent?) so retries do not double send.
    • Retries with exponential backoff and jitter; after repeated failures move to a dead-letter queue and alert.
    • Provider failures: use a second provider as a fallback for critical channels such as SMS verification codes.
    • Invalid tokens: providers tell you when a device token is dead; remove it.

    Protecting users and the system

    • Preferences and opt-out are mandatory for marketing messages (and legally required in many places).
    • Rate limit per user and per topic, and collapse bursts into a digest ("12 new comments").
    • Priority: security codes and payment alerts jump the queue ahead of marketing.
    • Scheduling: delay non-urgent notifications until a sensible local time.

    Why the design looks this way

    A first version with one notification server has three weaknesses: it is a single point of failure, everything scales together (database, cache and sending), and slow third-party calls drag down the whole thing at peak. The fixes are the ones in the design: move state out of the server, run several servers, and put a queue per channel between the API and the workers so an outage at one provider (say the SMS vendor) only backs up its own queue.

    Other points worth knowing: providers differ by region (a push service available in one market may not be in another), so keep each provider behind a plug-in interface. Only internal services or verified clients may call the send API, using an app key and secret, or the system becomes a spam relay. Notifications may be delayed or reordered but must never be silently lost, so persist each one before sending and retry from the stored record. And the single most useful health signal is the number of queued notifications: if it keeps growing, add workers.

    Life of a notification

    Figure SDA-CSD-06-5 · Notification states for tracking · © forcsguy.com

    Figure SDA-CSD-06-6 · The complete notification path · © forcsguy.com

    Observability

    Track sent, delivered, opened and failed counts per channel and per provider, plus queue depth and age. A growing queue age is the earliest sign something is wrong. Keep a trail so support can answer "did this user get the message?".

    Worked numbers: a steady stream and a blast

    Take 10 million users who each get about 5 notifications a day across push, email and in-app: 50 million a day, about 580 per second on average. That is easy. The hard case is a campaign blast: a push to all 10 million users within 10 minutes means 16,700 sends per second, and the push providers rate-limit each connection, so you need many parallel workers and connections, and you must prevent the blast from delaying a login code.

    Log volume: at about 200 bytes per delivery record, 50 million a day is 10 GB a day. Keeping 30 days of detail is about 300 GB, which is manageable; keep aggregates much longer.

    Provider failure: a push provider that fails for 10 minutes during a blast would otherwise cause 10 million retries at once. The queue absorbs it, workers back off, and delivery continues when the provider recovers.

    Figure SDA-CSD-06-7 · Separate lanes for urgent and bulk messages · © forcsguy.com

    One notification, start to finish

    1. Event: the order service publishes "order 4711 shipped" with the user id.
    2. Decide: the notification service loads the user's preferences (allowed channels, quiet hours, language), checks the per-user limit (not more than a few promotional messages a day) and the dedupe key (order-4711-shipped), and renders the template.
    3. Enqueue: a message is placed on the right queue with an idempotency key.
    4. Send: a worker calls the provider. On success it records "sent". On a temporary error it retries with backoff. On a permanent error (for example, an invalid device token) it removes the token and records the failure.
    5. Track: delivery and open receipts update the record; the data feeds dashboards and "did not receive" support tools.

    Figure SDA-CSD-06-8 · Deduplicate before sending · © forcsguy.com

    Mistakes people make

    • Sharing one queue between login codes and marketing. A blast delays the code people are waiting for. Use separate lanes.
    • No per-user rate limit. A bug that emits an event in a loop can send a user a hundred messages.
    • Ignoring preferences and legal rules. Unsubscribe, quiet hours and consent are requirements, not nice-to-haves.
    • Keeping dead device tokens. Providers tell you when a token is invalid. Remove it, or your sender reputation and efficiency suffer.
    • Retry storms. Back off with jitter and cap retries, or you will hammer a provider that is already failing.
    • Time zones. "9 am local time" needs the user's zone, and the send time must be scheduled per zone.

    Try it yourself

    Use the System Builder to see a burst absorbed by a queue, and read the rule engine lesson for how triggers, cooldowns and idempotent actions fit the "decide" step.

    More on this site

    Open this lesson with its quiz and exercise

Want the architecture first? Read our Netflix case study, with its diagram.

Go deeper on this

A path through videos, articles, other case studies and code on the same ideas as Scaling Push Messaging for Millions of Devices at Netflix.

  1. Warm upGet the vocabulary first.

  2. WatchHow others solve the same problem, on video.

    Videos belong to their creators and play from YouTube. More on YouTube (opens in a new tab)

  3. ReadEngineers explain it in their own words.

  4. See it in another companyThe same ideas, a different system.

  5. Read the codeSee how it looks when it is real.

Keep learning

Loading community…