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.

    NestJSPostgreSQLAWS LambdaSQSWebSocketsReusable MicroserviceSystem Design

    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.

    1. The producer submits the request directly or places it on a queue for background processing.
    2. The service validates the content, source application, and recipients.
    3. If storage is requested, it saves the notification and each recipient's state in PostgreSQL for later inbox access.
    4. If live delivery is requested, it finds active connections and sends the event to the connected users through WebSocket.
    5. 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.

    Rendering diagram…

    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| REST

    Design 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.

    Stack

    NestJSPostgreSQLAWS LambdaAmazon SQSAPI Gateway WebSocketREST APIReact clientsAsynchronous processingReusable microservice design