Getting started
Manual deploy & local dev
Run Squadmail locally with wrangler and deploy without the button.
Requirements
- Node 22.17+ and pnpm
- A Cloudflare account (Workers paid plan recommended for Queues and Durable Objects)
Local development
git clone https://github.com/squadmail/squadmail && cd squadmail
pnpm install
cp .dev.vars.example .dev.vars # set BETTER_AUTH_SECRET
pnpm db:migrate:local
pnpm dev:worker # vite build --watch + wrangler dev on :8787 Open http://localhost:8787, create the first account, add a domain such as squad.test in the setup wizard and create an inbox. No AWS account is needed for local development — domains simply stay unverified.
Simulate incoming mail
With SNS_SKIP_VERIFY=true in .dev.vars, the SES webhook accepts unsigned notifications on /hooks/ses/dev — the same JSON Amazon SNS posts in production:
CONTENT=$(base64 -w0 test.eml)
curl -X POST http://localhost:8787/hooks/ses/dev -H 'content-type: text/plain' --data @- <<JSON
{"Type":"Notification","TopicArn":"arn:aws:sns:eu-west-1:0:squadmail-inbound","Message":$(jq -Rs . <<<"{"notificationType":"Received","mail":{"source":"me@example.org","destination":["hello@squad.test"]},"receipt":{"recipients":["hello@squad.test"],"action":{"type":"SNS","encoding":"BASE64"}},"content":"$CONTENT"}")}
JSON The response lists the result per recipient (stored, duplicate or rejected with a reason). Never set SNS_SKIP_VERIFY in production.
A minimal test.eml:
From: Me <me@example.org>
To: hello@squad.test
Subject: Your code is 123456
Content-Type: text/plain
Use 123456 to verify your account. The message appears instantly in the dashboard, and wait calls and MCP get_verification resolve.
Clef locally
Workers AI always runs remotely ("remote": true on the AI binding), so labeling works in wrangler dev when you are logged in with wrangler login. Calls are billed to your account.
Manual deploy
npx wrangler login
pnpm run deploy # vite build && wrangler d1 migrations apply DB --remote && wrangler deploy
npx wrangler secret put BETTER_AUTH_SECRET
npx wrangler secret put AWS_ACCESS_KEY_ID
npx wrangler secret put AWS_SECRET_ACCESS_KEY Wrangler provisions resources declared without IDs automatically on first deploy.
Project layout
src/worker.ts Worker entry: SES + Stripe webhooks, OAuth provider, queue(), scheduled(), DOs
src/lib/server/ inbound pipeline, services, labeling, MCP server, auth
src/routes/api/v1/ REST API
src/routes/(app)/ dashboard
website/ squadmail.dev (landing page + these docs)
migrations/ D1 migrations (drizzle-kit) Tests
pnpm test # unit + Workers integration tests (vitest-pool-workers)
pnpm test:e2e # Playwright against wrangler dev
pnpm export:specs # regenerate OpenAPI + MCP reference for the docs