Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
135 commits
Select commit Hold shift + click to select a range
3724728
feat(email): inbound routing, sendEmail, dev inbox and test harness
Brooooooklyn Aug 30, 2026
3345169
feat(email): cf-native outbound and inbound email
Brooooooklyn Aug 30, 2026
f8d8b1c
fix(email): dev inbox capture, harness casing, and reply-sender docs
Brooooooklyn Aug 31, 2026
4ccb016
fix(email): RFC 2047 word splitting, harness reject parity, PARSE_FAILED
Brooooooklyn Aug 31, 2026
6c5da08
fix(email): address preflight, docs result narrowing, and the logs --…
Brooooooklyn Sep 1, 2026
9cfd3ba
fix(email): client stub, header line limit, filename escaping, prompt…
Brooooooklyn Sep 2, 2026
89b9d47
fix(email): treat inbound handlers as a backend, and pin the framewor…
Brooooooklyn Sep 10, 2026
af10ad3
fix(email): make the default reply subject best-effort, and qualify t…
Brooooooklyn Sep 11, 2026
814423a
docs(email): design the lifecycle and UX across the three hosting modes
Brooooooklyn Sep 11, 2026
62d3f38
docs(email): merge the lifecycle and UX design into 0078
Brooooooklyn Sep 11, 2026
3687547
docs(email): bring custom domains on Cloudflare into scope
Brooooooklyn Sep 11, 2026
f1a2ee0
docs(email): custom-domain token in three clicks via the dashboard te…
Brooooooklyn Sep 11, 2026
34efbdb
docs(email): take the custom-domain token from the clipboard; no loca…
Brooooooklyn Sep 11, 2026
e002e67
docs(email): full token grant diagram with branches; token_id + expir…
Brooooooklyn Sep 11, 2026
98c8d42
docs(email): what a Void Cloudflare OAuth client would change, and wh…
Brooooooklyn Sep 11, 2026
6834700
docs(email): Mode B is one keystroke — verified flow from the deep re…
Brooooooklyn Sep 11, 2026
3b90aeb
docs(email): say plainly that email is platform-only today
Brooooooklyn Sep 11, 2026
edd899b
docs(email): carry the Mode B research into progress, operations, les…
Brooooooklyn Sep 11, 2026
0272bb4
chore(deps): bump wrangler to 4.131 and @cloudflare/vite-plugin to 1.…
Brooooooklyn Sep 11, 2026
3877f80
feat(email): add email.from to void.json, keep send_email out of dev
Brooooooklyn Sep 11, 2026
3f31f97
feat(email): sendEmail / addresses / vars in ProvisionedWranglerPatch
Brooooooklyn Sep 11, 2026
276697e
feat(email): own-account runtime — SEND_EMAIL transport, sender defau…
Brooooooklyn Sep 11, 2026
12985d9
feat(email): Mode B module — preflight, derivation, checklist, apply,…
Brooooooklyn Sep 11, 2026
d0f4039
Merge branch 'task/runtime' into feat/email-reland
Brooooooklyn Sep 11, 2026
1667090
Merge branch 'task/config' into feat/email-reland
Brooooooklyn Sep 11, 2026
4289c10
Merge branch 'task/wrangler-patch' into feat/email-reland
Brooooooklyn Sep 11, 2026
b503001
Merge branch 'task/email-cloudflare' into feat/email-reland
Brooooooklyn Sep 11, 2026
60aa301
docs(email): describe email on the developer's own Cloudflare account
Brooooooklyn Sep 11, 2026
4766738
feat(email): void email setup / status for --backend cloudflare
Brooooooklyn Sep 11, 2026
fec3e1f
chore(deps): add ci-info to the void CLI
Brooooooklyn Sep 11, 2026
31b87f4
feat(email): wire the Mode B email step into `void deploy --backend c…
Brooooooklyn Sep 11, 2026
c063413
Merge branch 'task/deploy' into feat/email-reland
Brooooooklyn Sep 11, 2026
7595c56
Merge branch 'task/email-cli' into feat/email-reland
Brooooooklyn Sep 11, 2026
fa9178d
Merge branch 'task/docs' into feat/email-reland
Brooooooklyn Sep 11, 2026
d053c8b
fix(email): review round — 17 findings
Brooooooklyn Sep 11, 2026
f3638c4
Merge origin/main into feat/email-reland (platform monorepo)
Brooooooklyn Sep 11, 2026
a970c38
Merge feat/email-reland (void-platform) under platform/ — subtree
Brooooooklyn Sep 11, 2026
cd35afe
docs(email): monorepo paths in 0078 and the runbook Email section; tw…
Brooooooklyn Sep 11, 2026
42d5e46
feat(email): --platform cloudflare on void email setup/status (--back…
Brooooooklyn Sep 11, 2026
70f9067
fix(ci): import test helpers from vite-plus/test, lint the platform a…
Brooooooklyn Sep 11, 2026
75845aa
fix(ci): typecheck only the platform packages in platform-ci
Brooooooklyn Sep 11, 2026
9d300cd
fix(email): bound a hand-built reply at MAX_MESSAGE_BYTES
Brooooooklyn Sep 11, 2026
a96eb3d
fix(api): keep the email-slug backfill out of the routing-parse try b…
Brooooooklyn Sep 11, 2026
6ee214a
fix(api): resend the platform confirmation link on a repeat destinati…
Brooooooklyn Sep 11, 2026
5e4020e
fix(email): export the error classes, guard type-only imports, bound …
Brooooooklyn Sep 13, 2026
b34e350
docs(email): say plainly that `void dev` binds no send transport
Brooooooklyn Sep 13, 2026
1090b35
fix(deploy): gate --require-email on real readiness, not binding pres…
Brooooooklyn Sep 13, 2026
060e28f
fix(proxy): cap the recipient list before it is normalized
Brooooooklyn Sep 13, 2026
72ebb63
fix(api): say that suspension stops mail in both directions
Brooooooklyn Sep 13, 2026
a94e151
fix(api): per-project consent, mirror repair, and the teardown race
Brooooooklyn Sep 13, 2026
dca91bf
fix(api): retire owner-inherited recipients when the provider email c…
Brooooooklyn Sep 13, 2026
363d859
fix(email): do not spread a hostile header into Math.max
Brooooooklyn Sep 13, 2026
91e29fb
fix(api): roll back the owner destination when teardown wins the race
Brooooooklyn Sep 13, 2026
b4a3280
fix(email): bound the inbound action queue at the proxy, and in the h…
Brooooooklyn Sep 13, 2026
aa09501
fix(email): surface a stream read failure, and an unusable project slug
Brooooooklyn Sep 13, 2026
fc09ca4
fix(email): bound attachments before they are buffered
Brooooooklyn Sep 13, 2026
5bc03c7
Merge origin/main into feat/email-reland
Brooooooklyn Sep 13, 2026
ef0bf52
fix(proxy): check the forward header shape before enumerating it
Brooooooklyn Sep 13, 2026
38cac50
fix(proxy): build the envelope header bag with Object.fromEntries
Brooooooklyn Sep 13, 2026
b3f91fe
fix(email): name the error when reply() gets a native EmailMessage
Brooooooklyn Sep 13, 2026
004defd
fix(email): bound the text and html bodies before they are encoded
Brooooooklyn Sep 13, 2026
10f0939
fix(email): reject an email/ directory on node, bun and deno targets
Brooooooklyn Sep 13, 2026
bf0fb56
fix(proxy): refuse a non-string forward header value
Brooooooklyn Sep 14, 2026
dd00bd4
fix(email): bound recipients before any address is parsed or folded
Brooooooklyn Sep 14, 2026
ed94185
Merge origin/main into feat/email-reland
Brooooooklyn Sep 14, 2026
0228266
fix(cli): fail email setup when the inbound rows are not ready
Brooooooklyn Sep 14, 2026
eee1100
fix(proxy): pin the MIME From address to the envelope sender
Brooooooklyn Sep 14, 2026
df2cf2f
fix(email): count headers in the size projection, before any header i…
Brooooooklyn Sep 14, 2026
9a56594
fix(email): fold a header in one pass that keeps only finished lines
Brooooooklyn Sep 14, 2026
64f2f80
fix(email): project threading and part headers, and refuse an unfolda…
Brooooooklyn Sep 14, 2026
9a517aa
fix(email): scan a threading header without materializing its tokens
Brooooooklyn Sep 14, 2026
ae747ba
Merge origin/main into feat/email-reland
Brooooooklyn Sep 14, 2026
e948170
fix(api): commit the owner email change and the destination demotion …
Brooooooklyn Sep 14, 2026
c65eb3e
fix(api): cap a destination address before normalizing or matching it
Brooooooklyn Sep 14, 2026
7d76da5
fix(email): walk a body's lines without splitting or re-joining them
Brooooooklyn Sep 14, 2026
3c7e1eb
fix(email): charge each attachment part's framing and filename in the…
Brooooooklyn Sep 14, 2026
b68b770
fix(email): allow-list the attachment disposition at runtime
Brooooooklyn Sep 14, 2026
36fd47a
fix(api): re-derive the allowlist mirror after a destination row is d…
Brooooooklyn Sep 14, 2026
1c49ad9
test(email): keep the ~10 MB boundary renders inside CI's clock
Brooooooklyn Sep 14, 2026
e57dc4e
docs(email): pin why a deleted handler's `addresses` entry is a block…
Brooooooklyn Sep 14, 2026
cbe5c57
fix(proxy): bound the envelope sender before anything copies it
Brooooooklyn Sep 14, 2026
d238217
fix(email): remember a refused sending onboarding by the committed bi…
Brooooooklyn Sep 14, 2026
edd2022
fix(cli): reject a missing value for `void email --project` / `--limit`
Brooooooklyn Sep 14, 2026
4035761
fix(plugin): strip only Void's own `SEND_EMAIL` entry under serve
Brooooooklyn Sep 14, 2026
ec394c7
fix(email): say when an add mailed nothing because Cloudflare already…
Brooooooklyn Sep 14, 2026
53ed178
fix(proxy): bound four more tenant-sized inputs before anything alloc…
Brooooooklyn Sep 14, 2026
1e0950a
fix(email): reject a second email/_default.* handler
Brooooooklyn Sep 14, 2026
8f1f50e
fix(email): decode Q-words into a typed array; capture one id per uni…
Brooooooklyn Sep 14, 2026
d1a4911
fix(proxy): bound the platform token, request ids, and every untruste…
Brooooooklyn Sep 14, 2026
38365e8
fix(api,cli): cap project slugs at 56 and tell the CLI which owner ad…
Brooooooklyn Sep 14, 2026
559a967
fix(dev): require the dev-trigger token on every /__void/inbox route
Brooooooklyn Sep 14, 2026
1b14fa7
fix(email): bound the inbound subject the default reply subject is bu…
Brooooooklyn Sep 14, 2026
f1823b3
docs(api): name the delete-vs-register window on a shared destination…
Brooooooklyn Sep 14, 2026
6495539
fix(email): charge custom header names in the MIME projection, refuse…
Brooooooklyn Sep 14, 2026
b9b90cf
fix(api): roll the owner auto-add's Cloudflare tag back when the row …
Brooooooklyn Sep 14, 2026
3e0e919
fix(api): promote a CF-pending destination only off the tag it regist…
Brooooooklyn Sep 14, 2026
6c34c78
fix(api): fail project teardown closed when the email send mirrors ca…
Brooooooklyn Sep 14, 2026
f9670ec
fix(api): inherit the owner's Cloudflare consent only while the addre…
Brooooooklyn Sep 14, 2026
22a0bc3
fix(void): store the dev trigger token owner-only
Brooooooklyn Sep 14, 2026
3f17d5b
fix(void): read a disabled routing rule as absent; validate forward h…
Brooooooklyn Sep 14, 2026
b2d854d
fix(deploy-cloudflare): carry `addresses` through the trigger-config …
Brooooooklyn Sep 14, 2026
0f55976
fix(email): cap the local part at 64 octets; bound a reply's Referenc…
Brooooooklyn Sep 14, 2026
478dae3
fix(email): cap inbound header count; run the test harness on the pro…
Brooooooklyn Sep 14, 2026
4bd437e
fix(email): refuse reserved email slugs on every mail path; validate …
Brooooooklyn Sep 14, 2026
de6791b
fix(email): validate a hand-built reply's addresses at the call
Brooooooklyn Sep 14, 2026
c3a104d
fix(email): cap an address at 254 octets; document that own-account s…
Brooooooklyn Sep 14, 2026
eee1908
docs(email): record the RPC result materialization limit of inbound d…
Brooooooklyn Sep 15, 2026
c1b39c3
fix(email): mirror the router's forward-header bounds and reject-reas…
Brooooooklyn Sep 15, 2026
fee38c6
fix(email): refuse C0 controls and DEL in a bare address at the call
Brooooooklyn Sep 15, 2026
b48be39
fix(email): bound the attachment count before any entry is normalized
Brooooooklyn Sep 15, 2026
318de55
fix(email): refuse ':' in a bare address, and pin the framework addre…
Brooooooklyn Sep 15, 2026
6ab857e
refactor(email): validate addresses with email-validator on both sides
Brooooooklyn Sep 15, 2026
ed1e56d
fix(email): refuse control characters in attachment parameters, drop …
Brooooooklyn Sep 15, 2026
c4b34f0
fix(email): size the dev inbox capture cap for the serialized capture
Brooooooklyn Sep 15, 2026
b11b9dd
fix(email): mirror the router's inbound admission limits in the test …
Brooooooklyn Sep 15, 2026
942689e
fix(email): validate an attachment's media type before rendering it
Brooooooklyn Sep 15, 2026
4ca7b1d
fix(email): validate an attachment's Content-ID before rendering it
Brooooooklyn Sep 15, 2026
760e856
fix(email): refuse a non-string workerName as corrupt routing data
Brooooooklyn Sep 15, 2026
ced5f4d
fix(email): refuse a malformed proxy delivery list instead of droppin…
Brooooooklyn Sep 15, 2026
19e1be4
fix(email): quote a display name that contains a period
Brooooooklyn Sep 15, 2026
b92054c
fix(api): tighten the allowlist mirror inside the owner-email change …
Brooooooklyn Sep 15, 2026
81bcc55
Merge origin/main into feat/email-reland
Brooooooklyn Sep 15, 2026
2b36022
Merge remote-tracking branch 'origin/main' into feat/email-reland
Brooooooklyn Sep 15, 2026
eff0e68
docs(email): record the RPC result materialization decision in 0078
Brooooooklyn Sep 15, 2026
cd17b77
fix(email): refuse an incomplete or malformed mid-batch delivery list
Brooooooklyn Sep 15, 2026
8666af5
fix(email): route login guidance through Void, refuse a mistyped proj…
Brooooooklyn Sep 15, 2026
bb6f925
fix(email): gate the api email routes on the platform capability, say…
Brooooooklyn Sep 15, 2026
6ef17b1
fix(email): say when a revoked recipient is refused, pin parse-then-f…
Brooooooklyn Sep 15, 2026
2be1ec2
fix(email): refuse a malformed suspension marker on send, gate the pr…
Brooooooklyn Sep 15, 2026
bd07cbc
Merge origin/main into feat/email-mono (operator API, CLI docs rewrite)
Brooooooklyn Sep 15, 2026
d60939a
fix(email): classify the platform send binding's error code before it…
Brooooooklyn Sep 15, 2026
990d116
docs(0078): record the Mode B setup-by-direction question as open dec…
Brooooooklyn Sep 15, 2026
5d8ec49
Merge origin/main into feat/email-mono (Worker handoff, self-hosted c…
Brooooooklyn Sep 15, 2026
fb1509b
fix(email): let an exact tag beat a captured tag, refuse a repeated o…
Brooooooklyn Sep 15, 2026
8a243a4
fix(prerender): open the same local D1 the dev server and migration r…
Brooooooklyn Sep 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ export default extendConfig(
collapsed: false,
items: [
{ text: 'AI', link: '/guide/ai' },
{ text: 'Email', link: '/guide/email' },
{ text: 'Sandboxes', link: '/guide/sandboxes' },
{ text: 'Authentication', link: '/guide/auth' },
{ text: 'WebSockets', link: '/guide/websockets' },
Expand Down
609 changes: 609 additions & 0 deletions docs/guide/email.md

Large diffs are not rendered by default.

14 changes: 9 additions & 5 deletions docs/integrations/cloudflare.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,11 +258,12 @@ Direct deployment uses the top-level JSON/JSONC configuration; convert a `wrangl
Void reads your app's configuration and source, then:

