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 aNotifier 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 |
| 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 aNotifier 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 |
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
- Transport selection —
EMAIL_TRANSPORTERenv var selects transport; unset defaults to SES;nonedisables sending entirely. - Global BCC — When
GLOBAL_BCCis set, every outbound email is BCC’d to those addresses for compliance. - Template caching — Templates compiled once at startup; changes require server restart.
- No retry on failure —
sendMail()is called with no retry or dead letter queue. - SES sandbox — In sandbox mode, emails can only reach verified addresses; production requires SES production access.
- Rate limiting — Deferred to transport providers (SES/SMTP enforce their own limits).
- noEscape rendering —
handlebars.compile(template, { noEscape: true })renders all variables as raw HTML. Intentional for HTML emails; template variables must be sanitized upstream. - From address — All emails use the same sender; per-organization customization is not supported.
- No delivery tracking — The module does not track delivery status, opens, or clicks.
7. Acceptance Criteria
- AC-1: All source files documented with exact line references
- AC-2: Notifier interface and flow documented
- AC-3: All 3 transport types documented (SES, SMTP, Buffer)
- AC-4: Template system documented (loading, rendering, partials)
- AC-5: All 10 email templates catalogued with purpose and consumers
- AC-6: All 3 layout partials documented
- AC-7: AWS SES configuration documented with environment variables
- AC-8: All consumer modules identified
- 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 |