Skip to main content
Start the design with a quick back-of-the-envelope estimation to understand scale and failure modes.
  • Ask for expected throughput: how many notifications per day? Is traffic steady or bursty?
  • Example: 10 million notifications/day ≈ 115 notifications/second on average — modest for most databases and queues.
  • Always ask for peak/arrival patterns. A marketing blast to 1 million users at 09:00 AM creates a massive burst that must be handled without dropping messages.
Remember: we do not deliver messages ourselves. Push notifications, SMS, and email are delivered by external providers (APNs, FCM, Twilio, SendGrid), and those systems can be slow or unavailable.
External providers are out-of-process. Design for retries, timeouts, idempotency, and backpressure — you cannot assume constant availability or latency from third-party services.
A dark-themed infographic showing three notification channels—Push, SMS, and Email—being routed to external providers (APNS, FCM, Twilio, SendGrid) in a dashed "external systems" box. A right-hand sidebar lists numbered notes, and small "interviewer" and "you" icons sit at the bottom.
Keep these operational realities in mind: calls to external providers can vary from tens to hundreds (or more) of milliseconds and sometimes fail. Your system must be resilient to variable latency and failures. API design (high level) The Notify API should be simple and asynchronous from the caller’s perspective:
  • Single POST /notify endpoint.
  • Caller provides the target, message, and delivery channels.
  • The API accepts the request, persists a notification record, and returns immediately with an identifier (for tracing and status checks).
  • Actual delivery is performed asynchronously by worker/dispatcher processes that talk to external providers.
Example request and immediate response:
Why return immediately?
  • Keeps the caller fast and decoupled.
  • Lets you buffer work and apply retries, rate-limiting, and backoff without blocking the originating service.
  • The returned notification ID is critical for tracing, dashboards, and user-facing status queries.
Data model (minimal required fields) Store enough metadata to:
  • Route notifications (phone, device token, email).
  • Track state and retries.
  • Support in-app visibility (seen/unseen).
  • Correlate notifications to business objects (orders, refunds, etc.).
Recommended fields: Illustrative example layout:
Database choice and scaling
  • A relational database such as Postgres is a reasonable starting point: each notification is a small row and Postgres handles thousands of writes per second with proper tuning.
  • At ~10M notifications/day (≈115 writes/sec average), Postgres can perform well with indexes, partitioning, and batch writes.
  • For burst protection, add buffering between the API and the DB (an in-memory queue or durable message queue) or use write batching/partitioned tables.
  • For very large scale or long-term archival, consider an append-only log or a time-partitioned store and move cold data to cheaper storage.
Operational recommendations
  • Use a durable queue (e.g., Kafka, RabbitMQ, SQS) or DB-backed work table for dispatchers to pick up pending notifications.
  • Implement idempotency keys for retries with external providers.
  • Use exponential backoff + jitter when retrying failed deliveries.
  • Maintain per-provider rate limiting to avoid being throttled by APNs/FCM/Twilio/SendGrid.
  • Record provider-specific delivery status and error codes for observability and debugging.
A dark-themed diagram showing two glowing panels labeled "Notifications" and "Users" with example database fields and values. Arrows link the panels to a central Postgres icon with throughput notes (e.g., 10,000,000 a day, ~100 writes a second).
Links and references

Watch Video