> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Notification System Why We Need a Queue

> Explains using a message queue and workers to decouple notification acceptance from delivery, improving reliability, scalability, retries, and visibility when sending push, SMS, and email.

We’re designing a notification system that other services (for example, an order service or refund service) call via a `notify` endpoint. This notification system is a normal application running on servers — not a black box — and its primary goals are reliability, scalability, and clear visibility into notification state.

## High-level behavior

* When the notification API receives a request, it persists the complete notification record in PostgreSQL and marks its status as `pending`.
* At that point we must decide who performs the actual sending work: the API process itself (synchronous) or separate worker processes (asynchronous with a queue).

## Simplest approach — synchronous sending

* After saving the notification to the database, the server calls external providers directly:
  * Apple Push Notification Service (APNs) for push
  * An SMS gateway for text messages
  * An email provider for email
* The API waits for each provider’s acceptance/acknowledgement. When providers respond, the server updates the notification status from `pending` to `sent`.

This approach is straightforward and works at small scale, but it blocks the API process while waiting for external responses (often hundreds of milliseconds to seconds). Under heavy load (for example, a burst of 1 million notifications) this design will cause the system to fall behind and can exhaust resources.

<Callout icon="warning" color="#FF6B6B">
  Synchronous sending ties API capacity to external provider latency. A single slow provider or a crash while sending can produce ambiguous notification states and message loss.
</Callout>

## Failure scenarios with synchronous design

