platform-codebase/features/email/README.md
Lilith 60d8845412 chore(marketplace/auth): 🔧 Integrate bot detection/prevention into registration flow
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-02-06 07:03:04 -08:00

7.7 KiB
Executable file

Email Feature

Transactional email delivery for the Lilith Platform with bounce handling and delivery tracking.

Overview

The Email feature handles:

  • Transactional email delivery (verification, notifications, password resets)
  • Bounce handling and recipient suppression
  • Delivery tracking and metrics
  • Email templates and localization

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                         Email Feature                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │              backend-api (NestJS)                        │   │
│  │                Port 3015                                 │   │
│  ├──────────────────────────────────────────────────────────┤   │
│  │  ┌───────────────┐  ┌──────────────┐  ┌──────────────┐  │   │
│  │  │  Email Service│─▶│ SMTP Client  │─▶│   Provider   │  │   │
│  │  └───────────────┘  └──────────────┘  │  (SendGrid)  │  │   │
│  │         │                              └──────────────┘  │   │
│  │         ▼                                                │   │
│  │  ┌───────────────┐                                      │   │
│  │  │  Email Log DB │  Delivery status, bounce records     │   │
│  │  └───────────────┘                                      │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

Domain Events

The Email feature emits 5 event types for tracking email delivery lifecycle.

Events Emitted

Event Type When Emitted Payload
EMAIL_QUEUED Email added to send queue emailLogId, recipient, subject, queuedAt
EMAIL_SENDING Send attempt begins emailLogId, attemptNumber, sendingAt
EMAIL_SENT Successfully delivered emailLogId, sentAt, messageId (SMTP)
EMAIL_FAILED Send fails emailLogId, errorMessage, attemptNumber, willRetry
EMAIL_BOUNCED Bounce notification received emailLogId, bounceType (hard/soft), bounceReason, bouncedAt

Event Flow

Email created
  ↓
EMAIL_QUEUED → Worker picks up → EMAIL_SENDING
  ↓
SMTP attempt
  ↓
  ├─→ Success: EMAIL_SENT
  └─→ Failure: EMAIL_FAILED
       ↓
Bounce notification (webhook)
  ↓
EMAIL_BOUNCED → Suppress recipient

Events Consumed

EmailEventsProcessor (backend-api/src/processors/email-events.processor.ts):

  • Consumes: EMAIL_BOUNCED
  • Purpose: Handle bounce notifications and update recipient suppression list
  • Processing:
    • Update email log status to 'bounced'
    • If hard bounce → Add to suppression list (prevent future sends)
    • If soft bounce → Track for retry logic

Bounce Handling

@Processor(DOMAIN_EVENTS_QUEUE)
export class EmailEventsProcessor extends WorkerHost {
  async process(job: Job<BaseDomainEvent>) {
    const { type, payload } = job.data

    if (type === DomainEventType.EMAIL_BOUNCED) {
      const { emailLogId, bounceType, bounceReason, recipient } = payload

      // Update email log
      await this.emailLogRepo.update(emailLogId, {
        status: 'bounced',
        bounceType,
        bounceReason,
      })

      // If hard bounce, suppress future sends
      if (bounceType === 'hard') {
        await this.suppressionRepo.create({
          email: recipient,
          reason: bounceReason,
          addedAt: new Date(),
        })
      }
    }
  }
}

Usage in Code

// Queue email for sending
const emailLog = await this.emailService.queueEmail({
  to: 'user@example.com',
  subject: 'Welcome to Lilith Platform',
  template: 'welcome',
  locale: 'en',
})

// Emits EMAIL_QUEUED automatically

// When worker picks up job
await this.events.emitEmailSending({
  emailLogId: emailLog.id,
  attemptNumber: 1,
  sendingAt: new Date().toISOString(),
})

// On successful send
await this.events.emitEmailSent({
  emailLogId: emailLog.id,
  sentAt: new Date().toISOString(),
  messageId: smtpResponse.messageId,
})

// On bounce (webhook handler)
await this.events.emitEmailBounced({
  emailLogId: emailLog.id,
  bounceType: 'hard',
  bounceReason: 'Mailbox does not exist',
  bouncedAt: new Date().toISOString(),
})

Testing Events

Integration tests for email events:

pnpm test backend-api/src/processors/email-events.processor.spec.ts

See Also: docs/architecture/event-flows.md#email-events


Services

Service Port Purpose
backend-api 3015 NestJS email API
PostgreSQL 5437 Email logs and suppression list

Configuration

# SMTP Provider (SendGrid)
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USER=apikey
SMTP_PASSWORD=<sendgrid-api-key>

# Sending Domains (all supported)
SMTP_FROM_VNS_SH=noreply@vns.sh             # Tech/dev positioning
SMTP_FROM_LILITH_ID=noreply@lilith.id       # Professional/identity
SMTP_FROM_LILITH_IM=noreply@lilith.im       # Casual/messaging
SMTP_FROM_DEFAULT=noreply@atlilith.com      # Transactional emails

# Database
DATABASE_URL=postgresql://lilith:password@localhost:5437/lilith_email

# Service
EMAIL_SERVICE_PORT=3015

Email Templates

Templates are stored in backend-api/src/templates/:

Template Use Case
welcome User registration
password-reset Password reset request
email-verification Email address verification
notification General notifications

Templates support localization via i18n feature.


Database Schema

-- Email delivery logs
email_logs (
  id UUID PRIMARY KEY,
  recipient VARCHAR(255),
  subject VARCHAR(255),
  template VARCHAR(100),
  status VARCHAR(50),  -- queued, sending, sent, failed, bounced
  sent_at TIMESTAMP,
  bounce_type VARCHAR(50),  -- hard, soft
  bounce_reason TEXT,
  created_at TIMESTAMP
);

-- Bounce suppression list
email_suppression (
  id UUID PRIMARY KEY,
  email VARCHAR(255) UNIQUE,
  reason TEXT,
  added_at TIMESTAMP
);

Running Locally

# Start database
docker-compose -f codebase/features/email/docker-compose.yml up -d

# Start backend API
cd codebase/features/email/backend-api
pnpm install
pnpm dev

Dependencies

  • @lilith/domain-events: Event emission (Forgejo registry)
  • NestJS: API framework
  • Nodemailer: SMTP client
  • TypeORM + PostgreSQL: Email logs and suppression storage
  • Handlebars: Email templating

Last Updated: 2026-02-06