Shop It Docs
Developer ResourcesNotification

Notification Architecture

Architecture of the surface-aware notification system across MongoDB, API modules, and realtime services.

Notification Architecture

Component Map

Data Model

  • Single notification document per logical event.
  • surfaces[] indicates delivery targets.
  • statusBySurface.<surface> tracks lifecycle timestamps and metadata.

API Boundary

Read APIs (apps/api/src/modules/notification) are strict surface contracts. Write/producer APIs (apps/api/src/modules/notifications) map business events to targets and dispatch.

Realtime Boundary

  • Room key: notifications:{userId}:{surface}
  • Join/sync handlers in gateway
  • Publish helpers in realtime service
  • Event history replay on reconnect

Transactional Layer Architecture (NEW)

Architecture Overview

Layer Responsibilities

ComponentResponsibility
OrderNotificationServiceDomain-specific entry point. Builds EventContext from order data. Resolves event type based on order_source. Wraps router call with fail-safe.
NotificationRouterServiceCore orchestration. Looks up config from catalog. Manages Redis dedupe reservation. Dispatches to channels. Handles fallback on errors.
TransactionalNotificationCatalogStatic configuration. Maps transactional order/refund event types to template IDs, channels, and priorities.
NotificationRecipientResolverServiceResolves recipients from EventContext. Returns userId and optional email.
Template RenderersPure functions generating subject, html, text, previewText for each event type.
NotificationsServiceBase service. Enqueues jobs to BullMQ.
NotificationsProcessorBase processor. Handles MongoDB persistence, Firebase push, realtime events.

Event Flow Sequence

  1. Context Build: OrderNotificationService queries order + customer data, builds EventContext
  2. Event Resolution: Resolves event type (e.g., ORDER_PAYMENT_RECEIVED_CHECKOUT) based on order_source
  3. Router Entry: Calls notificationRouter.routeEvent(event, context)
  4. Config Lookup: Router gets EventConfig from catalog (templateId, channels, priority, title)
  5. Dedupe Check: Router builds dedupe key, attempts Redis SET NX EX
    • If duplicate: return early (no send)
    • If Redis fail: allow send (fail-open)
  6. Recipient Resolution: Router calls recipientResolver.resolve(context)
  7. Channel Dispatch: For each channel:
    • Push: Build notification body, call sendToUser() with inAppTargets + pushTargets
    • Email: Render template, call sendEmailToUsers()
  8. Queue Enqueue: Both methods add jobs to BullMQ NOTIFICATIONS queue
  9. Processing: NotificationsProcessor handles delivery (MongoDB persist, Firebase push, realtime emit)

Deduplication Strategy

Two-layer deduplication:

  1. Router Layer (Redis):

    • Key: notification_sent:transactional:<event>:<orderId>:<productId>
    • TTL: 7 days
    • Method: SET key value EX ttl NX
    • Purpose: Prevent rapid duplicate sends within 7-day window
  2. Processor Layer (MongoDB):

    • Field: externalRef
    • Constraint: Unique partial index when externalRef is string
    • Purpose: Ensure idempotent persistence per recipient

Source-Aware Event Resolution

The OrderNotificationService resolves different events based on order_source field:

order_sourceOrder Placed EventPayment Received Event
checkoutORDER_PLACED_CHECKOUTORDER_PAYMENT_RECEIVED_CHECKOUT
buy_nowORDER_PLACED_BUYNOWORDER_PAYMENT_RECEIVED_BUYNOW
admin_manualORDER_PLACED_BUYNOWORDER_PAYMENT_RECEIVED_BUYNOW
null/unknownORDER_PLACED_BUYNOWORDER_PAYMENT_RECEIVED_BUYNOW

Channel Configuration

All transactional events are configured with:

  • Push: inAppTargets=["mobile_in_app", "web_in_app"], pushTargets=["mobile_push"]
  • Email: HTML template with order details, CTAs

Priority mapping:

  • High: Payment events (received/failed), Refunds
  • Normal: Order placed, Order cancelled

Fail-Open Reliability Model

Failure ScenarioBehavior
Redis unavailable for dedupeLog warning, allow send (fail-open)
Recipient resolution emptyLog warning, skip dispatch
Template rendering failsUse fallback minimal content, continue dispatch
Queue enqueue failsLog error, do not propagate
Push delivery failsLog error, notification persisted in MongoDB
Email delivery failsLog error, notification persisted in MongoDB

Relationship to Base Infrastructure

The transactional layer does not replace the base infrastructure. Instead:

  1. Reuses NotificationsService for queue enqueue
  2. Reuses NotificationsProcessor for delivery
  3. Reuses MongoDB schemas for persistence
  4. Reuses Firebase for push delivery
  5. Reuses RealtimeService for in-app events
  6. Adds event standardization, deduplication, template rendering on top

Base infrastructure consumers outside transactional order flow continue using direct sendToUser() / sendEmailToUsers() calls without going through the transactional router.

Event Coverage

CategoryEvents
Order LifecycleORDER_PLACED_CHECKOUT, ORDER_PLACED_BUYNOW, ORDER_PAYMENT_RECEIVED_CHECKOUT, ORDER_PAYMENT_RECEIVED_BUYNOW, ORDER_PAYMENT_FAILED, ORDER_CANCELLED
Refund FlowORDER_REFUND_REQUESTED, ORDER_REFUND_APPROVED, ORDER_REFUND_REJECTED

Shared Outbox Cleanup Architecture (NEW)

All transactional notification events that persist through outbox_events participate in a shared cleanup lifecycle.

Key behaviors:

  • Pending rows are never deleted.
  • Retention and DLQ threshold are environment-configured.
  • Cleanup runs via BullMQ job execution, preserving retry/backoff semantics.