Architecture
System overview, AWS services, data flow, caching, and cost model.
Design principles
- Product-agnostic - The framework defines contracts (event shapes, subscriber schema, template format). You provide your own sequences, templates, and events.
- Zero idle cost - No servers running between emails. You only pay when a step executes.
- AI-native operations - No admin UI. All management happens through Claude Code via MCP tools and skills.
- Single-table DynamoDB - All subscriber state in one table, engagement events in a second. No relational database.
AWS services
| Service | Role |
|---|---|
| EventBridge | Event ingestion and routing. Custom bus receives events from your app. Rules route to Step Functions (sequences) or Lambda (fire-and-forget). |
| Step Functions | Sequence orchestration. One state machine per sequence. Handles branching, delays, and step ordering. |
| Lambda | Eight functions: SendEmail, CheckCondition, Unsubscribe, BounceHandler, EngagementHandler, Subscribe, Broadcast, ReplyHandler (conditional). |
| SQS | Broadcast send queue with dead-letter queue. Fans out broadcast emails to SendEmailFn with backpressure and retries. |
| DynamoDB | Two tables. Main table: subscriber profiles, active executions, send logs, suppression records. Events table: engagement tracking (opens, clicks, deliveries, replies). |
| S3 | HTML email templates. Deployed via CDK BucketDeployment. Cached in Lambda memory for 10 minutes. |
| SES | Email delivery with open/click tracking via configuration set. |
| SNS | Routes SES notifications (bounces, complaints, engagement events) to Lambda handlers. |
Data flow
┌──────────────────────────────────┐
│ Your App Backend │
│ publishes events to EventBridge │
└───────────────┬──────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ EventBridge (Custom Bus) │
│ │
│ Sequence rules ─────→ Step Functions │
│ Event rules ────────→ SendEmailFn │
│ Subscribe rule ─────→ SubscribeFn │
│ Broadcast rule ─────→ BroadcastFn ──→ SQS Queue │
└───────┬──────────────────┬──────────────┬────────┘
│ │ │
▼ ▼ ▼
Step Functions SendEmailFn BroadcastFn
│ │ │
├─ register ─────────┤ Scans subscribers
├─ send (per step) ──┤ by tags/attributes
├─ wait │ │
├─ choice (native) │ ▼
├─ condition ───→ CheckConditionFn SQS Queue
├─ complete ─────────┤ │
│ │ ▼
└────────────────────┤ SendEmailFn
│ (per subscriber)
┌───────┴───────┐
▼ ▼
DynamoDB S3 Templates
(state) (HTML + Liquid)
│
▼
SES ──→ Recipient
│
├─ Bounce/Complaint ──→ SNS ──→ BounceHandlerFn
│ └─ suppress subscriber
│ └─ stop executions
│
├─ Engagement ────────→ SNS ──→ EngagementHandlerFn
│ (open/click/delivery) └─ write to Events table
│
└─ Unsubscribe link ──→ UnsubscribeFn (Function URL)
└─ mark unsubscribed
└─ stop executions
Inbound Reply ──→ SES Receipt Rule ──→ SNS ──→ ReplyHandlerFn
(if REPLY_TO_EMAIL set) └─ write reply to Events table
└─ publish email.replied to EventBridge
└─ exitOn rules stop sequencesCost model
At 1,000 subscribers, total cost is under $5/month:
- Step Functions: $0.025 per 1,000 state transitions. A 5-email sequence is
12 transitions per run ($0.30 per 1,000 executions). - Lambda: Negligible at low volume. 128–256MB functions running for less than 1 second per invocation.
- DynamoDB: On-demand pricing. A few cents per month at 1,000 subscribers.
- SES: $0.10 per 1,000 emails.
- S3: Negligible for storing HTML templates. Compare this to $29–$299/month for email SaaS tools that do the same thing.