Email
Manage the email features of your platform: sending domains, limits and delivery.
On this pageCheck platform email settings · Manage sending domains · Review delivery
Managing email
These commands apply to a platform that enables email. Every project can send from <slug>+tag@<mail domain> on the platform’s shared mail domain. Deploying an app with email/ handlers sets up receiving at that address once its recipient rule is ready; the email guide describes what applications do with that.
Shared inbound mail uses one exact routing rule per receiving project and enables Cloudflare’s per-zone plus addressing. Creating a project or deploying an app that only sends mail consumes no inbound rule. Existing rules and the apex catch-all remain in place. Cloudflare normally allows 200 routing rules per domain; remove unused rules or request a higher limit if the shared domain reaches capacity. An unrelated rule for a project’s base address or a plus address blocks its setup until that conflict is removed.
Requires: login or token
These commands need a signed-in administrator session. Token files must stay out of version control.
This step inspects a stopped recipient creation and previews its resolution.
void platform email shared-recipients --project my-app
void platform email shared-recipient-resolve <recipient-id> --ended --outcome not-applied --reason "Provider audit confirms the request did not create a rule" --planExample output: The shared recipients, then a preview of the resolution.
Wait until the reported recovery time, confirm the original execution ended, and inspect the exact provider resource shown in the plan. Use applied when the exact owned rule exists, or not-applied only when the provider audit establishes that creation did not occur. Absence alone does not establish the outcome. Run the same command with --yes after inspection. Recovery records the decision in platform events without creating or deleting rules. Normal reconciliation verifies delivery afterward; a deleting project stays closed and its deletion can be retried. See operator email commands for the recovery requirements.
Deciding who mail may reach
By default, shared-sender mail reaches only each project's verified recipients. This step widens that for the whole installation, for teams whose apps mainly mail colleagues.
void platform email policy
void platform email policy-set domains --domains example.com,corp.example.netExample output: The current policy, then the new domain policy.
void platform email policy-set any
void platform email policy-set verifiedExample output: The policy updated to the chosen mode.
Choose a policy in the admin UI's Email page or with the commands above:
verified: only each project's verified recipients.domains: verified recipients plus any address on the listed domains.any: any recipient.
Unverified recipients still require Cloudflare Email Sending onboarding and Workers Paid. Otherwise those sends return UNVERIFIED_DESTINATION. Mail from a project's custom domain, and mail to the platform's own mail domain, always requires recipient verification.
Policy changes apply to new sends; admitted attempts may finish.
Project caps
Each project has 200 recipient submissions per UTC calendar month and 10 in a rolling 60-second window by default. An attempt counts once it starts, even if it fails or is uncertain. This step raises or lowers a project's caps by ID or slug.
void platform email limit hr-portal --monthly 2000 --burst 30Example output: The new monthly and burst limits for the project.
The project's page in the admin UI shows the current values and clears an override.
Recovering an interrupted send
If a sender stops after beginning a provider call, its unresolved attempt blocks removal of the project or sender domain. Inspect the attempt ID and start time:
void platform email attempts --project hr-portalExample output: The send attempts for the project.
First confirm that the original execution has ended through your Worker logs or incident records. Once the attempt is at least 24 hours old, resolve it with an audit reason that contains no recipient address or message content:
void platform email attempt-resolve <attempt-id> --ended --reason "Original Worker execution ended; provider outcome could not be verified" --plan
void platform email attempt-resolve <attempt-id> --ended --reason "Original Worker execution ended; provider outcome could not be verified"Example output: A preview of the resolution, then the attempt marked as resolved.
This records the send as outcome_unknown and releases its cleanup fence. The attempt remains charged and is never retried. If its 30-day delivery record has expired, only its recipient-free fence is removed. Do not resolve an attempt while its original execution may still be active.
This step reads a project's retained receipts and recipient outcomes.
void platform email logs hr-portal --page 1 --limit 50 --jsonExample output: One page of email logs as JSON.
Logs include operation IDs, recorded outcomes, provider references, and error codes. After project deletion, use its project ID until the 30-day metadata retention period expires. Message bodies, subjects, attachments, and credentials are not logged.
Registering email domains for projects
Your users may have no Cloudflare account. This step tells the platform that administrators register email domains. void email domain add then prints the command to request, instead of opening a token page.
void platform email settings-set --domains adminExample output: The updated email settings.
This step registers a domain for a project. The zone must be in the platform's Cloudflare account. The platform's own credential does the setup. No per-domain credential is stored.
void platform email domain-add example.com --project hr-portal
void platform email domain-status example.comExample output: The domain added, then its status.
void platform email domain-rotate-secret example.com
void platform email domainsExample output: A rotated secret, then the domain list.
Use the apex of the selected Cloudflare zone. Platform-managed custom inbound email uses its catch-all, which does not cover subdomains; choose an apex without another mail provider or use the platform’s shared mail address. domain-status reports inbound, outbound, and management readiness separately. Follow any required DNS or Cloudflare dashboard step, then use domain-sync to reconcile. Use domain-rotate-secret when you need to replace the zone ingress credential explicitly. Email Sending onboarding needs Workers Paid; after upgrading, domain-sync re-attempts it. For a zone in another Cloudflare account, pipe an API token for that account on standard input:
void platform email domain-add other.example --project hr-portal --token-stdin --yes < token.txtExample output: The domain added without a prompt.
Project owners see administrator-registered domains in void email domain list and status; status names the void platform email command for any step they cannot take themselves, and sync and remove point them at domain-sync and domain-remove. A domain an owner registered before you switched to admin stays theirs to renew, sync and remove. The runtime token needs the email permissions listed in the self-hosting guide for this to work.
A permission refusal leaves setup blocked. After correcting it, run domain-sync to resume that same setup operation; repeat domain-rotate-secret to resume a blocked rotation without replacing its staged secret. If project deletion leaves a blocked domain cleanup, correct the permission and run domain-remove <domain> to finish the retained cleanup. If its token has expired or been revoked, provide a replacement scoped to the same account and zone:
void platform email domain-remove other.example --token-stdin --yes < token.txtExample output: The domain removed without a prompt.
This recovery is available only after project deletion has begun and no other project uses the connection. For a live project, renew its token through domain-add. A timed-out Cloudflare mutation stays stopped because replay may duplicate a provider write. After confirming the original execution ended, wait 24 hours, inspect the exact routing, Worker, secret, catch-all, or Sending resource named by the preview, and use its recovery fence:
void platform email operation-resolve <operation-id> --ended --outcome <applied|not-applied> --reason "Provider state verified" --plan
void platform email operation-resolve <operation-id> --ended --outcome <applied|not-applied> --reason "Provider state verified"Example output: A preview of the resolution, then the operation marked as resolved.
Run recovery with the same platform version that created the persisted intent. applied continues without replaying the write; not-applied permits that exact step to retry. Secret rotation keeps its staged generation, and removal resumes from the unresolved cleanup step. Age alone never authorizes a retry.