1. Checks the app type, selected account, and Cloudflare credentials.
2. Creates missing resources or reuses resources already in your account.
3. Builds the app and checks the generated Worker config, secrets, and migration history.
4. Preserves or creates the auth secret, applies pending migrations, and uploads keys marked `.secret()` as encrypted Worker secrets.
5. Uploads a Worker Version and checks that it is ready.
6. Sends traffic to the new version and applies routes, custom domains, cron schedules, and queue consumers.
2. If the app uses email (`sendEmail()` or `email/` handlers), reads your zone's email state and, before any project code runs, asks once to set it up. See [Your own Cloudflare account](../guide/email.md#your-own-cloudflare-account) in the email guide.
3. Creates missing resources or reuses resources already in your account.
4. Builds the app and checks the generated Worker config, secrets, and migration history.
5. Preserves or creates the auth secret, applies pending migrations, and uploads keys marked `.secret()` as encrypted Worker secrets.
6. Uploads a Worker Version and checks that it is ready.
7. Sends traffic to the new version and applies routes, custom domains, cron schedules, queue consumers, and Email Routing rules.

The secret and migration checks need the built Worker, so they happen after provisioning and building. If a check fails, resources may already exist, but Void has not applied remote D1 migrations or uploaded the application Worker.

Expand All @@ -274,6 +275,8 @@ Run the first deploy from one machine at a time. The provisioning lock protects

