Setting up the Slack adapter
Mail gets delivered as a Slack DM to whoever owns the address. deploy-cloudflare.md only gets mail as far as received and stored, so finish that first.
What you need
- A cloned repo with
npm installrun in it. - A deployed Worker (the same one from
deploy-cloudflare.md, or a fresh one if this is the only adapter you're running). - A Slack workspace you can create an app in.
Every command below assumes your terminal's current directory is that cloned repo folder. wrangler reads wrangler.jsonc from wherever you run it, so a command run from anywhere else (your home folder, a different project) fails with Required Worker name missing rather than doing what it says. That specific error means "wrong folder," not a real problem with your setup.
1. Create the app from a manifest
Go to api.slack.com/apps → Create New App → From an app manifest → pick your workspace.

Paste this in (YAML tab), replacing YOUR-WORKER-URL (six occurrences) with your actual Worker URL first: one find-and-replace, not six manual edits:
display_information:
name: Cindermail
description: Disposable email delivered as a Slack DM
background_color: "#f38020"
oauth_config:
scopes:
bot:
- commands
- chat:write
features:
bot_user:
display_name: Cindermail
always_online: true
slash_commands:
- command: /cm-new
url: https://YOUR-WORKER-URL/slack/commands
description: Mint a new disposable address
usage_hint: "[expiry] [note]"
should_escape: false
- command: /cm-list
url: https://YOUR-WORKER-URL/slack/commands
description: List your addresses
should_escape: false
- command: /cm-extend
url: https://YOUR-WORKER-URL/slack/commands
description: Change when an address expires
usage_hint: "<address> [expiry]"
should_escape: false
- command: /cm-note
url: https://YOUR-WORKER-URL/slack/commands
description: Label an address
usage_hint: "<address> [note]"
should_escape: false
- command: /cm-torch
url: https://YOUR-WORKER-URL/slack/commands
description: Kill an address
usage_hint: "<address>"
should_escape: false
- command: /cm-remind
url: https://YOUR-WORKER-URL/slack/commands
description: Toggle expiry reminder DMs
usage_hint: "[on|off]"
should_escape: false
settings:
org_deploy_enabled: false
socket_mode_enabled: false
token_rotation_enabled: false
Every command is prefixed cm- rather than the bare name: Slack rejects overly generic single-word command names outright, and /remind specifically collides with a real Slack built-in. The prefix is stripped back off before dispatch, so the six commands themselves work identically to Discord and Telegram.
2. Enable DM slash commands
Left sidebar → App Home → Messages Tab → turn it on, then check "Allow users to send Slash commands and messages from the messages tab."

Easy to miss, and the only step that isn't obviously part of "installing the app": without it, the app's own DM won't accept any input at all, commands included, even though the six slash commands work fine from a regular channel either way.
3. Install to your workspace
Left sidebar → Install App → Install to Workspace. The consent screen shows exactly the two scopes from the manifest, nothing more:

Click Allow.
4. Copy the Bot Token
Still on Install App (or OAuth & Permissions) → OAuth Tokens → copy the Bot User OAuth Token (xoxb-...).

5. Copy the Signing Secret
Left sidebar → Basic Information → App Credentials → Signing Secret → Show.

App ID and Client ID aren't secret, they're public identifiers. Client Secret and Signing Secret are, and Slack masks both by default. The Verification Token below them is deprecated but still live and shown in plain text, worth blocking out of any screenshot you take of this page, same as the two secrets above it.
6. Set both as secrets on the Worker
npx wrangler secret put SLACK_BOT_TOKEN
npx wrangler secret put SLACK_SIGNING_SECRET
Or skip the terminal entirely: Workers & Pages → your Worker → Settings → Variables and Secrets → Add, Type: Secret, one entry per name above, paste the value, Deploy. Both paths write to the same encrypted store. Just make sure the Type dropdown says Secret, not Text. A Text variable is plaintext and gets silently wiped the next time this Worker is deployed, since only ADAPTERS is declared in wrangler.jsonc and a redeploy treats that file as the source of truth for anything not a proper Secret.
Add "slack" to ADAPTERS in wrangler.jsonc's vars (comma-separated with whatever else is there: "discord,telegram,slack"), then redeploy:
npx wrangler deploy
Your Request URL is /slack/commands on that same Worker, the same one for all six commands, since Slack sends the command name in the payload rather than needing a separate endpoint per command.
7. Try it
Open the app's own DM and run a command:

A reply comes back visible only to you, regardless of whether you ran it in the DM or a channel:

Send that address a test email and it arrives the same way Discord and Telegram deliver it: From/To/Subject header, then the body:

Commands
Every reply uses Slack's ephemeral response type: visible only to whoever ran the command, in a DM or any channel alike, so there's no restriction on where you run one from.
| Command | What it does | Rate limit |
|---|---|---|
/cm-new [expiry] [note] |
Creates an address. Permanent unless expiry is given. |
1 per 30s |
/cm-list |
Your addresses, notes, and expiry. | 15 per 60s |
/cm-extend <address> [expiry] |
Changes when an address expires. | 15 per 60s |
/cm-note <address> [note] |
Labels an address. Blank clears it. | 15 per 60s |
/cm-torch <address> |
Revokes an address. | 15 per 60s |
/cm-remind [on|off] |
Expiry reminder DMs. Blank shows the current setting. | 15 per 60s |
Plain text after the command, no structured options: for /cm-new and /cm-note, a leading number is read as expiry; everything else is the note. For /cm-extend, the address comes first and an optional trailing number is the new expiry:
/cm-new permanent, no note
/cm-new 7 expires in 7 days
/cm-new 7 netflix signup expires in 7 days, noted
/cm-new netflix signup permanent (no leading number), noted
/cm-extend x@you.com 10 days from now
/cm-extend x@you.com 5 5 days from now
/cm-extend x@you.com 0 permanent
expiry is in days on both /cm-new and /cm-extend, 0 meaning permanent. One asymmetry: bare /cm-new is permanent, bare /cm-extend uses the 10 day default, since /cm-extend should do what its name says. /cm-extend also sets expiry relative to now rather than adding to what's left: an address with 8 days left extended by 5 has 5 days, not 13.
Permanent addresses still count against your active-address limit, and show as permanent in /cm-list rather than a countdown. Cleanup skips them, so /cm-torch is what ends one.
Notes
A random local part tells you nothing about what you used it for. A note is an optional label, up to 80 characters, shown in /cm-list, visible only to the address's owner:
/cm-new 0 netflix trial
/cm-note x7k2p9qzrm@you.com bank alerts
/cm-note x7k2p9qzrm@you.com clears it
Expiry reminders
Off until asked for:
/cm-remind on DM about a day before an address expires
/cm-remind off stop
/cm-remind current setting
One DM covering everything of yours expiring soon, with notes, so you can /cm-extend what you still need. It rides the daily cleanup cron, so it lands 24 to 48 hours ahead rather than exactly a day; an address living under about two days never gets one, since no run sees it with a day still left: /cm-new 1 won't warn. /cm-extend re-arms the reminder against the new expiry.
Defaults for all of the above are configurable, see configuration.md.