* If the notification server crashes mid-send (say, processing notification #2000), some providers may have accepted the payload while others have not. The server cannot reliably know the final outcome for that notification.
* Blocking on provider responses makes the system fragile to spikes and slow downstreams, and reduces overall throughput and resiliency.

## Why a queue is the right component

A message queue decouples acceptance (persisting a notification) from delivery (sending to external providers). Benefits:

* Durable storage of the full notification remains in the database (PostgreSQL).
* The queue stores a small, transient ticket per notification (typically just the notification ID).
* Workers pull tickets and perform sending, allowing horizontal scaling and retries.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/CjAdnUFCNZoN2k5-/images/System-Design-for-Interview-Prep/Fan-Out-One-Event-Many-Readers/Notification-System-Why-We-Need-a-Queue/order-notification-queue-worker-diagram.jpg?fit=max&auto=format&n=CjAdnUFCNZoN2k5-&q=85&s=de4c51ac39a8c338b5b0efe448b123ca" alt="A dark-themed high-level flow diagram showing an Order Service -> Notification Server -> Queue -> Worker path, with a database and external providers (Apple Push, SMS gateway, Email) to the right. A user preference panel labeled &#x22;Alan&#x22; with phone, email and device token details is also shown." data-og-width="1920" width="1920" data-og-height="1080" height="1080" data-path="images/System-Design-for-Interview-Prep/Fan-Out-One-Event-Many-Readers/Notification-System-Why-We-Need-a-Queue/order-notification-queue-worker-diagram.jpg" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/kodekloud-c4ac6d9a/CjAdnUFCNZoN2k5-/images/System-Design-for-Interview-Prep/Fan-Out-One-Event-Many-Readers/Notification-System-Why-We-Need-a-Queue/order-notification-queue-worker-diagram.jpg?w=280&fit=max&auto=format&n=CjAdnUFCNZoN2k5-&q=85&s=87b4e3be06c125582934cdedfd7b1882 280w, https://mintcdn.com/kodekloud-c4ac6d9a/CjAdnUFCNZoN2k5-/images/System-Design-for-Interview-Prep/Fan-Out-One-Event-Many-Readers/Notification-System-Why-We-Need-a-Queue/order-notification-queue-worker-diagram.jpg?w=560&fit=max&auto=format&n=CjAdnUFCNZoN2k5-&q=85&s=06e0b318aa849716f8b3ca0edf88b5c7 560w, https://mintcdn.com/kodekloud-c4ac6d9a/CjAdnUFCNZoN2k5-/images/System-Design-for-Interview-Prep/Fan-Out-One-Event-Many-Readers/Notification-System-Why-We-Need-a-Queue/order-notification-queue-worker-diagram.jpg?w=840&fit=max&auto=format&n=CjAdnUFCNZoN2k5-&q=85&s=2a011969b7354d674033b59bf21f2865 840w, https://mintcdn.com/kodekloud-c4ac6d9a/CjAdnUFCNZoN2k5-/images/System-Design-for-Interview-Prep/Fan-Out-One-Event-Many-Readers/Notification-System-Why-We-Need-a-Queue/order-notification-queue-worker-diagram.jpg?w=1100&fit=max&auto=format&n=CjAdnUFCNZoN2k5-&q=85&s=74b1a81ccbd3b1851327cad06a8fd526 1100w, https://mintcdn.com/kodekloud-c4ac6d9a/CjAdnUFCNZoN2k5-/images/System-Design-for-Interview-Prep/Fan-Out-One-Event-Many-Readers/Notification-System-Why-We-Need-a-Queue/order-notification-queue-worker-diagram.jpg?w=1650&fit=max&auto=format&n=CjAdnUFCNZoN2k5-&q=85&s=b9641ded8c540b0bdb7223f5185ca89a 1650w, https://mintcdn.com/kodekloud-c4ac6d9a/CjAdnUFCNZoN2k5-/images/System-Design-for-Interview-Prep/Fan-Out-One-Event-Many-Readers/Notification-System-Why-We-Need-a-Queue/order-notification-queue-worker-diagram.jpg?w=2500&fit=max&auto=format&n=CjAdnUFCNZoN2k5-&q=85&s=529426fa2def703ebb51421530c3fee6 2500w" />
</Frame>

## What the queue provides (detailed)

* Database: the source of truth. Stores full notification body, channels, status, timestamps, retry history.
* Queue: stores lightweight tickets (e.g., `{ "notification_id": 35 }`) that represent pending work.
* Workers: read tickets, retrieve the notification from the DB, check contacts and preferences, and then send to external providers.
* Visibility timeout and retries: if a worker crashes before acknowledging a ticket, the queue detects the lack of acknowledgement and re-queues the ticket (or assigns it to another worker), preventing message loss.

Example ticket payload (what the queue holds):

```json theme={null}
{
  "notification_id": 35,
  "priority": "normal",
  "created_at": "2025-03-15T12:34:56Z"
}
```

## Final flow (with workers)

1. API receives notification request and persists the full record in PostgreSQL with status `pending`.
2. API enqueues a small ticket into the message queue (usually only the notification ID and metadata).
3. Worker processes consume tickets in parallel:
   * Read the `notification_id` from the ticket.
   * Fetch the full notification record from the database.
   * Load the recipient’s contact details (device token, phone, email) and notification preferences.
   * Decide which channels to use based on preferences (drop or skip channels if the user opted out).
   * Send to chosen providers (APNs, SMS gateway, email provider).
   * When provider acknowledgements arrive, update the notification status in the database and acknowledge the ticket in the queue so it’s removed.
4. If a worker crashes before acknowledging, the queue’s visibility timeout ensures the ticket becomes available again so another worker can retry.

## Concrete example

* A worker pulls a ticket referencing notification ID `35`.
* It loads notification `35` from PostgreSQL and finds it is addressed to Alan.
* The worker fetches Alan’s contact details and preferences from the users table (device token, phone number, email).
* If Alan opted out of marketing SMS and this notification is marketing-related, the worker discards the SMS channel and acknowledges the ticket (no unwanted delivery).
* Otherwise the worker sends through the appropriate providers and updates the notification status to `sent` (or `failed` with retry metadata on transient errors).

## Comparison: synchronous vs queued (at-a-glance)

| Aspect | Synchronous sending (API does sending) | Queued + workers (recommended) |
| - | - | - |
| API latency | High (blocked by provider responses) | Low (returns after enqueueing) |
| Throughput | Limited by API process and provider latency | Scales horizontally with workers |
| Failure handling | Ambiguous states on crash | Queue visibility timeout enables safe retries |
| Visibility / audit | Hard to track in-flight sends | Database + queue ticketing gives clear state |
| Ideal for | Very low volume / prototyping | Production traffic, bursts, high reliability |

## Implementation notes & best practices

* Keep the DB as the source of truth; queue tickets should be minimal to reduce queue size and processing overhead.
* Use idempotent worker behavior so retries do not cause duplicate notifications (store unique provider IDs or use deduplication keys).
* Persist retry metadata and error reasons in the database for observability and debugging.
* Respect user preferences and legal requirements (e.g., opt-outs, regional restrictions) in workers before sending.
* Choose a queue system that supports visibility timeouts and dead-letter queues (DLQs) for non-recoverable failures.

<Callout icon="lightbulb" color="#1CB2FE">
  Using a message queue decouples acceptance from delivery: the API accepts requests quickly, workers reliably deliver notifications later, and the system gains retries, visibility timeouts, and horizontal scaling without losing messages when workers fail.
</Callout>

## Links and references

* PostgreSQL: [https://www.postgresql.org/](https://www.postgresql.org/)
* Message queue overview: [https://en.wikipedia.org/wiki/Message\_queue](https://en.wikipedia.org/wiki/Message_queue)
* AWS SQS visibility timeout: [https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html)
* Apple Push Notification Service (APNs): [https://developer.apple.com/documentation/usernotifications/setting\_up\_a\_remote\_notification\_server](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server)
* SMS gateway: [https://en.wikipedia.org/wiki/SMS\_gateway](https://en.wikipedia.org/wiki/SMS_gateway)
* Email service providers: [https://en.wikipedia.org/wiki/Email\_service\_provider](https://en.wikipedia.org/wiki/Email_service_provider)

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/system-design-for-interview-prep/module/36e67187-d5c7-44a8-9c92-65e2548b2f80/lesson/1840a4cd-fadf-4012-9378-5fe07d5ebe40" />
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.