Architecture
Trace how a message moves through the Worker, how routing decisions are made, and how the CLI turns config.json into a running deployment.
Overview
Mailmask has two parts that run on different runtimes:
- Cloudflare Worker (
src/worker/) — Receives inbound mail from Cloudflare Email Routing and forwards, rejects, or drops it. - Node.js CLI (
src/cli/) — Editsconfig.json, generateswrangler.toml, and deploys.
The CLI writes the config, and deploy injects it into the Worker as three JSON environment variables: SETTINGS, DESTINATIONS, and ALIASES. There is no database or storage binding.
Mail Flow
- Receive — The catch-all hands every address without its own rule to the Worker.
- Validate —
src/worker/index.tsparses the three variables and checks them with the same Zod schema the CLI uses. Invalid or unparsable config rejects the message instead of silently accepting it. - Resolve —
resolveRouteinsrc/worker/router.tsreturns a decision: forward, reject, or drop, with a reason. - Act —
handleEmailinsrc/worker/handlers.tsforwards with alias headers, or refuses and sends a best-effort notification for known aliases.
Routing Decision
resolveRoute is a pure function with no I/O, so every rule is unit-tested directly. It checks, in order:
- Domain — A recipient outside the base domain is rejected.
- Subaddress — With subaddressing on, a
+tagsuffix is stripped. - Alias lookup — An unknown address follows the unknown alias action.
- Enabled — A disabled alias is refused.
- Expiry — An alias whose
expires_athas passed is refused. - Block list — A sender matching
blocked_sendersis refused. - Allow list — When
allowed_sendersis set, a sender matching none of it is refused. - Destination — A missing destination is rejected as misconfigured; otherwise the message is forwarded.
A refusal uses drop when the unknown alias action is drop, and reject otherwise. Refusals never fall through to the default destination.
Forwarding
The Worker calls message.forward() with the destination address. When add_alias_headers is on, it adds:
X-Mailmask-Alias— The original recipient, including any+tag.X-Mailmask-Label— The alias label, when one is set.
The message body is never read, stored, or logged.
Deploy Pipeline
src/cli/commands/deploy.ts drives each step through the Cloudflare v4 API, and src/cli/commands/generate.ts writes wrangler.toml with workers_dev and preview_urls turned off. Deploy never enables Email Routing or edits DNS.
Error Strategy
| Layer | Strategy |
|---|---|
| Worker entry | Invalid or unparsable config rejects the message with a temporary error. |
| Router | Pure function that always returns a decision and never throws. |
| Notifications | Best effort. Network failures are swallowed. |
| CLI | Errors are caught per command and set a non-zero exit code. |
| Config validation | Zod schemas plus checks for missing destinations, duplicates, and the default destination. |
Source Structure
packages/mailmask/src/
├── cli/
│ ├── commands/ — Alias, destination, settings, validate, generate, and deploy
│ ├── menu/ — Interactive configuration interface
│ └── index.ts — Commander entry point (mailmask / mm)
├── lib/ — Constants, regexes, package paths, and Zod schemas
├── worker/
│ ├── handlers.ts — Forward, reject, or drop one message
│ ├── index.ts — Worker entry point (email and fetch)
│ ├── notify.ts — Best-effort refusal notifications
│ └── router.ts — Pure alias resolution and sender matching
├── tests/ — Unit tests mirroring the source
└── types/ — Type declarations mirroring the source