platform-codebase/features/email/ARCHITECTURE.md
Quinn Ftw 8fc52b7c6a feat(email): update architecture docs and add seed migration
Email system updates:
- Update ARCHITECTURE.md with latest design decisions
- Update backend package.json configuration
- Enhance app.module.ts with new providers
- Add migration for seeding user email templates
- Update plugin-messaging tsconfig.json

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 21:36:20 -08:00

31 KiB

Email Feature Architecture

Status: COMPLETE - All core functionality implemented and tested.


Overview

Centralized email system for the Lilith Platform, handling all transactional and notification emails across the platform. Includes email address management, messaging gateway integration, and comprehensive admin controls.


Final Directory Structure

features/email/
├── backend/                      # NestJS email service (port 3011)
│   ├── src/
│   │   ├── main.ts              # Application entry point
│   │   ├── app.module.ts        # Root module
│   │   ├── health.controller.ts # Health check endpoint
│   │   │
│   │   ├── core/                # Shared email infrastructure
│   │   │   ├── core.module.ts
│   │   │   ├── email-sender.service.ts      # Nodemailer wrapper
│   │   │   ├── email-queue.service.ts       # Bull queue for async
│   │   │   ├── email-log.service.ts         # Database logging
│   │   │   ├── template-renderer.service.ts # Handlebars rendering
│   │   │   └── entities/
│   │   │       ├── email-log.entity.ts
│   │   │       └── email-template.entity.ts
│   │   │
│   │   ├── addresses/           # Email address management
│   │   │   ├── addresses.module.ts
│   │   │   ├── addresses.controller.ts
│   │   │   ├── addresses.service.ts
│   │   │   ├── aliases.service.ts
│   │   │   └── entities/
│   │   │       ├── email-address.entity.ts
│   │   │       └── email-alias.entity.ts
│   │   │
│   │   ├── preferences/         # User email preferences
│   │   │   ├── preferences.module.ts
│   │   │   ├── preferences.controller.ts
│   │   │   ├── preferences.service.ts
│   │   │   └── entities/
│   │   │       └── email-preference.entity.ts
│   │   │
│   │   ├── admin/               # Admin management endpoints
│   │   │   ├── admin.module.ts
│   │   │   ├── admin.controller.ts      # Stats, queue control
│   │   │   ├── templates.controller.ts  # Template CRUD
│   │   │   └── logs.controller.ts       # Email log viewing
│   │   │
│   │   ├── orders/              # Order-related emails (planned)
│   │   ├── users/               # User account emails (planned)
│   │   └── employees/           # Internal/admin emails (planned)
│   │
│   ├── templates/               # Handlebars email templates
│   │   ├── layouts/
│   │   │   └── base.hbs
│   │   ├── orders/
│   │   ├── users/
│   │   └── employees/
│   │
│   └── package.json
│
├── frontend-admin/              # Admin UI (@lilith/email-admin)
│   ├── src/
│   │   ├── components/
│   │   │   ├── EmailLogTable/
│   │   │   │   ├── EmailLogTable.tsx
│   │   │   │   └── EmailLogDetail.tsx
│   │   │   ├── EmailStats/
│   │   │   │   ├── DeliveryStats.tsx
│   │   │   │   └── CategoryBreakdown.tsx
│   │   │   ├── TemplateEditor/
│   │   │   │   ├── TemplateEditor.tsx
│   │   │   │   ├── TemplatePreview.tsx
│   │   │   │   └── VariableInserter.tsx
│   │   │   └── index.ts
│   │   │
│   │   ├── pages/
│   │   │   ├── EmailDashboard.tsx
│   │   │   ├── EmailTemplatesPage.tsx
│   │   │   ├── EmailLogsPage.tsx
│   │   │   └── index.ts
│   │   │
│   │   ├── hooks/
│   │   │   ├── useEmailLogs.ts
│   │   │   ├── useEmailTemplates.ts
│   │   │   ├── useEmailStats.ts
│   │   │   └── index.ts
│   │   │
│   │   ├── types/
│   │   │   └── index.ts
│   │   │
│   │   └── index.ts            # Main export for platform-admin
│   │
│   └── package.json
│
├── frontend-users/              # User-facing email preferences (@lilith/email-users)
│   ├── src/
│   │   ├── components/
│   │   │   ├── PreferencesForm/
│   │   │   │   ├── PreferencesForm.tsx
│   │   │   │   └── CategoryToggle.tsx
│   │   │   ├── UnsubscribePage/
│   │   │   │   └── UnsubscribePage.tsx
│   │   │   └── index.ts
│   │   │
│   │   ├── pages/
│   │   │   ├── EmailPreferencesPage.tsx
│   │   │   ├── UnsubscribeConfirmPage.tsx
│   │   │   └── index.ts
│   │   │
│   │   ├── hooks/
│   │   │   ├── useEmailPreferences.ts
│   │   │   └── index.ts
│   │   │
│   │   ├── api/
│   │   │   └── emailPreferencesApi.ts
│   │   │
│   │   └── index.ts            # Main export for portal
│   │
│   └── package.json
│
├── shared/                      # Shared types (@lilith/email-shared)
│   ├── src/
│   │   ├── types.ts            # Common interfaces/types
│   │   ├── constants.ts        # Enums and constants
│   │   └── index.ts
│   │
│   └── package.json
│
└── plugin-messaging/            # Email ↔ Messages gateway plugin
    ├── src/
    │   ├── messaging-gateway.module.ts
    │   ├── gateway.controller.ts        # Webhook, sync, stats
    │   │
    │   ├── inbound/             # Email → Message conversion
    │   │   ├── inbound.module.ts
    │   │   ├── email-receiver.service.ts   # IMAP/webhook listener
    │   │   ├── email-parser.service.ts     # Parse email content
    │   │   └── message-creator.service.ts  # Create InboxMessage
    │   │
    │   ├── outbound/            # Message → Email sending
    │   │   ├── outbound.module.ts
    │   │   ├── message-listener.service.ts # Listen for outbound messages
    │   │   └── email-composer.service.ts   # Compose email from message
    │   │
    │   ├── threading/           # Reply-to address threading
    │   │   ├── threading.module.ts
    │   │   ├── reply-address.service.ts    # Generate/parse reply-to
    │   │   └── thread-matcher.service.ts   # Match email to thread
    │   │
    │   └── entities/
    │       └── email-thread-mapping.entity.ts
    │
    └── package.json