Set `CLOUDFLARE_API_TOKEN` in your CI secrets. The token needs Workers Scripts: Edit and read access to bound resources, plus edit permission for each product Void needs to provision. Set `CLOUDFLARE_ACCOUNT_ID` if the token can access more than one account.

Email setup needs a browser session from `void cloudflare login`, which carries the Email Routing and Email Sending scopes, or a `CLOUDFLARE_API_TOKEN` that also has Email Routing Edit and Email Sending Edit. A Global API Key pair is refused. In CI the email step never prompts: run `void email setup --platform cloudflare` once locally, commit `wrangler.jsonc`, then deploy with `--require-email`.

When a Worker has no version preview URL, such as a Worker with Durable Objects, also set `CLOUDFLARE_WORKERS_SUBDOMAIN`. Use the account subdomain, for example `my-team` or `my-team.workers.dev`. Local deploys cache this value in ignored `.void/cloudflare.json` after Cloudflare reports a deployment URL; a fresh CI checkout has no such cache.

#### Cloudflare Access
Expand Down Expand Up @@ -335,6 +338,7 @@ Worker apps support D1, KV, R2, Queues, typed state, PostgreSQL and MySQL throug
- **Worker apps need a fresh build.** `--skip-build` works for existing static, SPA, and SSG output. Worker validation needs the current build's vars and auth schema.
- **Named Cloudflare environments aren't supported.** Direct commands use the top-level root config and reject `CLOUDFLARE_ENV`, `CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH`, and project-local name overrides. Use a separate root config per deployment target.
- **Node.js, Bun, and Deno use a different deployment path.** See their [integration guide](./nodejs-bun-deno.md).
- **Email is set up on the first deploy, on a zone you own.** Put `email.from` in `void.json`; the deploy reads your account, prints a checklist of what it would change, and asks once. See [Your own Cloudflare account](../guide/email.md#your-own-cloudflare-account). Outside Void's reach: `email/_default.ts` and dynamic local parts (`email/[user].ts`) need a catch-all, which exists only on a zone apex, so on a mail subdomain they get no rule; a mail domain that already has non-Cloudflare MX records is refused, never routed over; `sendEmail()` to arbitrary recipients needs Workers Paid (Email Sending onboarding), otherwise verified destinations only; Void owns the top-level `addresses` array in `wrangler.jsonc`, so hand-written entries there skip the email step; and `void email usage`, `logs`, `allow`, and `destinations` are platform-only.

WebSocket routes and typed state use SQLite-backed Durable Objects. Commit the bindings and `new_sqlite_classes` migration history in `wrangler.jsonc`; don't delete or reorder deployed migration steps. Older hosted classes using `new_classes` keep their existing storage.

Expand Down
4 changes: 4 additions & 0 deletions docs/integrations/nodejs-bun-deno.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ Void-managed WebSocket route files (`*.ws.ts`) are still Cloudflare-only because

You can still define [cron jobs](../guide/jobs.md) in `crons/` and they will compile into the bundle, but there is no built-in scheduler to invoke them. On Cloudflare, Workers Cron Triggers call the `scheduled` handler automatically. On Node.js, you'll need an external scheduler (e.g. `node-cron`, systemd timers, or your hosting platform's cron) to trigger the exported handler.

### No inbound email

Inbound [email handlers](../guide/email.md#inbound) in `email/` need Cloudflare Email Routing to invoke the worker's `email()` export, and the Node.js entry exposes HTTP only. An `email/` directory fails the build with guidance. Outbound `sendEmail` has no send transport on these targets either: it returns a `BINDING_MISSING` error.

### No prerendering

[Prerendering](../guide/edge/prerendering.md) is a platform feature that relies on Cloudflare's edge cache and the Void deploy pipeline. It is not available for non-CF targets.
Expand Down
Loading