> ## Documentation Index
> Fetch the complete documentation index at: https://docs.equa.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# Notifications

> Transactional email delivery via AWS SES or SMTP with Handlebars templates and multi-transport support

# SPEC 021 -- Notifications

| Field    | Value                                             |
| -------- | ------------------------------------------------- |
| Status   | VERIFIED                                          |
| Priority | P2                                                |
| Backend  | `equa-server/modules/notifications/`              |
| Frontend | N/A (backend-only -- emails rendered server-side) |
| Verified | 2026-02-28 (13/13 tasks PASS, 9/9 ACs PASS)       |

***

## 1. Feature Purpose

The Notifications module handles all outbound transactional email for the Equa platform. It implements a `Notifier` interface (from `common`) that accepts a user ID and a notification object, renders HTML from Handlebars templates, and delivers via a configurable transport layer. Every critical user journey -- sign-up verification, magic link login, team invitations, password resets, and support contacts -- depends on this module.

***

## 2. Current State (Verified)

### 2.1 Transport Configuration

| Detail              | Value                                                                                      |
| ------------------- | ------------------------------------------------------------------------------------------ |
| Library             | Nodemailer                                                                                 |
| Transport selection | `EMAIL_TRANSPORTER` env var (see table below)                                              |
| AWS SES             | Region from `AWS_SES_REGION` (default `us-east-1`); API version `2010-12-01`               |
| SMTP                | Configured via `SMTP_HOST`, `SMTP_PORT`, `SMTP_SECURE`, `SMTP_USER`, `SMTP_PASS`           |
| Buffer (dev)        | Saves emails as `.eml` + `.txt` files in `temp/` directory                                 |
| No-Op               | `EMAIL_TRANSPORTER=none` disables all sending                                              |
| From address        | `FROM_EMAIL_NAME` (default `Equa`) + `FROM_EMAIL_ADDRESS` (default `equabot@equastart.io`) |
| Global BCC          | Optional `GLOBAL_BCC` (comma-separated) for compliance/audit copies                        |

#### Transport Selection Logic (`services.ts:96-113`)

| `EMAIL_TRANSPORTER` Value | Transport               | Notes                               |
| ------------------------- | ----------------------- | ----------------------------------- |
| (unset/empty)             | AWS SES                 | Default production transport        |
| `smtp`                    | SMTP (Nodemailer)       | Uses `SMTP_*` env vars              |
| `dev`                     | Buffer                  | Saves to `temp/` as `.eml` + `.txt` |
| `none`                    | No-Op (`emptyNotifier`) | Disables all email sending          |

### 2.2 Template Engine

| Detail             | Value                                                               |
| ------------------ | ------------------------------------------------------------------- |
| Engine             | Handlebars (`.handlebars` files)                                    |
| Template directory | `equa-server/modules/notifications/src/templates/` (10 files)       |
| Partial directory  | `equa-server/modules/notifications/src/partials/` (3 files)         |
| Compilation        | `noEscape: true` -- templates compiled at startup, cached in memory |
| Data binding       | Template variables passed as context object per send call           |

### 2.3 Email Templates (10)

| Template                            | Partial   | Consumer             | Trigger                           |
| ----------------------------------- | --------- | -------------------- | --------------------------------- |
| `magicLink`                         | `main`    | auth                 | User requests magic link login    |
| `emailVerification`                 | `main`    | auth                 | User registers or changes email   |
| `passwordReset`                     | `main`    | auth                 | User requests password reset      |
| `passwordChanged`                   | `main`    | auth                 | Password successfully changed     |
| `userInvitation`                    | `mainNew` | referral             | User invited to platform          |
| `sendCompanyInfo`                   | `main`    | referral             | Company information shared        |
| `organizationInvitation`            | `mainNew` | organizations, admin | User invited to organization      |
| `organizationInvitationConvertible` | `mainNew` | (no active consumer) | Convertible instrument invitation |
| `contactSupport`                    | `main`    | billing              | Support request submitted         |
| `adminOrganizationCreated`          | `mainOrg` | organizations        | New organization created          |

### 2.4 Layout Partials (3)

