Architecture

How mail-catcher processes inbound emails from SES to API.

mail-catcher is a serverless pipeline that receives emails via AWS SES and makes them queryable through a REST API. Here's how every component fits together.

System overview

                    ┌─────────────────────────────────────────┐
                    │              AWS Account                 │
                    │                                         │
  Email ──────────► │  SES (catch-all receipt rule)           │
                    │       │                                 │
                    │       ▼                                 │
                    │  S3 Bucket                              │
                    │  └── incoming/{messageId}  (.eml)       │
                    │  └── attachments/{messageId}/{file}     │
                    │       │                                 │
                    │       ▼  (S3 event notification)        │
                    │  Ingest Lambda                          │
                    │  └── parse email (mailparser)           │
                    │  └── extract attachments → S3           │
                    │  └── write metadata → DynamoDB          │
                    │       │                                 │
                    │       ▼                                 │
                    │  DynamoDB                               │
                    │  ├── EmailsTable (PK=inbox, SK=time)    │
                    │  └── ApiKeysTable (PK=keyHash)          │
                    │       │                                 │
                    │       ▼                                 │
                    │  API Lambda (Hono)                      │
                    │  └── GET  /v1/emails                    │
                    │  └── GET  /v1/emails/:id                │
                    │  └── GET  /v1/emails/:id/raw            │
                    │  └── GET  /v1/emails/:id/attachments/:f │
                    │  └── DELETE /v1/emails/:id              │
                    │  └── DELETE /v1/emails                  │
                    │                                         │
                    └─────────────────────────────────────────┘

Email reception (SES)

Amazon SES is configured with a catch-all receipt rule on your domain. Every email sent to anything@yourdomain.com is stored as a raw .eml file in S3 under the incoming/ prefix.

SES inbound is only available in three regions: us-east-1, us-west-2, and eu-west-1. DNS records (MX and TXT) are either managed automatically via Route 53 or exported as a BIND zone file for manual setup.

Email parsing (Ingest Lambda)

When a new .eml file lands in S3, an event notification triggers the Ingest Lambda. It:

  1. Downloads and parses the raw email using mailparser (RFC 5322)
  2. Extracts the inbox name from the recipient address (the part before @)
  3. Saves each attachment to S3 under attachments/{messageId}/{filename}
  4. Writes a metadata record to DynamoDB with: sender, recipient, subject, body (plain + HTML), attachment info, and a 7-day TTL

Data storage

DynamoDB: EmailsTable

KeyFormatPurpose
PK (Partition)inboxGroups emails by inbox name
SK (Sort){ISO timestamp}#{messageId}Enables chronological ordering
GSI: MessageIdIndexmessageIdEnables direct lookup by message ID

Records include a ttl attribute set to 7 days from ingestion for automatic cleanup.

DynamoDB: ApiKeysTable

KeyFormatPurpose
keyHash (Partition)SHA-256 hashStores hashed API keys for auth

Keys are permanent until manually revoked.

S3: EmailBucket

PrefixContentRetention
incoming/Raw .eml files8 days (lifecycle rule)
attachments/Parsed attachment files8 days (lifecycle rule)

The 1-day buffer between DynamoDB TTL (7 days) and S3 lifecycle (8 days) ensures S3 objects outlive their index entries. See Data Retention for customization details.

API layer (Hono on Lambda)

The REST API runs on a single Lambda function using Hono as the HTTP framework. It provides:

  • Email listing with pagination, filtering, and long-polling
  • Single email retrieval by message ID
  • Raw email download via pre-signed S3 URLs (15-minute expiry)
  • Attachment download via pre-signed S3 URLs
  • Email deletion (single and bulk)
  • Bearer token authentication with SHA-256 hashed keys stored in DynamoDB

An optional CloudFront distribution + custom domain can be configured via the API_DOMAIN environment variable.

Infrastructure as code

All resources are defined using SST v4 (built on Pulumi). The infrastructure code lives in packages/infra/src/:

  • index.ts: S3 bucket, DynamoDB tables, Lambda functions, API router
  • ses-inbound.ts: SES receipt rules and DNS records

SST stages (dev, prod) provide full resource isolation between environments.

Search Documentation

Search through the docs