Notifications Microservice
A shared backend service for internal applications to send notifications, keep an inbox history, and deliver live updates to connected users. I proposed, designed, and built the service.
Company project · Confidential
Developed at Lotería de Córdoba. Source code, screenshots, and recordings cannot be shared due to confidentiality.
The Problem
At Lotería de Córdoba, several internal applications needed to alert users about new events. Some alerts needed a lasting place in an inbox; others only mattered while the user was connected. I proposed one shared service so each application could request the delivery it needed through a consistent interface.
My Role
I designed and implemented the backend end to end: the data model, request processing, live delivery, and the integration contract for other applications. I prepared and tested the AWS infrastructure in my personal account and documented the deployment plan. The company's external DevOps team handled production rollout and account-level configuration using that plan.
How a Notification Reaches a User
The application requesting a notification is the producer. It provides the content, intended recipients, and whether the notification should be stored, delivered live, or both.
- The producer submits the request directly or places it on a queue for background processing.
- The service validates the content, source application, and recipients.
- If storage is requested, it saves the notification and each recipient's state in PostgreSQL for later inbox access.
- If live delivery is requested, it finds active connections and sends the event to the connected users through WebSocket.
- For queued requests, failed messages can be isolated through a separate failure queue without failing the whole batch.
If a user is offline, a stored notification remains available when they open their inbox later. A live-only event does not create that inbox history. This distinction lets applications choose the behavior appropriate to each event.
Architecture
Amazon SQS holds queued requests, and AWS Lambda functions process them. PostgreSQL stores notifications, recipient state, and active connections. API Gateway WebSocket maintains the connections used to push live events, while the NestJS REST API lets applications retrieve stored notifications and inbox data.
Click to enlarge
Diagram source (Mermaid)
flowchart LR
subgraph APIs["Producer APIs"]
direction TB
APIA[Producer API 1]
APIB[Producer API 2]
APIC[Producer API 3]
end
subgraph NS["Notifications Microservice"]
direction LR
SQS[SQS Queue]
DLQ[Dead Letter Queue]
LambdaConsumer["Lambda SQS Consumer"]
LambdaConnect["Lambda on Connect"]
LambdaDisconnect["Lambda on Disconnect"]
DB[(PostgreSQL)]
REST["REST API (NestJS)"]
WSG["API Gateway WebSocket"]
end
subgraph RA["Frontend Applications"]
direction TB
React[React Apps]
end
APIA --> SQS
APIB --> SQS
APIC --> SQS
SQS -->|trigger| LambdaConsumer
LambdaConsumer -->|failed messages| DLQ
LambdaConsumer <-->|read/write| DB
LambdaConsumer -->|"real-time notification push"| WSG
WSG -->|$connect| LambdaConnect
WSG -->|$disconnect| LambdaDisconnect
LambdaConnect -->|syncs connection state| DB
LambdaDisconnect -->|syncs connection state| DB
REST <-->|read/write| DB
WSG <-->|$connect / $disconnect| React
WSG <-->|real-time notification push| React
React <-->|HTTP requests| RESTDesign Decisions and Tradeoffs
Direct Requests or Background Processing
Applications can use a direct request-response path or submit queued work. The shared validation contract keeps these entry points consistent while allowing different processing needs.
Storage and Live Delivery Are Independent
Saving an inbox item and delivering an immediate event serve different purposes. Keeping them independent supports lasting notifications, temporary live events, and requests that need both.
Track Who Is Connected
Connection and disconnection handlers update the active-client records used for live delivery. This gives the send path an explicit record of which connections to target.