Labs

System lab · medium · 27 min

Notifications: Push, SMS and email

One API for every channel, with preferences, retries and no duplicates.

Every app ends up building a notification system, and almost every first version is the same: a function called send_push() sprinkled through the codebase. It works until someone gets the same OTP four times, a marketing blast delays a password reset by twenty minutes, or the SMS provider goes down and nobody can log in.

This lab designs a shared notification service from a blank page. It's less about raw scale and more about care: the right message, on the right channel, at the right time, once.

The brief

You're building the notification platform for a large consumer company with several products (say a food delivery app, a payments app and a shopping app). Product teams don't talk to Apple, Google, email providers or SMS gateways directly. They call your service.

Who uses it?

  • Product teams send notifications: "your order is out for delivery", "your OTP is 482913", "50% off this weekend".
  • End users receive them, and control which ones they get.
  • The operations team watches delivery rates and switches providers when one fails.

In scope: push (iOS and Android), SMS and email; templates; user preferences; deduplication; retries; provider failover; delivery status.

Out of scope: in-app inbox UI, building your own SMS or email delivery infrastructure (you use providers), campaign management tools for marketers, and A/B testing of message copy.

Requirements

Functional

  • Send a notification to a user on one or more channels, from a template plus data.
  • Respect user preferences: opt-outs per category and channel, and quiet hours.
  • Support priorities: transactional (OTP, order updates) and promotional (marketing).
  • Send to one user or to a large audience (a campaign).
  • Track status per message: queued, sent to provider, delivered (where the channel can tell us), failed.

Non-functional

  • Transactional is fast: OTPs and order updates reach the provider within 2 seconds at p99.
  • Never twice: the same logical notification must not reach the same user twice, even if the caller retries.
  • Never lost: once accepted, a transactional notification is delivered or definitively marked failed.
  • Isolation: a 50-million-user campaign must not slow down transactional messages.
  • Provider-independent: if one SMS or email provider fails, traffic moves to another.

Notice "never twice" and "never lost" together. In a world of retries, those two pull in opposite directions. Module 15 and Module 12 explain why, and a deep dive below works through it.