Skip to main content

Notification System

Source: equa-server/modules/notifications/ Last Updated: 2026-02-28 Verified Against: Spec 021 verification-results.md (13/13 tasks PASS)

Overview

The notification system handles transactional email delivery for the Equa platform. It implements a Notifier interface that accepts a user ID and a notification object, renders HTML from Handlebars templates, and sends via a configurable transport layer. The module is backend-only with no dedicated API endpoints — it is consumed as a library by auth, admin, referral, billing, and organization modules.

Architecture

Core Interface

The Notifier type from common:
The factory function newNodeMailerNotifier (email-notifier.ts:64-90) creates a Notifier that:
  1. Resolves recipient email via getContactInfo(user) or falls back to notification.args.to
  2. Looks up the template by notification.template name
  3. Renders HTML via the compiled Handlebars template
  4. Sends via transporter.sendMail({ from, to, bcc, subject, html })
  5. Calls optional onFinished callback
Throws if to is undefined or template is not found.

Email Transports

Transport is selected via the EMAIL_TRANSPORTER environment variable in services.ts:96-113:

Transport Composition

combineNodeMailerMethods (email-notifier.ts:49-62) sends via multiple transports simultaneously using Promise.all. Returns the last transport’s response. Used in test utilities to combine dev + test methods.

Template System

Loading (templates.ts:27-35)

newEmailTemplateMap loads templates synchronously at startup:
  1. Reads all .handlebars files from src/partials/ and registers as Handlebars partials
  2. Reads all .handlebars files from src/templates/ and compiles with { noEscape: true }
  3. Returns a map of { templateName: compiledFunction }
noEscape: true means all Handlebars expressions render raw HTML without escaping. This is intentional for HTML email content but requires that template variables are sanitized upstream if they contain user-provided content.

Email Templates (10)

Layout Partials (3)

AWS SES Configuration

newSesClient (aws.ts:6-13) creates an AWS SES client with API version 2010-12-01 using aws-sdk v2. newAwsConnectionConfig (aws.ts:15-21) builds credentials from environment:
The SES region is passed as a function parameter (not read from env directly by the notifications module). The wiring in services.ts reads AWS_SES_REGION and passes it through. These are separate credentials from S3 file storage (AWS_S3_ACCESS_KEY_ID / AWS_S3_SECRET_ACCESS_KEY).

Complete Environment Variables

Type Definitions

Core Types (types.ts)

Transport Types (email-notifier.ts)

User Email Resolution

UserEmailGetter is implemented as getUserContactInfo in persistence/src/common/reading.ts:528-542:
  • SQL: SELECT "fullName", users."email" FROM "users" LEFT JOIN profiles ON profiles."id" = $1 WHERE "users"."id" = $1
  • Returns { name: fullName, address: email } or undefined if not found
  • Wired in services.ts:137 and passed to newNodeMailerNotifier

Legacy: Mandrill

The mandrill/ subdirectory contains a Mandrill (Mailchimp Transactional) implementation that is fully commented out. The index.ts re-exports types only. This is dead code from a previous email provider; current production uses Nodemailer + SES exclusively.

Known Issues