Deploy to your Cloudflare account
Two ways to put Tinbox on your own Cloudflare account — the one-click button or the local tinbox CLI — plus the dashboard and command steps for everything the deploy can't finish for you.
Tinbox runs entirely on your Cloudflare account. Nothing transits a Tinbox relay — your mail, keys, and subscriber data never leave infrastructure you own.
Every path below deploys the same one bundled worker (createWorker()),
which handles fetch (the API + inbox UI), email (inbound via Email Routing),
queue, and scheduled. It needs a small, fixed set of resources on your
account — a D1 database (mail + audiences), an R2 bucket (raw .eml +
attachments), a KV namespace (routing cache), and two Queues (newsletter
fan-out + inbound projection, each with a dead-letter queue). Resources are named
tinbox-<org>-*, so several tenants can share one Cloudflare account.
Pick a path
| Path | When to use | Auth |
|---|---|---|
| One-click button | No terminal; fastest first deploy | Cloudflare dashboard sign-in |
tinbox CLI | One command, full control, scriptable | wrangler login or an API token |
Using a coding agent?
Give it the canonical agent runbook,
your domain, and permission to stop for HUMAN STEPs. It runs the same preflight
and CLI path below, then hands you to the deployed web app to claim the owner
account. The runbook is the source of truth for agent-assisted setup.
What each path automates vs. leaves to you
| Step | One-click button | tinbox CLI |
|---|---|---|
| Cloudflare auth | ✓ dashboard sign-in | wrangler login / token |
| Provision D1 / R2 / KV / Queues | ✓ auto-provisioned | ✓ tinbox setup |
| Migrations (baseline schema) | ✓ via deploy script | ✓ tinbox setup |
Internal secrets (BETTER_AUTH_SECRET, ASSET_SIGNING_KEY) | prompted in deploy form | ✓ auto-generated |
| External keys (AI, GitHub OAuth) | prompted in deploy form | prompted |
| Deploy the worker | ✓ | ✓ --deploy |
| Custom domain | manual (deploys to workers.dev) | route in config |
| Inbound Email Routing | finish in-app | ✓ with token (else guided in-app) |
| Sending DNS (SPF/DKIM/DMARC) | manual | ✓ attempted with token (else one guided dashboard step) |
| "Continue with Tinbox" SSO | in-app | ✓ domain proof |
The CLI path is the only path that can automate Cloudflare's mail-plane APIs. With
a scoped API token, tinbox setup enables Email Routing, points the catch-all at
your live worker, and onboards Email Sending before you open the app. The
Finish the setup section is the clear fallback for work a
token cannot do or verify.
Path 1 — One-click (Deploy to Cloudflare)
Best when you want an inbox live without touching a terminal. The button clones a small project repo into your GitHub, provisions the resources, and deploys — all from the Cloudflare dashboard.
- From a button-ready repo (see
examples/cloudflare-button), click Deploy to Cloudflare. - Sign in with your Cloudflare account, review the setup page (Worker name, resource names), and fill in any prompted secrets.
- Cloudflare auto-provisions D1/R2/KV/Queues, builds with Workers Builds
(
npm install→npm run deploy), and deploys. Every later push to your new repo redeploys.
The button deploys to a *.workers.dev host and can't touch your DNS, so after
it finishes, attach a custom domain and turn on mail — see
Finish the setup. The in-app onboarding walks you through
the same steps.
Path 2 — The tinbox CLI
Best when you have a terminal and want one idempotent command to do the whole provision. Check the requirements, scaffold, then set up:
# Before creating a project — Node 20+, package manager, zone and plan:
npx tinbox doctor --preflight yourdomain.com
# Authenticate once — wrangler login, or an API token for zero-click mail setup:
wrangler login
npx tinbox init yourdomain.com
cd tinbox-yourdomain-com
tinbox setup --deploy
tinbox doctor # read-only health report: what's set up, what's left
tinbox setup is idempotent — run it again any time; it only does what's still
missing. With a CLOUDFLARE_API_TOKEN it turns on Email Routing, attaches the
catch-all to your worker, and onboards Email Sending. Without one, the running
app shows only the remaining guided Cloudflare step. It prompts for external keys
(ANTHROPIC_API_KEY, GitHub OAuth) it can't generate.
Finish the setup
The CLI automatically completes the mail-plane steps below when
CLOUDFLARE_API_TOKEN has the documented Email Routing and Email Sending scopes.
Use these instructions for the one-click path, a no-token CLI deploy, or an API
failure. Settings → Domains always shows the current live state and the next
remaining action.
Attach a custom domain
Needed for a real address and for SSO. The domain's zone must be on this account.
Workers & Pages → your worker → Settings → Domains & Routes → Add → Custom Domain → tinbox.<your-domain>.
Add the route to wrangler.jsonc, then redeploy:
"routes": [{ "pattern": "tinbox.<your-domain>", "custom_domain": true }]
pnpm wrangler deploy
Turn on inbound mail (Email Routing)
Inbound mail reaches the worker's email() handler via a catch-all rule.
Your domain → Email → Email Routing → Enable, then add a catch-all rule → action Send to a Worker → select your worker.
With a CLOUDFLARE_API_TOKEN set, this enables routing and points the catch-all at the worker for you (wrangler has no Email Routing command, so the CLI uses Cloudflare's API):
pnpm tinbox setup
Addresses still have to exist as inbox addresses in the app for mail to be accepted.
Verify your sending domain
Sending needs SPF, DKIM, and DMARC records on your domain.
Add the SPF, DKIM, and DMARC records at your DNS provider (or your domain's DNS tab if it's on Cloudflare). Until the domain is verified you can only send to verified destinations.
Print the exact records — and publish them automatically when your DNS is on Cloudflare and a token is set:
pnpm tinbox setup
Set secrets
Worker → Settings → Variables and Secrets → add BETTER_AUTH_SECRET (required for sign-in), ANTHROPIC_API_KEY (or OPENAI_API_KEY), ASSET_SIGNING_KEY, and GitHub OAuth keys as needed.
tinbox setup generates and pushes the internal ones; set the rest by hand (value read from stdin):
wrangler secret put BETTER_AUTH_SECRET
wrangler secret put ANTHROPIC_API_KEY
Enable sign-in for the default auth.mode: "tinbox"
This step is required before anyone can sign in when you keep the default auth
mode. It is optional only when you explicitly switch tinbox.config.ts to
auth.mode: "local" and redeploy.
Settings → Sign-in → Enable, add one DNS TXT record to prove the domain, then Re-check — no redeploy.
Run the same domain proof and write the SSO credentials as worker secrets:
pnpm tinbox setup
Verify
tinbox doctor # what's provisioned, migrated, and wired
tinbox whoami # confirms the token reaches your account + inboxes
When those look right, send yourself a test message and watch it land in the inbox UI.