Skip to content
Sendoff
Esc
↑↓navigate↵open⌘Jpreview
On this page

Setup

Everything `bun run setup` creates in your Cloudflare and AWS accounts, step by step.

bun run setup connects Sendoff to your accounts and deploys it. bun create sendoff runs it for you after cloning; run it yourself from your Sendoff folder any time after. Every step checks what already exists first, so it’s safe to run again: after a failure, it skips what’s done and carries on. Your answers and resource IDs are saved in the gitignored apps/app/.sendoff/instance.json, and pre-fill the prompts next time.

Steps

Cloudflare account

Reads your account from wrangler whoami, opening a browser to log in if needed, and asks which account if you have several. It asks you to confirm the account is on Workers Paid, which Wrangler can’t check.

Your list

The name, sender name and address, an optional reply-to, an optional postal address for the footer, and your website. Then the hostname for Sendoff (by default news. plus your sender’s domain) and the SES region. Setup checks whether the hostname’s DNS is on Cloudflare in this account. If it isn’t, Sendoff reaches it through a small Pages project; see Domains outside Cloudflare.

AWS access

If the AWS CLI is logged in, setup uses that login (including short-lived SSO sessions) for its own AWS calls. It creates an IAM user called sendoff with a send-only policy and gives the Worker that user’s key, so the Worker can send email and read its own SES status but can’t change anything in your account. Without the CLI, it writes the IAM policy it needs to .sendoff/iam-policy.json and asks for keys.

Cloudflare resources

A D1 database (sendoff), a KV namespace for OAuth grants (sendoff-oauth), a queue (sendoff-send), a Pages project (sendoff-pages) if your DNS isn’t on Cloudflare, and a Turnstile widget for your hostname. The Cloudflare API token is only asked for here, and only if the Turnstile widget or DNS records still need creating.

SES and SNS

Your domain as an SES identity with Easy DKIM, a custom MAIL FROM domain (bounce. plus your domain), a configuration set (sendoff), an SNS topic (sendoff-events) that only SES can publish to, an event destination for bounces, complaints, rejections and deliveries, and SES’s account-level suppression list.

DNS

Three DKIM CNAME records, an MX and SPF TXT record for the MAIL FROM domain, and a DMARC record if your domain doesn’t have one. Records that clash with existing ones are reported, not changed. Skipped once SES has verified the domain and it has DMARC. If your DNS isn’t on Cloudflare, there’s also a CNAME from your hostname to the Pages project, and setup lists every record for you to add at your DNS provider.

Deploy

Generates .sendoff/wrangler.jsonc, applies the database migrations, and deploys the Worker to your hostname with its secrets. If the Worker already has an MCP token, setup asks whether to keep it or create a new one, and shows any new token straight after the deploy. If your DNS isn’t on Cloudflare, it then deploys the Pages project and adds your hostname to it as a custom domain.

Wire up events

Waits for your hostname to answer (a new custom domain takes a few minutes to get its certificate), subscribes /webhooks/ses to the SNS topic, and waits for the Worker to confirm it. Then it waits for SES to verify your domain.

Production access

Shows a description of what you send for AWS’s review and submits it only if you say yes. See Deliverability.

Finish

Saves your settings and runs the preflight checks.

Domains outside Cloudflare

Sendoff normally runs on your hostname as a Workers Custom Domain, which only works when the domain’s DNS is on Cloudflare. If yours is somewhere else, you don’t have to move it. Setup creates a Cloudflare Pages project called sendoff-pages instead, because a Pages custom domain works with a single CNAME at any DNS provider. The project holds a few lines of code that pass every request, unchanged, to the Sendoff Worker over a service binding, which adds no cost or latency.

What changes for you:

  • One more DNS record: a CNAME from your hostname (like news.example.com) to the project’s *.pages.dev address. Setup lists it with the SES records, which you add yourself, since setup can’t write to DNS outside Cloudflare.
  • Add the domain to the project in the dashboard, if setup can’t. Setup does it with your Wrangler login, and if that fails, it shows where in the Cloudflare dashboard to do it.
  • Everything else is the same. Links in your emails still use your own hostname, and bun run deploy updates the Pages project along with the Worker.

Your hostname has to be a subdomain, like news.example.com, not the domain itself. Most DNS providers don’t allow a CNAME on a bare domain.

Setup remembers the choice. If you move your DNS to Cloudflare later, Sendoff keeps using the Pages project, which carries on working.

What the Worker holds

Secrets, uploaded with the deploy:

Secret Purpose
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY The sendoff IAM user’s send-only key
MCP_TOKEN Authenticates Claude Code and Codex, and signs you in when connecting claude.ai
TOKEN_SECRET Signs the confirmation, unsubscribe and tracked links in emails, plus preview links and sign-in sessions. Generated once and never rotated, because rotating it would break every unsubscribe link already sent.
TURNSTILE_SECRET_KEY Verifies the signup form’s Turnstile challenge

Variables, in the generated config: PUBLIC_URL, SES_REGION, SES_CONFIGURATION_SET, SES_EVENTS_TOPIC_ARN and TURNSTILE_SITE_KEY.

SES regions

Setup offers us-east-1, us-east-2, us-west-2, eu-west-1, eu-central-1 and ap-southeast-2. Production access, quotas and identities are per region, so pick one and stay in it.

Last updated on

Was this page helpful?