Skip to content

What wrangler's login can do in the account, measured for the install

id: wrangler-login-reach
kind: platform-limit
measured_on: 2026-09-25
stale_when: >
wrangler's login scope list changes (28 scopes on 4.118.0, remeasured 30 September 2026; 25 on 4.90.1 when
rerun that day, recorded as 24 on 25 September;
`wrangler login --scopes-list` is the record);
Cloudflare changes which permission group governs POST or DELETE /zones/{zone_id}/email/routing/dns, or
the routing rules endpoints, or /accounts/{account_id}/event_subscriptions/subscriptions; or the registrar
or raw DNS endpoints start answering an `email_routing:write`-shaped token; or GET
/accounts/{account_id}/email/routing/addresses stops answering a token scoped email_routing:write
values:
wrangler.login_creates_subdomain_routing_records: 1
wrangler.login_deletes_subdomain_routing_records: 0
wrangler.login_reaches_raw_dns_records: 0
wrangler.login_reaches_registrar: 0
wrangler.login_writes_zone_catch_all: 1
wrangler.login_creates_event_subscription: 1

The question. Whether mailda install can set a Node up to receive, send and observe delivery outcomes with the consent the operator already gave to wrangler, so the Node’s own grant (ADR 42) stops being on the critical path. The grant asks for eight scopes; wrangler’s login carries zone:read, email_routing:write, email_sending:write and queues:write among the 24 then recorded (25 when 4.90.1 was rerun, and 28 on wrangler 4.118.0; below), and the Node’s provisioning code reaches Cloudflare through exactly those permission groups for everything but two endpoints.

Measured on 25 September 2026 with wrangler 4.90.1’s login token (then read from wrangler’s config file after wrangler whoami refreshed it; since 30 September 2026 the CLI asks wrangler auth token --json), in account 1e0170…, on the zone mailda.site. Reads first, then writes on the throwaway subdomain probe.mailda.site, then cleanup.

endpointmethodanswer
/zones?account.id=…GET200
/zones/{zone}/email/routingGET200, enabled: true, status: ready
/zones/{zone}/email/routing/rulesGET200
/zones/{zone}/email/routing/dns?subdomain=probe.mailda.siteGET200
/accounts/{account}/event_subscriptions/subscriptionsGET200
/accounts/{account}/queuesGET200
/zones/{zone}/email/sending/subdomainsGET200
/accounts/{account}GET200
/zones/{zone}/dns_recordsGET403
/accounts/{account}/registrar/domainsGET403
/zones/{zone}/email/routing/dns {name: probe.mailda.site}POST200; three MX records appeared in public DNS (route1/2/3.mx.cloudflare.net, priorities 96/75/44)
/zones/{zone}/email/routing/rules (a disabled drop rule)POST200, with an id
/zones/{zone}/email/routing/rules/{id}DELETE200
/zones/{zone}/email/routing/dns (body {name} and ?name=)DELETE403 10000 Authentication error
/zones/{zone}/email/routing/rules/catch_all (mailda.site’s own content: disabled, drop)PUT200; read back unchanged. A no-op write, chosen so the permission is measured without moving any mail

The apex mailda.site was untouched throughout; its MX records were the same before and after.

What this establishes. With wrangler’s login the CLI can enable routing for a subdomain (the endpoint writes the MX records itself, so dns.write is not needed for that), write and delete a routing rule, and read every state the Node’s Setup screen reads. Delivery-events subscriptions and the queue are read under the same permission group (queues:write) that the 16 September measurement in queue-provisioning.md found creates the subscription. So the install can leave a Node receiving, sending and observing outcomes without the Node holding any grant. The two things it cannot do are raw DNS records and the registrar, which is domain purchase.

What it does not establish. The sending-domain onboarding write was not run with wrangler’s token: the first real run below met a domain already onboarded, so it stays a same-permission-group inference. The subscription write is measured, below. And nothing here is about the grant: a Node that holds one still reaches exactly what cloudflare-oauth-scopes.md says.

The boundary it draws. Wrangler’s token may create a subdomain’s routing records and may not remove them, and it may write a zone’s catch-all (measured later the same day, when the apex catch-all became the receiving step’s offer for a zone’s own name). Taking a subdomain back out of Email Routing is therefore a dashboard act, or an act of a grant holding dns.write, and the settings table says so rather than offering an undo the install cannot make. And since receiving reads and writes only Email Routing’s own records (26 September 2026), dns.write is not in the grant either. The cost of that: a subdomain whose MX already points at a foreign mail host is not detectable through the routing endpoints, which report only Cloudflare’s records, so the records are written beside a foreign MX rather than refused. The proposal used to refuse that case by reading raw DNS.

Residue. The cleanup’s last step was refused, so probe.mailda.site kept its three MX records; they are removed by hand in the dashboard (the zone’s DNS, three MX records named probe). Harmless meanwhile: mail to that subdomain bounces, and nothing routes it.

The second real run: the catch-all taken over, live (25 September 2026, 17:08 UTC)

Section titled “The second real run: the catch-all taken over, live (25 September 2026, 17:08 UTC)”

The same Node, after receiving moved to Email Routing’s own record endpoints. With wrangler’s login token through the operator headers, on the apex whymelabs.com (routing ready, catch-all worker -> butler, enabled, eighteen literal rules including four forwards):

