Use this guide when you want to enter an apex root domain in KaisouMail /domains and let the project create the Cloudflare full zone on your behalf.
This is the flow we are using right now. Compared with “manually add the zone in Cloudflare and enable it in the project later”, it removes one manual handoff but requires more complete runtime permissions and configuration.
The API Worker runtime must have:
EMAIL_ROUTING_MANAGEMENT_ENABLED=trueCLOUDFLARE_RUNTIME_API_TOKEN (or the shared CLOUDFLARE_API_TOKEN)EMAIL_WORKER_NAMEWhere:
EMAIL_ROUTING_MANAGEMENT_ENABLED=true allows the project to read and mutate Cloudflare domain and Email Routing stateEMAIL_WORKER_NAME determines which Worker future mailbox routing rules targetProject-direct binding adds one extra step compared with enabling an existing zone: creating the zone.
Use the full runtime minimum in Cloudflare Token Permissions, and make sure the token scope covers the intended Cloudflare account and zone set.
CLOUDFLARE_ACCOUNT_IDDirect bind also requires the GitHub repository secret:
CLOUDFLARE_ACCOUNT_IDThe deploy workflow injects it into the API Worker runtime. Without it, the project may have a valid token but still not know which Cloudflare account should own the new zone.
After deploy, confirm:
GET /api/meta returns cloudflareDomainLifecycleEnabled=trueGET /api/meta returns cloudflareDomainBindingEnabled=true/domains shows the Bind mailbox domain formIf the second value is still false, check whether CLOUDFLARE_ACCOUNT_ID actually reached Worker runtime instead of existing only in the GitHub Actions job environment.
/domainsOpen /domains in the control plane and locate the Bind new domain card:

/api/domains/bind is apex-onlyIf you want addresses such as user@mail.customer.com, do not submit
mail.customer.com directly to /api/domains/bind. The recommended path is:
customer.comsubdomain=mailuser@mail.customer.comIf Cloudflare already has a zone, you can use the
/domains catalog enable flow instead, but the
product does not treat child-zone onboarding as a standard free-tier path.
After you click Bind to Cloudflare, the project calls POST /api/domains/bind and then performs:
POST /zonesGET /zones/:zone_idPOST /zones/:zone_id/email/routing/enableactive, update the apex authoritative nameservers firstYou usually land in one of these states:
active: delegation is already satisfied and the domain can be used for new mailboxes immediately.provisioning_error / pending: the zone already exists, but activation is not finished yet. At this point you must update nameservers at the domain registrar before doing anything else.When the domain is retained in provisioning_error, the row stays visible in the domain catalog like this:

At that point, click the details icon in the action column for the same row and read the zone plus the Cloudflare-assigned nameservers from the dialog:

Then copy the nameservers from that dialog into your registrar exactly as shown.
In other words: the project creates the zone, but it does not update registrar NS records for you. That step is always manual.
/domains and retryAfter updating nameservers:
pending to active in Cloudflare/domainsactiveIf the apex authoritative nameservers are still not switched, repeatedly clicking Retry usually will not help.
Once the domain becomes active:
POST /api/mailboxes and POST /api/mailboxes/ensure can target it with rootDomainrootDomain, the server randomly selects from all active domainsGET /api/meta includes it in the current active root-domain listIf you also enable Catch All for that domain from /domains:
Catch All mailboxesIf you temporarily do not want new mailboxes to land on that domain, disable it in /domains. Disable stops new allocations, but it does not automatically delete historical routing rules.
Recommended order: check permissions and runtime config first, then inspect zone activation state, then inspect Email Routing write permissions.
com.cloudflare.api.account.zone.create permissionTypical message:
Requires permission "com.cloudflare.api.account.zone.create" to create zones for the selected accountIn the UI, it usually appears as a short inline prompt like this:

This means the runtime token cannot create a new Cloudflare zone, so the bind flow fails on the first Cloudflare call.
Fix steps:
/domainsTypical messages:
permission deniedforbiddenunauthorizedRequires permission ...If the failure is permission-related but not explicitly zone.create, the token usually lacks one of the required capabilities in the bind path, such as:
Fix steps:
CLOUDFLARE_ACCOUNT_IDTypical message:
Cloudflare domain binding requires CLOUDFLARE_ACCOUNT_ID to be configuredThis is not a token-permission issue. Runtime is missing the account id, so the Worker does not know where the new zone should be created.
Fix steps:
CLOUDFLARE_ACCOUNT_ID to repository secretsGET /api/meta returns cloudflareDomainBindingEnabled=true/domainspending or nameservers are not delegated yetTypical messages:
Zone is pending activationpending, activation, nameserver, or delegatedThis means Cloudflare accepted the apex zone creation request, but authoritative nameserver switching is not complete yet, so Email Routing cannot be enabled.
In the UI, this usually appears as a retained row with provisioning_error, plus a details icon and Retry in the action column:

This is the most common recoverable failure in the project-direct flow:
provisioning_error/domainsFix steps:
pending in Cloudflareactive/domains and click RetryTypical messages:
Email Routing management is enabled but EMAIL_WORKER_NAME is not configuredEmail Routing management is enabled but CLOUDFLARE_RUNTIME_API_TOKEN or CLOUDFLARE_API_TOKEN is not configuredThis is not a Cloudflare ACL problem. Worker runtime is missing configuration required by the bind flow.
Fix steps:
CLOUDFLARE_RUNTIME_API_TOKEN or CLOUDFLARE_API_TOKENEMAIL_WORKER_NAME/domainsTypical messages:
Authentication errorThis usually means the token:
Fix steps:
Zone: Zone: EditZone: Email Routing Rules: EditZone: Zone Settings: EditIf the error does not fit the categories above, keep checking in this order:
CLOUDFLARE_ACCOUNT_ID, and EMAIL_WORKER_NAME are all configured