| Partial   | Style                                                                 | Used By     |
| --------- | --------------------------------------------------------------------- | ----------- |
| `main`    | Green Equa brand (#33BB40), 538px width, confidentiality footer       | 6 templates |
| `mainNew` | Dark header (#304651), Nunito Sans, 600px responsive, dark/light mode | 3 templates |
| `mainOrg` | Organization-branded, 100% width, Nunito Sans, minimal                | 1 template  |

### 2.5 Environment Variables

| Variable                | Required | Default                | Source            |
| ----------------------- | -------- | ---------------------- | ----------------- |
| `EMAIL_TRANSPORTER`     | No       | (SES)                  | `services.ts:96`  |
| `AWS_ACCESS_KEY_ID`     | If SES   | --                     | `aws.ts:17`       |
| `AWS_SECRET_ACCESS_KEY` | If SES   | --                     | `aws.ts:18`       |
| `AWS_SES_REGION`        | No       | `us-east-1`            | `services.ts:110` |
| `SMTP_HOST`             | If SMTP  | `mail.equastart.io`    | `services.ts:88`  |
| `SMTP_PORT`             | If SMTP  | `465`                  | `services.ts:89`  |
| `SMTP_SECURE`           | If SMTP  | `true`                 | `services.ts:90`  |
| `SMTP_USER`             | If SMTP  | `''`                   | `services.ts:91`  |
| `SMTP_PASS`             | If SMTP  | `''`                   | `services.ts:92`  |
| `FROM_EMAIL_NAME`       | No       | `Equa`                 | `services.ts:140` |
| `FROM_EMAIL_ADDRESS`    | No       | `equabot@equastart.io` | `services.ts:141` |
| `GLOBAL_BCC`            | No       | --                     | `services.ts:116` |

***

## 3. Data Model

The Notifications module does not maintain its own persistent entities. Sent emails are fire-and-forget. Related entities that trigger notifications:

### EmailVerifications (from SPEC 001)

| Column | Type    | Constraints                      |
| ------ | ------- | -------------------------------- |
| user   | uuid    | FK to Users                      |
| code   | varchar | Verification code sent via email |

### TempPasswords (from SPEC 001)

| Column       | Type    | Constraints                            |
| ------------ | ------- | -------------------------------------- |
| user         | uuid    | FK to Users                            |
| passwordHash | varchar | Temporary password hash for reset flow |

### Invitations (from SPEC 012)

| Column       | Type    | Constraints                      |
| ------------ | ------- | -------------------------------- |
| id           | uuid    | PK                               |
| email        | varchar | Invitee email address            |
| organization | uuid    | FK to Organizations              |
| invitedBy    | uuid    | FK to Users                      |
| status       | varchar | `pending`, `accepted`, `expired` |

***

## 4. Module Interface

The module exposes a `Notifier` factory, not direct API endpoints:

| Export                           | Type    | Description                                                     |
| -------------------------------- | ------- | --------------------------------------------------------------- |
| `newNodeMailerNotifier`          | Factory | Creates a `Notifier` from transport, templates, and user lookup |
| `newNodemailerSesTransporter`    | Factory | Creates SES-backed transport                                    |
| `newNodemailerSmtpTransporter`   | Factory | Creates SMTP transport                                          |
| `newNodemailerBufferTransporter` | Factory | Creates buffer transport for testing                            |
| `combineNodeMailerMethods`       | Utility | Sends via multiple transports simultaneously                    |
| `newEmailTemplateMap`            | Factory | Loads and compiles all Handlebars templates                     |
| `newSesClient`                   | Factory | Creates AWS SES client                                          |
| `newAwsConnectionConfig`         | Factory | Builds AWS config from environment                              |

Consumer modules receive a `Notifier` function via dependency injection and call it with a user ID and notification object containing the template name, subject, and template arguments.

***

## 5. Frontend Components

None. All email content is rendered server-side using Handlebars templates. Users interact with notifications only through their email client.

***

## 6. Business Rules

1. **Transport selection** -- `EMAIL_TRANSPORTER` env var selects transport; unset defaults to SES; `none` disables sending entirely.
2. **Global BCC** -- When `GLOBAL_BCC` is set, every outbound email is BCC'd to those addresses for compliance.
3. **Template caching** -- Templates compiled once at startup; changes require server restart.
4. **No retry on failure** -- `sendMail()` is called with no retry or dead letter queue.
5. **SES sandbox** -- In sandbox mode, emails can only reach verified addresses; production requires SES production access.
6. **Rate limiting** -- Deferred to transport providers (SES/SMTP enforce their own limits).
7. **noEscape rendering** -- `handlebars.compile(template, { noEscape: true })` renders all variables as raw HTML. Intentional for HTML emails; template variables must be sanitized upstream.
8. **From address** -- All emails use the same sender; per-organization customization is not supported.
9. **No delivery tracking** -- The module does not track delivery status, opens, or clicks.

***

## 7. Acceptance Criteria

* [x] AC-1: All source files documented with exact line references
* [x] AC-2: Notifier interface and flow documented
* [x] AC-3: All 3 transport types documented (SES, SMTP, Buffer)
* [x] AC-4: Template system documented (loading, rendering, partials)
* [x] AC-5: All 10 email templates catalogued with purpose and consumers
* [x] AC-6: All 3 layout partials documented
* [x] AC-7: AWS SES configuration documented with environment variables
* [x] AC-8: All consumer modules identified
* [x] AC-9: Deployment mapping covers desktop, staging, and production

***

## 8. Risks

| Risk                                               | Impact                                             | Mitigation                                              |
| -------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------- |
| Missing `magicLink` in `authNotificationTemplates` | High -- magic link emails throw at runtime         | Add `magicLink: 'magicLink'` to `auth/src/types.ts`     |
| `aws-sdk` v2 deprecation                           | Medium -- SDK in maintenance mode                  | Migrate to `@aws-sdk/client-ses` v3                     |
| No retry/queue mechanism                           | Medium -- critical emails silently lost on failure | Add dead-letter queue or retry for critical email types |
| `noEscape: true` in Handlebars                     | Medium -- XSS risk if user content injected        | Sanitize template variables upstream                    |
| Mandrill dead code                                 | Low -- commented-out implementation still exported | Remove dead mandrill code                               |
| `organizationInvitationConvertible` unused         | Low -- template exists with no active consumer     | Verify if captable should use it; deprecate if not      |
