Deployment
Environment setup, build pipeline, and CDK deploy workflow.
Prerequisites
- Node.js 22+
- pnpm
- AWS CLI configured with a profile that has permissions for CDK, Lambda, DynamoDB, S3, SES, EventBridge, Step Functions, SNS, SSM
- AWS CDK CLI (
npm install -g aws-cdk) - A verified SES domain or email address in the target region
Environment setup
Option 1: Interactive setup (recommended)
Use the Claude Code skill:
/setup-envThis walks you through:
- Choosing an AWS profile
- Discovering verified SES identities
- Choosing sender details (from email, display name, reply-to)
- Choosing or creating an SES configuration set
- Naming resources (tables, bucket, bus, etc.)
- Generating the HMAC unsubscribe secret
- Writing the
.envfile
Option 2: Manual setup
cp .env.example .envEdit .env with your values:
ACCOUNT=123456789012
REGION=us-east-1
STACK_NAME=Mailshot
TABLE_NAME=Mailshot
EVENTS_TABLE_NAME=Mailshot-events
TEMPLATE_BUCKET_NAME=mailshot-templates-123456789012
EVENT_BUS_NAME=mailshot-bus
SES_CONFIG_SET_NAME=mailshot-config
SNS_TOPIC_NAME=mailshot-ses-notifications
DEFAULT_FROM_EMAIL=hello@yourdomain.com
DEFAULT_FROM_NAME=YourCompany
REPLY_TO_EMAIL=support@yourdomain.com
UNSUBSCRIBE_SECRET=<generate with: openssl rand -hex 32>
SSM_PREFIX=/mailshotBuild pipeline
Build order matters — shared must build first:
pnpm install
pnpm --filter @mailshot/shared build # Types and constants
pnpm --filter @mailshot/<sequenceId> build # Templates (per sequence)
pnpm --filter @mailshot/cdk build # CDK infrastructureOr build everything at once:
pnpm -r buildDeploy workflow
Option 1: Full deploy skill (recommended)
/deployThis runs the complete pipeline:
- Validates all sequence configs
- Builds shared package
- Builds all sequences (renders templates to HTML)
- Generates Mermaid diagrams
- Verifies all template HTML files exist
- Builds and synthesizes CDK
- Shows build artifacts for review
- Deploys to AWS (after confirmation)
- Reports stack outputs
Option 2: Manual deploy
# Validate
pnpm --filter @mailshot/cdk synth
# Deploy
pnpm --filter @mailshot/cdk deployWhat CDK deploys
A single cdk deploy creates/updates everything:
- DynamoDB — Main table + Events table (with TemplateIndex GSI)
- S3 — Template bucket + BucketDeployment (uploads
build/<sequenceId>/templates/per sequence) - Lambda — 5 functions (SendEmail, CheckCondition, Unsubscribe, BounceHandler, EngagementHandler)
- Step Functions — One state machine per sequence (auto-discovered from
sequences/*/) - EventBridge — Custom bus + routing rules for triggers and fire-and-forget events
- SES — Configuration set + SNS event destinations
- SNS — Topics for bounce/complaint and engagement notifications
- SSM — All config values as parameters under the configured prefix
Build artifacts
After building, all artifacts are in build/:
build/
<sequenceId>/
templates/ Rendered HTML files (deployed to S3)
welcome.html
day-1-checkin.html
diagrams/ Mermaid flowcharts
diagram.mmd
diagram.png
cdk/
cdk.out/ Synthesized CloudFormation templatesCDK constructs
The stack is composed of 6 constructs, instantiated in order:
- StorageConstruct — Tables + bucket
- SesConfigConstruct — SES config set + SNS topics
- LambdasConstruct — Lambda functions with IAM permissions
- SsmConstruct — SSM parameters
- StateMachinesConstruct — Step Functions state machines (auto-discovers sequences)
- EventBusConstruct — EventBridge bus + rules (auto-discovers sequences)
SSM Parameter Store
All configuration is stored in SSM, not as Lambda environment variables. Lambdas read from SSM at runtime with 5-minute caching. This means you can update config values in SSM without redeploying Lambdas (changes take effect within 5 minutes).
Parameters under the configured prefix (default /mailshot/):
| Parameter | Value |
|---|---|
table-name | Main DynamoDB table name |
events-table-name | Events table name |
template-bucket | S3 template bucket name |
default-from-email | SES sender address |
default-from-name | SES sender display name |
reply-to-email | Reply-to address (optional) |
ses-config-set | SES configuration set name |
unsubscribe-base-url | UnsubscribeFn Function URL |
unsubscribe-secret | HMAC signing secret |