Deploy your own Cutout
Cutout is self-host first. Click the Deploy to Cloudflare button (or fork manually and connect Cloudflare Builds), wire up bindings and secrets in the Cloudflare dashboard, and pushes to main deploy to your own Worker.
01 Deploy to Cloudflare
The fastest path is the one-click Deploy button on the repo README. It forks ananthb/cutout into your account, creates a Worker, auto-provisions the bindings declared in wrangler.toml (D1, KV, R2, Queues, Email send-binding, Analytics Engine), and connects the fork to Cloudflare Builds so future pushes deploy automatically.
If you'd rather wire it up by hand: fork ananthb/cutout, then in the Cloudflare dashboard create a Worker named cutout and connect the fork under Workers › cutout › Settings › Builds. Build configuration:
- Build command: leave default (CF Builds detects
package.jsonand runsnpm install). - Deploy command:
npm run deploy— the script inpackage.jsonrunscargo install -q worker-build && wrangler d1 migrations apply cutout-db --remote && wrangler deploy.
02 Bind resources in the dashboard
If you used the Deploy button, most bindings are already created. Otherwise create them and attach by name to the Worker under Workers › cutout › Settings › Bindings. The names must match the binding values in wrangler.toml:
- D1:
DB→ databasecutout-db(rule storage, reverse-alias mappings, pending dispatches, bot reply contexts). - KV:
KV→ one namespace, any name. - R2:
EMAILS→ bucketcutout-emails(raw bytes for queued retries and Store-action persistence). - Queues: producer
RETRIES→cutout-retries; consumerscutout-retries(with DLQcutout-retries-dlq) andcutout-retries-dlq. - Email: send-binding
EMAIL. - Analytics Engine: dataset
EVENTS→cutout_events(optional — if absent, the dashboard hides aggregated stats).
03 Set runtime variables and secrets
Under Workers › cutout › Settings › Variables and Secrets, add:
- Variables (plain text):
CF_ACCESS_TEAM(your Access team domain) andCF_ACCESS_AUD(AUD tag of the Access app guarding/manage; set up in step 8). Optional:CF_ACCOUNT_ID(enables Analytics Engine SQL API and Browser Rendering REST API features) andPUBLIC_BASE_URL(origin used in viewer links posted to Telegram/Discord; empty suppresses the link). - Secrets:
CF_API_TOKEN— optional, with Account Analytics: Read for stats panels and/or Browser Rendering: Edit for in-chat screenshots; both off if absent.VIEWER_HMAC_KEY— required only if at least one chat destination useslink_auth = token; generate withopenssl rand -hex 32.
The full reference is at the bottom of wrangler.toml.
04 Push to main
Cloudflare Builds runs npm run deploy: it installs worker-build, applies any pending D1 migrations, then runs wrangler deploy. After it succeeds, your Worker is live at cutout.<your-workers-subdomain>.workers.dev (or a custom route you've added under Settings › Domains & Routes).
05 Enable Email Routing on each domain
For every domain you want Cutout to handle, in the Cloudflare dashboard:
- Open Email › Email Routing and click Enable.
- Under Routes, edit the catch-all address, set the action to Send to a Worker, and pick the
cutoutworker.
06 Onboard each domain to Email Service
This is what lets Cutout send outbound mail (replies, proxy-mode forwards, and fan-out beyond the first destination).
- Dashboard › Email › Email Sending › Onboard Domain.
- Accept the DNS records (MX / SPF / DKIM / DMARC) on the
cf-bounce.<yourdomain>subdomain.
07 Verify destination addresses
Cloudflare requires every email destination to be a verified address.
- Dashboard › Email › Email Routing › Destination addresses.
- Click Add destination address and enter the address (e.g. your real Gmail).
- Click the confirmation link sent to that address.
08 Protect /manage with Cloudflare Access
The management UI must not be public. Create an Access application whose policy covers cutout.<yourdomain>/manage/* (or whichever route you mapped to the worker). Copy the application's AUD tag and your team domain into the CF_ACCESS_AUD and CF_ACCESS_TEAM Worker variables (step 3) — Cloudflare Builds preserves them across redeploys.
09 Optional: enable Telegram and Discord destinations
Skip this step if you only want to forward to email. The /manage editor only offers a chat channel as a destination kind once the relevant bot secrets are set on the Worker. Add them under Workers › cutout › Settings › Variables and Secrets › Secrets:
Telegram
- Talk to @BotFather on Telegram and create a bot. Copy the HTTP API token.
- Add it as the secret
TELEGRAM_BOT_TOKENin the dashboard. - Optional but recommended. Add the secret
TELEGRAM_WEBHOOK_SECRET(any random string) so only Telegram can post to your worker. - Register the webhook with Telegram (replace
<TOKEN>and<HOST>; thesecret_tokenparam is only needed if you setTELEGRAM_WEBHOOK_SECRET):curl -X POST "https://api.telegram.org/bot<TOKEN>/setWebhook" \ -d "url=https://<HOST>/telegram/webhook" \ -d "secret_token=<WEBHOOK_SECRET>" - Add the bot to each chat or channel you want messages forwarded to. To find a chat's
chat_id, post anything in the chat then visithttps://api.telegram.org/bot<TOKEN>/getUpdates: the integer (negative for groups, positive for DMs) is what you put in a rule destination astelegram:<chat_id>.
Discord
- Open the Discord Developer Portal and create an application. Under Bot, reset and copy the token. Under General Information, copy the Application ID and Public Key.
- Add all three as Worker secrets in the dashboard:
DISCORD_BOT_TOKEN,DISCORD_APP_ID,DISCORD_PUBLIC_KEY. - Back in the developer portal, set the Interactions Endpoint URL (under General Information) to
https://<your-cutout-host>/discord/interactions. Discord pings this URL with a verification request: with the public key in place, the worker responds correctly and the URL saves. - Invite the bot to your server via OAuth2 (scope
bot, permission Send Messages for the target channels). - For each Discord channel you want forwarded messages in, copy its Channel ID (Discord Settings › Advanced › Developer Mode, then right-click channel › Copy Channel ID) and use it in a rule destination as
discord:<channel_id>.
Telegram replies (Telegram's native reply) and Discord replies (the Reply button on each forwarded message) route back through Cutout to the original email sender, so the chat client becomes a fully functional mailbox for that thread.
10 Add routing rules
Visit https://<your-cutout-host>/manage. Add Forward rules using glob patterns on the local part and domain. For email destinations, use the Proxy via rewrite mode toggle to ensure Reply-To works when replying via your custom domain (see How it works for the tradeoffs).
Updating
Pull from upstream into your fork's main branch (or merge a PR): Cloudflare Builds re-runs on every push to main.