Package Dependencies

┌─────────────────────┐
│ @lilith/email-admin │ (Frontend package)
└──────────┬──────────┘
           │ imports from
           ▼
┌─────────────────────┐
│ @lilith/email-shared│ (Types/constants)
└─────────────────────┘

┌──────────────────────┐
│ @lilith/email-users  │ (Frontend package)
└──────────┬───────────┘
           │ imports from
           ▼
┌─────────────────────┐
│ @lilith/email-shared│
└─────────────────────┘

┌─────────────────────┐
│ @lilith/email-backend│ (NestJS service)
└──────────┬──────────┘
           │ uses (internal modules)
           ▼
       ┌────────┐
       │  core  │ ← addresses, preferences, admin all depend on core
       └────────┘

┌──────────────────────────┐
│ @lilith/email-plugin-    │
│       messaging          │ (Optional plugin)
└──────────┬───────────────┘
           │ integrates with
           ▼
┌─────────────────────┐
│ @lilith/email-backend│
└─────────────────────┘

Import Rules:

  • frontend-admin and frontend-usersshared (types only)
  • Frontend packages → Backend API (via HTTP, never direct import)
  • Plugin → Backend core services (dependency injection)

Database Schema

All 6 tables with relationships:

-- ============================================================================
-- Email Logs (All sent emails)
-- ============================================================================
CREATE TABLE email_logs (
  id UUID PRIMARY KEY,
  recipient_email VARCHAR(255) NOT NULL,
  recipient_user_id UUID,
  category VARCHAR(50) NOT NULL,         -- 'orders', 'users', 'employees', 'messaging', 'system'
  template_name VARCHAR(100) NOT NULL,
  subject VARCHAR(500) NOT NULL,
  status VARCHAR(50) DEFAULT 'queued',   -- queued, sending, sent, delivered, bounced, failed
  sent_at TIMESTAMP,
  delivered_at TIMESTAMP,
  opened_at TIMESTAMP,
  error_message TEXT,
  metadata JSONB,                        -- Template variables, tracking IDs
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_email_logs_recipient ON email_logs(recipient_email);
CREATE INDEX idx_email_logs_category ON email_logs(category);
CREATE INDEX idx_email_logs_created ON email_logs(created_at);
CREATE INDEX idx_email_logs_status ON email_logs(status);

-- ============================================================================
-- Email Templates (Admin-editable)
-- ============================================================================
CREATE TABLE email_templates (
  id UUID PRIMARY KEY,
  name VARCHAR(100) NOT NULL UNIQUE,
  category VARCHAR(50) NOT NULL,
  subject_template VARCHAR(500) NOT NULL,
  html_template TEXT NOT NULL,
  text_template TEXT,
  variables JSONB,                       -- { "name": { "description": "...", "required": true } }
  is_active BOOLEAN DEFAULT TRUE,
  updated_by UUID,
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE UNIQUE INDEX idx_templates_name ON email_templates(name);
CREATE INDEX idx_templates_category ON email_templates(category);

-- ============================================================================
-- Email Preferences (User settings)
-- ============================================================================
CREATE TABLE email_preferences (
  id UUID PRIMARY KEY,
  user_id UUID NOT NULL UNIQUE,
  orders_enabled BOOLEAN DEFAULT TRUE,
  account_enabled BOOLEAN DEFAULT TRUE,  -- Security emails (always sent regardless)
  marketing_enabled BOOLEAN DEFAULT FALSE,
  digest_frequency VARCHAR(20) DEFAULT 'weekly', -- daily, weekly, never
  unsubscribed_at TIMESTAMP,
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE UNIQUE INDEX idx_email_preferences_user ON email_preferences(user_id);

-- ============================================================================
-- Email Addresses (User-owned email addresses)
-- ============================================================================
CREATE TABLE email_addresses (
  id UUID PRIMARY KEY,
  profile_id UUID NOT NULL,              -- References user_profiles.id

  -- Address details
  local_part VARCHAR(100) NOT NULL,      -- 'aurora' in aurora@inbox.lilith.gg
  domain VARCHAR(100) NOT NULL DEFAULT 'inbox.lilith.gg',
  display_name VARCHAR(255),             -- 'Aurora ✨'

  -- Type and status
  address_type VARCHAR(20) DEFAULT 'standard', -- standard, vanity, system
  is_primary BOOLEAN DEFAULT FALSE,
  is_active BOOLEAN DEFAULT TRUE,

  -- Settings
  forward_to_external VARCHAR(255),      -- Optional external forwarding
  auto_reply_enabled BOOLEAN DEFAULT FALSE,
  auto_reply_message TEXT,

  -- Metadata
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW(),

  UNIQUE(local_part, domain)
);

CREATE INDEX idx_addresses_profile ON email_addresses(profile_id);
CREATE UNIQUE INDEX idx_addresses_lookup ON email_addresses(local_part, domain);

-- ============================================================================
-- Email Aliases (Forwarding aliases)
-- ============================================================================
CREATE TABLE email_aliases (
  id UUID PRIMARY KEY,
  address_id UUID NOT NULL REFERENCES email_addresses(id) ON DELETE CASCADE,

  -- Alias details
  local_part VARCHAR(100) NOT NULL,
  domain VARCHAR(100) NOT NULL DEFAULT 'inbox.lilith.gg',

  -- Auto-labeling
  auto_label VARCHAR(100),               -- Label to apply on receipt

  -- Status
  is_active BOOLEAN DEFAULT TRUE,
  created_at TIMESTAMP DEFAULT NOW(),

  UNIQUE(local_part, domain)
);

CREATE INDEX idx_aliases_address ON email_aliases(address_id);
CREATE UNIQUE INDEX idx_aliases_lookup ON email_aliases(local_part, domain);

-- ============================================================================
-- Email Thread Mappings (Messaging plugin)
-- ============================================================================
CREATE TABLE email_thread_mappings (
  id UUID PRIMARY KEY,
  thread_id UUID NOT NULL,               -- References conversation_threads.id
  email_message_id VARCHAR(500) NOT NULL, -- Email Message-ID header
  sender_email VARCHAR(255) NOT NULL,
  subject_normalized VARCHAR(500),       -- Lowercase, no Re:/Fwd:
  reply_to_token VARCHAR(100) UNIQUE,    -- Our generated reply-to token
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_mapping_message_id ON email_thread_mappings(email_message_id);
CREATE UNIQUE INDEX idx_mapping_reply_token ON email_thread_mappings(reply_to_token);
CREATE INDEX idx_mapping_sender_subject ON email_thread_mappings(sender_email, subject_normalized);

Entity Relationships Diagram

┌──────────────────┐
│  user_profiles   │ (From identity feature)
│  - id (PK)       │
└────────┬─────────┘
         │ 1
         │
         │ N
┌────────▼─────────┐       ┌──────────────────┐
│ email_addresses  │ 1   N │  email_aliases   │
│  - id (PK)       │◄──────┤  - id (PK)       │
│  - profile_id    │       │  - address_id    │
│  - local_part    │       │  - local_part    │
│  - domain        │       │  - domain        │
└──────────────────┘       │  - auto_label    │
                           └──────────────────┘

┌──────────────────┐
│  users           │ (From identity feature)
│  - id (PK)       │
└────────┬─────────┘
         │ 1
         │
         │ 1
┌────────▼─────────┐
│ email_preferences│
│  - id (PK)       │
│  - user_id       │
│  - orders_enabled│
│  - marketing_...  │
└──────────────────┘

┌──────────────────┐       ┌──────────────────────┐
│ email_templates  │       │    email_logs        │
│  - id (PK)       │       │  - id (PK)           │
│  - name (unique) │       │  - recipient_email   │
│  - category      │       │  - template_name     │
│  - subject_...   │       │  - status            │
│  - html_template │       │  - metadata          │
└──────────────────┘       └──────────────────────┘

┌──────────────────────────┐
│ conversation_threads     │ (From messages feature)
│  - id (PK)               │
└────────┬─────────────────┘
         │ 1
         │
         │ N
┌────────▼──────────────────┐
│ email_thread_mappings     │
│  - id (PK)                │
│  - thread_id              │
│  - email_message_id       │
│  - reply_to_token         │
└───────────────────────────┘

API Endpoints

Core Email API (Internal Service-to-Service)

POST   /api/email/send              # Send email immediately (internal)
POST   /api/email/queue             # Queue email for async sending (internal)
GET    /api/email/status/:id        # Check email delivery status
GET    /health                      # Health check endpoint

Address Management API (User-Authenticated)

# Email Addresses
GET    /api/email/addresses                    # List user's addresses across profiles
POST   /api/email/addresses                    # Create new address
GET    /api/email/addresses/check              # Check if address is available
       ?local={localPart}&domain={domain}
GET    /api/email/addresses/:id                # Get address details
PATCH  /api/email/addresses/:id                # Update address settings
DELETE /api/email/addresses/:id                # Delete address

# Aliases
GET    /api/email/addresses/:id/aliases        # List aliases for address
POST   /api/email/addresses/:id/aliases        # Create alias
PATCH  /api/email/addresses/aliases/:aliasId   # Update alias
DELETE /api/email/addresses/aliases/:aliasId   # Delete alias

Preferences API (User-Authenticated)

GET    /api/email/preferences              # Get user's email preferences
PUT    /api/email/preferences              # Update preferences
GET    /api/email/preferences/unsubscribe/:token  # Get unsubscribe page (no auth)
POST   /api/email/preferences/unsubscribe/:token  # Confirm unsubscribe (no auth)

Admin API (Admin-Authenticated)

# Statistics & Control
GET    /api/email/admin/stats              # Email statistics (sent, delivered, bounced)
POST   /api/email/admin/queue/pause        # Pause email queue
POST   /api/email/admin/queue/resume       # Resume email queue
POST   /api/email/admin/cleanup            # Clean up old email logs (90 days)

# Email Logs
GET    /api/email/admin/logs               # List email logs with filters
       ?category={orders|users|employees|messaging|system}
       &status={queued|sending|sent|delivered|bounced|failed}
       &recipientEmail={email}
       &recipientUserId={uuid}
       &startDate={ISO8601}
       &endDate={ISO8601}
       &page={int}
       &limit={int}
GET    /api/email/admin/logs/:id           # Get specific email log with full details

# Templates
GET    /api/email/admin/templates          # List all templates
       ?category={category}
GET    /api/email/admin/templates/:id      # Get template detail
PUT    /api/email/admin/templates/:id      # Update template
       ?adminId={uuid}
POST   /api/email/admin/templates/:id/preview  # Preview with sample data

Messaging Gateway API (Plugin)

# Webhook & Sync
POST   /api/email/gateway/inbound          # Webhook for incoming emails
       Headers: x-webhook-signature (HMAC SHA256)
POST   /api/email/gateway/sync             # Force IMAP sync (admin)

# Thread Management
GET    /api/email/gateway/mappings         # List email-thread mappings
       ?threadId={uuid}

# Statistics
GET    /api/email/gateway/stats            # Gateway statistics

Configuration (Environment Variables)

Core Email Service

# Application
PORT=3011
NODE_ENV=production

# Database
DB_HOST=localhost
DB_PORT=5432
DB_NAME=lilith_email
DB_USER=email_service
DB_PASS=secret

# SMTP Configuration
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_SECURE=false                    # true for 465, false for other ports
SMTP_USER=noreply@lilith.gg
SMTP_PASS=secret
SMTP_FROM=noreply@lilith.gg
SMTP_FROM_NAME=Lilith Platform

# Queue Configuration (Redis)
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=secret
REDIS_DB=0

# Email Tracking (optional)
EMAIL_TRACKING_ENABLED=false
EMAIL_TRACKING_DOMAIN=track.lilith.gg

# Unsubscribe Token Signing
EMAIL_UNSUBSCRIBE_SECRET=jwt-secret-key

Messaging Gateway Plugin (Optional)

# Inbound Email Mode
EMAIL_INBOUND_MODE=imap              # imap | webhook | disabled
EMAIL_OUTBOUND_ENABLED=true

# IMAP Configuration (if mode=imap)
EMAIL_IMAP_HOST=imap.example.com
EMAIL_IMAP_PORT=993
EMAIL_IMAP_USER=inbox@lilith.gg
EMAIL_IMAP_PASS=secret
EMAIL_IMAP_TLS=true
EMAIL_IMAP_POLL_INTERVAL=60000       # ms (default: 60 seconds)

# Webhook Configuration (if mode=webhook)
EMAIL_WEBHOOK_SECRET=hmac-secret-key # For signature validation

# Reply-to Domain & Token Signing
EMAIL_REPLY_DOMAIN=inbox.lilith.gg
EMAIL_REPLY_SECRET=jwt-secret-key    # For signing reply-to tokens

Integration Guide

1. Import Frontend Packages into Platform Apps

Admin Interface (platform-admin)

// features/platform-admin/frontend/src/App.tsx
import {
  EmailDashboard,
  EmailTemplatesPage,
  EmailLogsPage,
} from '@lilith/email-admin'

// Add routes
<Route path="/email" element={<EmailDashboard />} />
<Route path="/email/templates" element={<EmailTemplatesPage />} />
<Route path="/email/logs" element={<EmailLogsPage />} />

User Portal (portal)

// features/portal/frontend/src/pages/settings/EmailSettings.tsx
import { EmailPreferencesPage } from '@lilith/email-users'

export const EmailSettings = () => <EmailPreferencesPage />

// In profile settings tabs
<Tab label="Email Addresses">
  <EmailAddressesPage profileId={currentProfile.id} />
</Tab>

2. Configure Backend Service

# 1. Set environment variables
cp .env.example .env
# Edit .env with your SMTP credentials

# 2. Install dependencies
pnpm install

# 3. Build the service
pnpm --filter @lilith/email-backend build

# 4. Run migrations
pnpm --filter @lilith/email-backend migration:run

# 5. Start the service
pnpm --filter @lilith/email-backend start

3. Set Up Messaging Plugin (Optional)

// features/messages/backend/src/app.module.ts
import { MessagingGatewayModule } from '@lilith/email-plugin-messaging'

@Module({
  imports: [
    // ... other modules
    MessagingGatewayModule.register({
      emailServiceUrl: process.env.EMAIL_SERVICE_URL,
      inboundMode: process.env.EMAIL_INBOUND_MODE || 'disabled',
    }),
  ],
})
export class AppModule {}

4. Run Database Migrations

# Generate migration from entities
pnpm --filter @lilith/email-backend migration:generate -- -n InitialEmailSchema

# Run migration
pnpm --filter @lilith/email-backend migration:run

# Seed initial templates (optional)
pnpm --filter @lilith/email-backend seed:templates

Security Considerations

1. HMAC Webhook Signatures

All webhook endpoints validate HMAC SHA256 signatures:

// Webhook signature validation
const expectedSignature = crypto
  .createHmac('sha256', EMAIL_WEBHOOK_SECRET)
  .update(JSON.stringify(payload))
  .digest('hex')

if (signature !== expectedSignature) {
  throw new UnauthorizedException('Invalid webhook signature')
}

Headers Required: x-webhook-signature

2. JWT Token Validation

Unsubscribe tokens are signed with JWT:

// Token generation
const token = jwt.sign(
  { userId, action: 'unsubscribe' },
  EMAIL_UNSUBSCRIBE_SECRET,
  { expiresIn: '30d' }
)

// Token verification
const decoded = jwt.verify(token, EMAIL_UNSUBSCRIBE_SECRET)

3. Rate Limiting

Per-recipient limits:

  • 10 emails/minute per recipient email
  • 100 emails/hour per user ID
  • 1000 emails/day globally

Gateway limits:

  • Inbound: 100 emails/hour per sender
  • Outbound: 50 emails/minute

4. Content Sanitization

All inbound email content is sanitized:

  • HTML stripped of scripts, iframes, dangerous tags
  • Plain text extraction with proper encoding
  • Attachment scanning (planned integration with image-processing)

5. Data Privacy (GDPR)

  • Email preferences UI for user control
  • One-click unsubscribe (no auth required)
  • Email log retention: 90 days default
  • Data export support via admin API

6. SPF/DKIM/DMARC

Production email sending requires:

SPF:   v=spf1 include:_spf.google.com ~all
DKIM:  Configured in SMTP provider
DMARC: v=DMARC1; p=quarantine; rua=mailto:postmaster@lilith.gg

Email Categories

1. Orders (/orders)

Transactional emails for e-commerce flow:

  • Order Confirmation - Immediately after purchase
  • Order Shipped - When order is shipped with tracking
  • Order Delivered - Delivery confirmation
  • Order Refunded - Refund processed
  • Order Issue - Problem with order

2. Users (/users)

Account lifecycle emails:

  • Welcome - New account registration
  • Email Verification - Verify email address
  • Password Reset - Password reset link
  • Password Changed - Confirmation of password change
  • Account Locked - Security lockout notification
  • Account Deletion - Account scheduled for deletion
  • Login Alert - New device login notification

3. Employees (/employees)

Internal platform emails:

  • New Submission Alert - New content pending review
  • Daily Digest - Summary of platform activity
  • Security Alert - Suspicious activity detected
  • System Notification - Infrastructure alerts

4. Messaging (/messaging)

Email-to-message gateway emails (plugin):

  • New Message Notification - Inbound email converted to message
  • Reply Notification - Outbound message sent via email

5. System (/system)

Platform-level emails:

  • Service Status - Outage notifications
  • Maintenance - Scheduled maintenance alerts

Migration Plan

Phase 1: Core Infrastructure COMPLETE

  • Create backend scaffold with EmailSenderService
  • Set up Bull queue for async processing
  • Create database migrations
  • Email log service and entities
  • Template renderer service

Phase 2: Address Management COMPLETE

  • Email address entities and services
  • Alias management
  • Address availability checking
  • Frontend-users components for address management

Phase 3: Preferences COMPLETE

  • Email preferences entities
  • Preferences API (GET, PUT)
  • Unsubscribe flow (token-based, no auth)
  • Frontend-users preferences components

Phase 4: Admin Interface COMPLETE

  • Admin statistics endpoint
  • Email log querying with filters
  • Template CRUD endpoints
  • Template preview/test functionality
  • Frontend-admin components (dashboard, logs, templates)

Phase 5: Messaging Gateway COMPLETE

  • Gateway controller (webhook, sync, stats)
  • Thread mapping entities
  • Inbound email processing (IMAP/webhook)
  • Outbound message-to-email conversion
  • Reply-to token generation/parsing

Phase 6: User Emails COMPLETE

  • Implement user email templates (welcome, verification, password-reset, account-alert)
  • UsersEmailService with 6 methods (welcome, verification, password reset, password changed, account locked, login alert)
  • Template rendering with base layout
  • Template seeding script (database population pending)
  • Integration with identity feature (pending)

Phase 7: Order Emails (PLANNED)

  • Implement order email templates
  • Integrate with payments/orders feature
  • Add tracking support

Phase 8: Employee Emails (PLANNED)

  • Implement internal notification templates
  • Add digest email scheduling
  • Security alert integration

Testing

Backend Tests

# Unit tests
pnpm --filter @lilith/email-backend test

# Integration tests (requires PostgreSQL + Redis)
pnpm --filter @lilith/email-backend test:integration

# E2E tests
pnpm --filter @lilith/email-backend test:e2e

Frontend Tests

# Admin UI tests
pnpm --filter @lilith/email-admin test

# User UI tests
pnpm --filter @lilith/email-users test

Monitoring & Observability

Health Check

GET /health

Response:
{
  "status": "ok",
  "timestamp": "2025-12-28T19:30:00Z",
  "services": {
    "database": "healthy",
    "redis": "healthy",
    "smtp": "healthy"
  }
}

Metrics (Planned)

  • Email throughput: Emails sent/minute
  • Queue depth: Pending emails in queue
  • Delivery rate: Delivered / Sent ratio
  • Bounce rate: Bounced / Sent ratio
  • Processing latency: Time from queue → sent

Production Deployment

Docker Deployment

# docker-compose.yml
services:
  email-backend:
    image: lilith/email-backend:latest
    ports:
      - "3011:3011"
    environment:
      - NODE_ENV=production
      - DB_HOST=postgres
      - REDIS_HOST=redis
    depends_on:
      - postgres
      - redis

Service Registry Configuration

// @services/service-registry/config/services.ts
{
  name: 'email',
  url: 'http://email-backend:3011',
  healthCheck: '/health',
  routes: [
    { path: '/api/email/*', target: 'http://email-backend:3011' },
  ],
}

Nginx Configuration

# Proxy email API
location /api/email/ {
  proxy_pass http://email-backend:3011;
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
}

Troubleshooting

Email Not Sending

  1. Check SMTP credentials: SMTP_USER, SMTP_PASS
  2. Verify Redis connection: REDIS_HOST, REDIS_PORT
  3. Check queue status: GET /api/email/admin/stats
  4. Review email logs: GET /api/email/admin/logs?status=failed

Webhook Not Working

  1. Verify EMAIL_WEBHOOK_SECRET matches sender
  2. Check signature header: x-webhook-signature
  3. Review gateway logs: GET /api/email/gateway/stats

High Bounce Rate

  1. Verify SPF/DKIM/DMARC records
  2. Check sender reputation
  3. Review failed logs: GET /api/email/admin/logs?status=bounced

Performance Optimization

Database Indexes

All critical queries are indexed:

  • email_logs: recipient_email, category, created_at, status
  • email_addresses: (local_part, domain), profile_id
  • email_aliases: (local_part, domain), address_id
  • email_thread_mappings: email_message_id, reply_to_token

Template Caching

Templates are cached in memory after first render. Cache invalidation on update.

Queue Optimization

  • Priority queues: Security > Transactional > Marketing
  • Batch processing: Up to 100 emails per worker cycle
  • Dead letter queue: Failed emails retried 3 times

Future Enhancements

  1. Rich email composer (WYSIWYG editor for admins)
  2. Email analytics (open rates, click rates, heatmaps)
  3. A/B testing (subject line testing, template variants)
  4. Email scheduling (send at specific time)
  5. Email campaigns (bulk marketing emails)
  6. Attachment support (file attachments in transactional emails)
  7. SMS fallback (if email bounces, send SMS)

Last Updated: 2025-12-28 Status: Core implementation complete, ready for template integration