Skip to main content

Distributed notification system

The Distributed Notification System is a robust, scalable microservices-based platform designed to handle email and push notifications asynchronously. Built with modern DevOps practices, it leverages Docker orchestration, message queuing, and caching to deliver notifications reliably at scale.
This system is designed for Stage 4 backend development, showcasing microservices communication patterns, asynchronous processing, and containerized deployment strategies.

Architecture overview

The system consists of 5 core microservices that communicate both synchronously (via REST) and asynchronously (via RabbitMQ):

API Gateway

Entry point for all client requests. Validates, authenticates, and routes notification requests to appropriate queues.

User Service

Manages user information, notification preferences, and maintains user data in PostgreSQL with Redis caching.

Template Service

Stores and manages notification templates with variable substitution, versioning, and multi-language support.

Email Service

Consumes messages from RabbitMQ email queue and sends emails via SMTP/API providers (SendGrid, Mailgun, Gmail).

Push Service

Consumes messages from RabbitMQ push queue and sends push notifications via FCM, OneSignal, or Web Push.

Key features

Asynchronous message processing

All notifications are processed asynchronously using RabbitMQ with dedicated queues for email and push notifications. This ensures high throughput and fault tolerance.
  • Exchange: notifications.direct
  • Queues: email.queue, push.queue, failed.queue (dead letter queue)
  • Retry mechanism: Exponential backoff for failed deliveries

Intelligent caching with Redis

User preferences are cached in Redis to minimize database lookups and improve response times. The caching layer ensures notification workers can quickly verify user preferences before sending.

Data persistence with PostgreSQL

Each service maintains its own PostgreSQL database:
  • User Service: User information and preferences (users_db)
  • Template Service: Templates and version history
  • Shared store: Notification status tracking (pending, delivered, failed)

Idempotency guarantees

Every notification request includes a unique request_id to prevent duplicate deliveries. The system checks the shared PostgreSQL store before processing any notification.

Health monitoring

All services expose a /health endpoint for container orchestration and monitoring. Correlation IDs track the full notification lifecycle across services.

Docker orchestration

The entire system is containerized using Docker and orchestrated with Docker Compose, making it easy to deploy, scale, and test locally.

Communication patterns

Used for quick lookups and queries:
  • Fetching user data from User Service
  • Retrieving templates from Template Service
  • Querying notification status
Used for notification delivery:
  • API Gateway publishes notification requests to queues
  • Email/Push services consume messages and send notifications
  • Failed messages are retried with exponential backoff
  • Permanently failed messages move to dead letter queue

Service ports

ServicePortPurpose
API Gateway8000Client-facing REST API
User Service8001User management endpoints
Template Service8002Template management
Email Service8003Email worker (internal)
Push Service8004Push notification worker (internal)
RabbitMQ5672, 15672Message queue + management UI
PostgreSQL5432Relational database
Redis6379Cache and session store
All services require RabbitMQ, Redis, and PostgreSQL to be running. Use Docker Compose to ensure proper service dependencies and startup order.

Get started

Quick start

Get the system running locally in minutes with Docker Compose

Architecture

Deep dive into the microservices architecture and communication flows

API reference

Explore the REST API endpoints and message formats

CI/CD integration

The system includes continuous integration and deployment:
  • CI: Builds Docker images, runs tests, and blocks PR merges on failures
  • CD: Triggered on merge to main, rebuilds images and prepares for deployment

Design principles

1

Separation of concerns

Each microservice has a single, well-defined responsibility
2

Fault tolerance

Services handle failures gracefully with retry mechanisms and circuit breakers
3

Horizontal scalability

Services can scale independently based on load
4

Observability

Correlation IDs and structured logging enable full lifecycle tracking

Build docs developers (and LLMs) love