Cloudflare Email Worker that, for each incoming email, forwards it to another mail server and posts a Discord notification chosen by the recipient's username.
Mail to {username}@your-domain (e.g. user1@gophercon.jp) is forwarded to
{username}@FORWARD_EMAIL_DOMAIN and posted as a Discord embed to the webhook
mapped to {username}. Usernames with no mapping are forwarded only (no error).
Domain-agnostic; runtime dependencies kept to
postal-mime (MIME parsing) and
html-to-text (HTML body →
plain text).
email() (src/index.ts) starts message.forward() without
awaiting (concurrent with the notification), looks up the webhook for the
username — parsing DISCORD_WEBHOOK_MAP once per instance — posts the email as a
Discord embed with the body trimmed to Discord's limits, then awaits the forward.
Notification errors are logged and swallowed so forwarding is never affected.
Two values, read as Workers secrets in production and dev vars locally:
| Name | Description |
|---|---|
DISCORD_WEBHOOK_MAP |
JSON object mapping username → Discord webhook URL. |
FORWARD_EMAIL_DOMAIN |
Domain incoming mail is forwarded to. |
Both live in a git-ignored .env.yaml (see
.env.yaml.example):
cp .env.yaml.example .env.yaml # then edit
bun run gen:dev-vars # -> .dev.vars for local dev.env.yaml is for local dev only; in production the same two values are Workers
secrets set in the Cloudflare dashboard (see Deployment).
Toolchain (bun, node) is pinned in mise.toml; installs enforce a
3-day release cooldown (bunfig.toml) for supply-chain safety.
mise install # pinned bun/node (optional)
bun install
bun run gen # gen:cf-types + gen:dev-vars
bun run dev # vite dev, loads .dev.vars
bun run typecheck # tsc --noEmit
bun run test # vitest (Workers runtime)
bun run build # vite buildCloudflare deploys on merge to main (see Deployment); there is
no local deploy script.
Pull-based, via
Cloudflare Workers Builds:
Cloudflare builds this repository itself, on push to main and on pull requests.
Nothing deploys from GitHub Actions and the repository holds no Cloudflare
credentials.
Secrets and Email Routing are set on the Worker in the Cloudflare dashboard, so changing a webhook or the forward domain needs no commit.