intermediate~2.5h

Design a Notification System on AWS

A worked system-design example for a multi-channel (push/email/SMS) notification system on AWS — SNS as the fan-out hub, per-channel delivery via Pinpoint/SES, and user preference management.

Want a visual for this topic?

Generate a diagram tailored to Design a Notification System on AWS — the AI picks whichever visual (architecture, flowchart, ER diagram, etc.) best fits this specific AWS concept.

Sign in to generate a visual →
0
Subtopics

🎓 Learning objectives

  • Design a system that can deliver one logical notification across multiple channels (push, email, SMS)
  • Explain SNS's role as the central fan-out point and why per-channel logic lives downstream of it
  • Incorporate user notification preferences (opt-outs, channel choice) into the design
  • Handle delivery failures and retries per channel without blocking other channels

What is it?

A multi-channel notification system's AWS design uses SNS as a central fan-out hub — one published event representing a single logical notification ('order shipped,' 'password reset requested') gets delivered to separate, independent per-channel consumers (an SQS queue or Lambda for push, one for email via SES, one for SMS via SNS/Pinpoint), each responsible for its own channel-specific delivery logic, retry behavior, and failure handling, decoupled from the triggering service and from each other.

Why it exists

This design exists because real products need to reach users across multiple channels using genuinely different downstream services (SES for email, a push provider for mobile, SNS/Pinpoint for SMS), each with its own delivery semantics, failure modes, and retry needs — a single monolithic 'send notification' function trying to handle all three inline becomes a tangled, hard-to-extend mess, which is exactly the coupling problem SNS fan-out is designed to remove.

Problem it solves

It solves reliably delivering a single logical notification across multiple, genuinely different delivery channels, with each channel's specific delivery logic, retry behavior, and failure handling fully decoupled from the triggering business event and from every other channel.

Intuition

The core design insight: 'send this notification' and 'how each channel actually delivers it' are two genuinely separate concerns, and SNS's fan-out is what lets the triggering service handle only the first concern, leaving each channel free to evolve its own delivery logic, retry strategy, and failure handling independently.

Analogy

This is like a single company-wide announcement (one SNS publish) that different departments each act on in their own way and at their own pace — the mailroom (email/SES), the phone-tree team (SMS), and the office-notice-board crew (push notifications) each independently decide how and whether to deliver it to their specific audience, without any of them blocking or depending on the others.

Technical explanation

SNS subscription filter policies evaluate message attributes (not the message body) against a JSON policy attached to each subscription, meaning SNS itself, not the subscriber, decides whether a given published message is even delivered to a particular queue — this offloads per-channel relevance filtering to SNS's own delivery mechanism rather than requiring every consumer to receive and then discard irrelevant messages. SES's bounce and complaint handling can itself be configured to publish notifications to a separate SNS topic, which a dedicated Lambda consumes to update a user's stored email deliverability status — this is the standard way production email-sending systems avoid repeatedly sending to addresses that have already bounced or complained, which matters both for delivery success and for protecting the sending domain's overall reputation with mailbox providers.

Architecture

A triggering service publishes to a single SNS topic with message attributes (notification type, target user ID) that downstream subscriptions can filter on. Separate SQS queues, one per channel, subscribe to the topic (optionally with a filter policy limiting which notification types reach that queue), each consumed by its own Lambda function responsible for that channel's actual delivery — email via SES (or Pinpoint's email channel), push via Pinpoint or a direct APNs/FCM integration, SMS via SNS's SMS publishing or Pinpoint's SMS channel. Each channel's Lambda checks a stored user-preferences table before sending, and each queue has its own dead-letter queue to catch persistently failing messages without affecting other channels' processing.

Workflow

  1. A triggering service publishes a single notification event to an SNS topic, with message attributes describing the notification type and target user. 2) Independent SQS queues (one per channel) are subscribed to the topic, optionally using subscription filter policies to only receive relevant notification types. 3) A channel-specific Lambda consumes each queue, checks the target user's stored preference for that channel, and if enabled, calls the appropriate delivery service (SES for email, Pinpoint/SNS for push/SMS). 4) Each channel's Lambda handles its own delivery failures — retrying transient errors, moving persistently failing messages to its own dead-letter queue, and updating stored contact info (e.g., marking an email as bounced, a push token as invalid) based on channel-specific failure feedback.

Example

An order-processing service publishes a single OrderShipped event to an SNS topic when a shipment is confirmed. Three independent SQS-queue-backed Lambda consumers are subscribed: one calls SES to send a shipping-confirmation email, one calls Pinpoint to send a push notification to the user's mobile app, and one calls SNS's SMS publishing to text a tracking link — each checks the user's stored channel preferences before sending, and each handles its own failures (a bounced email, an expired push token) independently.

Real-world usage

SNS-fan-out-to-per-channel-consumers is an extremely common production pattern for any application sending notifications across more than one channel, and is frequently asked as a system-design interview question specifically because it tests whether a candidate reaches for a decoupled, extensible fan-out design rather than a tightly-coupled, hard-to-extend monolithic notification function.

Trade-offs

