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
Edit this page on GitHub