actoutcome
receiving proposalno refusal; has MX route1/2/3 from the zone’s own routing records; nothing to write
PUT …/rules/catch_all → this Worker200; read back worker -> mailda-whymelabs (enabled); the previous target recorded
the eighteen literal rulesuntouched, verified from the account afterwards
sendingonboarded already
delivery-events subscriptionsubscribed already (created by the run before)

So the catch-all write is measured changing a live catch-all, not only permitted. The put-back path (provider.catch_all_put_back) is still unmeasured live; the recorded before is worker -> butler.

The founder’s Node mailda-whymelabs, the apex whymelabs.com, wrangler’s login token carried on the operator headers. Four outcomes:

  • The zone picker worked. whymelabs.com was picked from the account’s list.
  • Receiving refused, and the refusal was ours. The proposal read GET /zones/{zone}/dns_records to learn the MX already on the apex, and the token answered 10000 Authentication error: the raw-DNS boundary the table above records. The CLI then printed a fix that blamed the domain (“must be a subdomain of a zone”), which was wrong. The catch-all question was never reached.
  • Sending: onboarded already. The domain had been onboarded for sending earlier, so no write happened and none was measured.
  • Delivery outcomes: the subscription was created. mailda-whymelabs-sending-events-whymelabs.com, publishing message.delivered, deferred, bounced, failed, rejected and complained into mailda-whymelabs-sending-events. That is the subscription write with wrangler’s token, queues.write’s permission group, and the value wrangler.login_creates_event_subscription records it.

Measured afterwards (26 September 2026 UTC, same token): Email Routing’s own records endpoint is both the plan and the read-back, so raw DNS is not needed for receiving at all.

stateGET /zones/{zone}/email/routing/dns
a subdomain never enabled, ?subdomain=probe2.mailda.siteerrors: [{code: "mx.missing", missing: {…the MX record…}} ×3, {code: "spf.missing", …}], no records
a subdomain enabled, ?subdomain=probe.mailda.siteerrors: null, records: [MX ×3, TXT spf]
an apex with routing ready, no querythe zone’s own records: MX ×3, the DKIM TXT, …

So a subdomain’s plan is the missing list, its read-back is errors: null with records, and an apex’s records come with enabling routing on the zone. The receiving code is being rewritten onto these endpoints; the raw-DNS boundary above then bounds nothing the install does.

Email Routing destination addresses (28 September 2026)

Section titled “Email Routing destination addresses (28 September 2026)”

Read for the verified-destinations read that mailda setup and mailda upgrade make with wrangler’s login (email-sending-events.md, the addition of this date). Account 1e0170…, wrangler 4.118.0’s login, about 05:00 UTC:

endpointmethodanswer
/accounts/{account}/email/routing/addresses?page=1&per_page=50GET200, wrangler 4.118.0’s login (email_routing:write): 7 listed, 6 status: "verified" with an ISO verified timestamp, 1 unverified with verified: null; fields created, email, id, modified, status, tag, verified. ?verified=true also 200

Counts were read; addresses were not printed. So the ordinary path reads the list with the consent the operator already gave wrangler, and the Node’s token does not need the permission for it.

Not measured: what a token without Email Routing Addresses: Read gets from the same endpoint. If it were 200 with an empty list rather than a refusal, a read that could not see the list would be recorded as one that found none verified; the Node’s first read with its own token settles it. Also not recorded yet: the exact shape of one verified string (format only), which the Node normalizes through Date.parse because it was never printed.

The scope list, remeasured on wrangler 4.118.0 (30 September 2026)

Section titled “The scope list, remeasured on wrangler 4.118.0 (30 September 2026)”

This receipt’s stale_when named the scope count, and the locked wrangler had moved from 4.90.1 to 4.118.0. wrangler login --scopes-list (read-only: it prints the list and signs nobody in), run in an isolated HOME with no login, names 28 scopes:

account:read user:read workers:write workers_kv:write workers_routes:write workers_scripts:write
workers_tail:read d1:write pages:write zone:read ssl_certs:write ai:write ai-search:write
ai-search:run websearch.run agent-memory:write queues:write pipelines:write secrets_store:write
artifacts:write flagship:write containers:write cloudchamber:write connectivity:admin
email_routing:write email_sending:write browser:write challenge-widgets.write

The four the values above rest on are all still there: zone:read, email_routing:write, email_sending:write and queues:write. Still none reaches raw DNS records or the registrar (dns_records:* and registrar:* are absent), so wrangler.login_reaches_raw_dns_records and wrangler.login_reaches_registrar stay 0 on the same grounds.

What changed since 4.90.1, from its own --scopes-list rerun the same day (the copy in nvm’s global modules, the same isolated HOME): it names 25, not the 24 this receipt recorded on 25 September, whose list was not kept, so that count is not reproduced. The three in 4.118.0 and not in 4.90.1 are agent-memory:write, challenge-widgets.write and websearch.run; none was removed. None of the three touches anything the values describe. Not remeasured: the endpoint answers in the tables above, which need a signed-in run; the scope list is the part of this receipt a change in wrangler alone could invalidate, and it has not.