Decoupling channels via SNS fan-out adds a small latency/infrastructure overhead compared to a single service directly calling SES/Pinpoint/SNS inline, in exchange for genuine independence between channels — a failure or slowdown in one channel's delivery never affects another's, and new channels can be added purely through new subscriptions with zero changes to the triggering service. Checking user preferences at each channel's consumer (rather than filtering centrally) keeps the SNS-publish step simple and channel-agnostic, at the cost of some duplicated preference-checking logic across each channel's Lambda — a reasonable tradeoff, though a shared preference-checking library/utility helps avoid actual code duplication.

Visual explanation

Picture a single alarm bell (SNS publish) that rings simultaneously in three separate departments — the mailroom, the phone-call team, and the digital-signage team — each department decides independently, based on its own rulebook (channel-specific preferences and delivery logic), exactly how and whether to act on that one alarm, without ever needing to check in with the other departments.

Advantages

  • Adding a new notification channel requires only a new SNS subscription and consumer, with zero changes to any triggering service's code

  • Each channel's delivery failures, retries, and dead-letter handling are fully isolated from every other channel

  • SNS subscription filter policies let each channel receive only the notification types relevant to it, without every consumer needing to filter irrelevant messages itself

  • Pinpoint's engagement analytics and campaign features are available for channels that need more than simple single-event delivery, without forcing that complexity onto simpler channels

Disadvantages

  • More moving infrastructure pieces (one topic, multiple queues, multiple Lambdas) than a single monolithic notification-sending function, with correspondingly more configuration to maintain

  • User preference-checking logic is duplicated across each channel's consumer unless deliberately factored into a shared library, risking drift if not maintained consistently

  • Debugging 'why didn't this user get notified' requires tracing across multiple independent consumers and their individual logs, rather than one linear code path

  • SES/SMS carriers/push providers each have their own rate limits and sending reputation considerations that need separate monitoring per channel, rather than one unified delivery-health view

Common mistakes

  • Building one monolithic function that directly calls SES, Pinpoint, and SNS inline for every notification, coupling the triggering service to every channel's specific API and failure modes

  • Filtering user channel preferences at the SNS-publish step instead of at each channel's own consumer, forcing the publish step to understand every channel's preference logic

  • Not configuring per-channel dead-letter queues, letting a failure in one channel silently drop notifications with no visibility

  • Ignoring SES bounce/complaint feedback (or invalid push token responses) and repeatedly sending to addresses/devices that will never successfully receive a notification, risking sender reputation damage

In the AWS Console

  1. 1

    SNS → Topics → Create topic, then SQS → Queues → Create queue, subscribing each from the topic's Subscriptions tab

    Create an SNS topic for notification events, and create one SQS queue per channel subscribed to it.

  2. 2

    SNS → Subscriptions → [subscription] → Edit → Subscription filter policy

    Add a subscription filter policy to each queue's subscription to limit it to relevant notification types.

  3. 3

    SES → Verified identities → Create identity

    Verify a sending domain/email address in SES for the email channel.

🎤 Interview questions

How would you design a system that sends one logical notification (e.g., 'your order shipped') across multiple channels — push, email, SMS — without duplicating the triggering logic per channel? (Listen for: publish a single event describing the notification once to an SNS topic, with separate subscribers (an SQS queue or Lambda per channel) handling push-notification delivery, email delivery (via SES), and SMS delivery (via SNS's own SMS publishing or Pinpoint) independently — the triggering service only needs to know about one topic, and channels can be added/removed by changing subscriptions, not the triggering code)

Where should user notification preferences (e.g., 'don't send me SMS, only email') be checked in this design? (Listen for: at or near the point where each channel's delivery Lambda is about to actually send — the Lambda checks the user's stored preference before calling SES/Pinpoint/SNS for that channel, rather than trying to filter at the SNS-publish step, since the publish step shouldn't need to know per-user, per-channel preference details; alternatively, SNS subscription filter policies can pre-filter which messages even reach a given channel's queue based on message attributes)

If email delivery fails for one user, should that affect SMS or push delivery for the same notification? (Listen for: no — each channel's delivery is handled by its own independent subscriber/consumer processing its own queue, so a failure in the email-delivery path (e.g., SES throttling or a bounced address) has no effect on the SMS or push delivery paths for the same logical notification, since they're entirely decoupled after the initial SNS publish)

How would you handle a channel-specific delivery failure, like an SES bounce or a push token that's no longer valid? (Listen for: configure a dead-letter queue on each channel's SQS queue to catch messages that fail delivery repeatedly, and separately handle channel-specific failure signals (SES bounce/complaint notifications via its own SNS topic, invalid push tokens returned by the push service) to update the user's stored contact info/token, preventing repeated future failed attempts to the same broken destination)

Why use Pinpoint (or direct APNs/FCM integration) for push notifications instead of just adding another SNS subscriber? (Listen for: SNS itself actually DOES support direct mobile push delivery as a subscription protocol, so a simple design can use SNS directly for push too; Pinpoint is typically chosen additionally when campaign-style targeting, user engagement analytics, or more sophisticated per-user delivery preferences/journeys across channels are needed beyond simple single-event fan-out)

💬 Deep Dive with AI

Related concepts

sns-eventbridgesqs-messagingamazon-mq-msk

Next Step

Continue to Design an AI Chatbot on AWS