Architecture
Mailgrid runs entirely on Cloudflare's edge with AWS SES as the outbound gateway. Here's the topology.
Overview
Cloudflare Edge (Worker + bindings)
┌─────────────────────────────────────┐
HTTP / MCP / ───►│ Hono router │── SigV4 ──► AWS SES (ap-south-1)
SMTP / Webhook │ ├ middleware: auth, rate, valid │
│ └ 22 route modules │ ▲
│ │ ▲ │ │
│ ▼ │ │ │
│ src/domain/* (15 modules) │ │
│ │ │ │
│ ┌────┴────┬──────┬─────┬──────┐ │ │
│ │ D1 │ R2 │ KV │ AI │ │ │
│ │ SQLite │ EMLs │keys │Llama │ │ │
│ └─────────┴──────┴─────┴──────┘ │ │
│ ┌─────────┐ ┌──────────────┐ │ │
│ │ Queues │ │ Durable Obj │ │ │
│ │ webhook │ │ RateLimiter │ │ │
│ │ + DLQ │ │ (per-tenant) │ │ │
│ └─────────┘ └──────────────┘ │ │
└─────────────────────────────────────┘ │
▲ │
└── SNS subscription ── topic ◄──────┘
(bounce/complaint/open/click)Storage
| Service | What | Why |
|---|---|---|
| D1 | Tenants, keys, events, suppressions, templates, contacts, streams, files, IPs, users, audit logs | SQLite, relational, pay-per-request |
| R2 | Raw EML archive + File Cache binaries | S3-compatible, no egress fees |
| KV | API-key prefix → tenant cache | Sub-ms reads on every auth check |
| Durable Objects | Per-tenant rate-limit bucket | Strong consistency for token-bucket math |
| Queues | SES webhook → event processing | Async + DLQ + retry |
| Workers AI | Llama 3.1 8B (templates, summarization, Q&A) | Lives on the same edge |
A request flow
For a single POST /api/emails:
- Cloudflare's edge accepts the TLS connection.
- Worker entry (
src/index.ts) routes via Hono. - Middleware: request-id → auth (KV lookup → D1 verify) → rate limit (DO) → Zod validate.
- Route handler in
src/http/routes/emails.tscallsdomain/email.ts:send(). - Domain checks suppressions, claims idempotency, renders template, resolves stream + attachments, signs SES request via SigV4.
- SES accepts the message and returns a messageId.
waitUntil()schedules R2 archive write + event row, response returns.- SES later publishes
delivered/opened/bouncedto SNS, which POSTs to/api/webhooks/ses, which enqueues into Cloudflare Queues, which consumes and writes more event rows.
No AWS SDK
The Workers bundle limit is 10 MB compressed. The AWS JavaScript SDK is ~2 MB just for SES. So Mailgrid does AWS API calls via raw fetch() + a hand-rolled SigV4 signer in src/lib/sigv4.ts (~150 lines). The total Worker bundle is currently 419 KB.
Why Cloudflare over AWS Lambda
| Dimension | Workers | Lambda |
|---|---|---|
| Cold start | ~5 ms | 100–1000 ms |
| Global edge | Default, 300+ POPs | Lambda@Edge / CloudFront extras |
| Stateful primitives | D1, R2, KV, DO, Queues bundled | DynamoDB, S3, ElastiCache — all separate |
| Pricing (100k req/day) | ~$5/mo | ~$20/mo + egress |
| Outbound HTTPS | Free | Paid egress |
Why AWS SES over Resend/Postmark
- $0.10 / 1k emails. Resend is $1, Postmark $1.25.
- Dedicated IPs $24.95/mo flat. Competitors $50–$300.
- SNS feedback loops — standardized, mature.
- No vendor lock-in — you control the IP, warmup, bounces.
- What we trade: a dashboard. We build that ourselves on D1 + tracking + analytics.