Skip to content

[Email Service] Retire /email-routing/ docs and clean up Email Service#31082

Open
ttoino wants to merge 12 commits into
cloudflare:productionfrom
ttoino:joaop/email-service-cleanup
Open

[Email Service] Retire /email-routing/ docs and clean up Email Service#31082
ttoino wants to merge 12 commits into
cloudflare:productionfrom
ttoino:joaop/email-service-cleanup

Conversation

@ttoino
Copy link
Copy Markdown

@ttoino ttoino commented May 27, 2026

Summary

This PR retires the standalone /email-routing/ documentation tree and folds it into Email Service, the merged product covering both inbound (Email Routing) and outbound (Email Sending) email on the developer platform. It also resolves a long-standing backlog of inconsistencies, stale content, and structural issues in the Email Service docs that built up during the migration.

Documentation consolidation

  • Retire the legacy /email-routing/ page tree (26 pages, two orphan partials, and the duplicate directory entry).
  • Restore technical reference content that was lost in the original migration: ARC support, inbound MX hostnames, sender rewriting, RBL/SMTP error format, the SPF record breakdown, dual DKIM signatures on forwarded mail, Workers API reply requirements (DMARC, once-per-event, 100 References limit), the four send_email binding variants, the Email Routing platform limits, locked-DNS records mechanics, and a new audit logs page.
  • Add 301 redirects from every old /email-routing/... URL to its /email-service/... equivalent (with section anchors where content moved within a page) and a catch-all for anything not enumerated. Flatten four pre-existing legacy redirect chains.
  • Update inbound link occurrences within email-service/ docs, partials, and changelog entries. External-file inbound links continue to resolve via the redirects; they are tracked for a follow-up.
  • Move the four historical Email Routing changelog entries into the email-service product folder, and add redirects so the old per-product changelog URL still resolves.

Content corrections

  • Fill stubbed examples and fix an Attachment field-name mismatch (contentTypetype) that would have made the example code fail against the canonical Workers binding interface.
  • Reconcile the 5 MiB outbound message size limit between the attachments example and the platform limits page.
  • Convert in-handler auto-reply examples from env.EMAIL.send() to message.reply() so DMARC alignment and threading semantics are preserved. Queue-consumer sites stay on env.EMAIL.send() because the original EmailMessage is no longer in scope there.
  • Correct the Email Routing limits row covering "Addresses": the 200-address limit applies to destination addresses, which are account-level, not to per-domain custom addresses.
  • Repair a broken 4-backtick code fence on the landing page, fix indentation on a WranglerConfig block and a caution admonition in the get-started flows, add a missing language tag on a code fence in the local-development pages, and change a :::warning to :::caution (Starlight does not support warning).

Terminology and dashboard alignment

  • Rename "Custom addresses" → "Routing rules" and "Catch-all address" → "Catch-all rule" throughout the routing configuration page (preserving the #destination-addresses anchor used by inbound links).
  • Rename the form field "Custom address" → "Email pattern" to match the dashboard.
  • Rename get-started headings "Create a forwarding rule" / "Test your forwarding rule" → "...routing rule" to match dashboard terminology.
  • Update dashboard URLs to match per-domain / account-wide reality and replace "Add destination/subdomain" button language with inline-form wording where the dashboard no longer exposes a button.
  • Update five stale "go to Email Routing" navigation lines to the full "Compute > Email Service > Email Routing" path.
  • Rewrite the Domain management section: per-service removal flows, clarified locked-record semantics, and correct the unlock path (Email Routing settings page, only Email Routing records support unlocking).

Structure and consistency

  • Restructure observability/logs status lists (Handled, Delivery failed, Error) and remove the inaccurate "Email headers" section.
  • Add Email Routing datasets to observability/metrics-analytics.
  • Split Workers binding limits into Free/Paid columns and link to the canonical Workers platform limits page.
  • Convert WranglerConfig source blocks from TOML to JSONC so inline // comments survive the component's TOML↔JSONC round-trip on the default wrangler.jsonc tab. Fix two malformed code fences along the way.
  • Resolve sidebar.order collisions at the root and within configuration/ and reference/.
  • Standardise the Discord URL, rename "Suppressions lists" → "Suppression lists", rename "Observability & Logs" → "Observability and logs", convert title-case Tab labels and headings to sentence case in configuration/domains.mdx and elsewhere, add trailing slashes on internal links, switch fully-qualified https://developers.cloudflare.com/api/... links to relative /api/..., remove unused component imports.

Closes EMAIL-1832

Documentation checklist

  • The change adheres to the documentation style guide.
  • If a larger change - such as adding a new page- an issue has been opened in relation to any incorrect or out of date information that this PR fixes.
  • Files which have changed name or location have been allocated redirects.

@ttoino ttoino changed the title [Email Service] Restore migrated content, fix inconsistencies, resolve TODOs [Email Service] Restore migrated content, fix issues, retire /email-routing/ May 27, 2026
@ttoino ttoino changed the title [Email Service] Restore migrated content, fix issues, retire /email-routing/ [Email Service] Retire /email-routing/ docs and clean up Email Service May 27, 2026
@ttoino ttoino force-pushed the joaop/email-service-cleanup branch from 5293851 to fa082d2 Compare May 27, 2026 15:08
@ttoino ttoino marked this pull request as ready for review May 27, 2026 15:38
@ttoino ttoino requested review from a team as code owners May 27, 2026 15:38
@ttoino ttoino force-pushed the joaop/email-service-cleanup branch from edcd2de to 75209a3 Compare May 27, 2026 17:24
@cloudflare-docs-bot
Copy link
Copy Markdown

Review

⏸️ Automatic reviews for this PR are paused.

This PR has already received 2 automatic reviews. To run another review, a codeowner can comment /review or /full-review.

Tip: Keep PRs in draft mode until they are ready for review — the bot skips draft PRs automatically.

@ttoino ttoino force-pushed the joaop/email-service-cleanup branch from 9bc23be to 21282b1 Compare May 28, 2026 16:43
ttoino added 11 commits May 29, 2026 11:12
Carry forward technical details, procedures, and reference data from
the older /email-routing/ documentation set that were dropped during
the move to /email-service/.

- Postmaster: ARC, inbound MX hostnames, sender rewriting, RBL/SMTP
  error format, full SPF breakdown, dual DKIM signature behaviour.
- Workers API: message.reply() requirements (DMARC, once-per-event,
  100 References limit) and the X-* header rule on forward().
- send_email binding variants: destination_address,
  allowed_destination_addresses, allowed_sender_addresses.
- Platform limits: Email Routing inbound caps (200 rules, 200
  addresses, 25 MiB inbound) and EXCEEDED_CPU guidance.
- Domains: locked DNS records section, _dc-mx behaviour, leftover
  DKIM cleanup via the API.
- New page: observability/audit-logs.
- Misc: Activity log time-range filtering, SPF re-onboard fallback,
  Worker-rename route-binding warning, test-from-different-account tip.
Align WranglerConfig source blocks with the repo's predominant
authoring convention (JSONC + // comments). The WranglerConfig
component re-serializes between TOML and JSONC and strips comments
during round-trip; JSONC sources let the default wrangler.jsonc tab
display the inline comments where readers expect them.

Also fix two malformed code fences in hard-bounce-handling.mdx and
local-development/routing.mdx (stray 4-backtick closers that left
the markdown structure broken).
Address a backlog of editorial TODOs and content inconsistencies
discovered during a full review of the email-service docs.

Content corrections
- Fill stubbed Simple email example in api/send-emails/workers-api.
- Fix Attachment field name contentType -> type (4 sites in
  email-attachments.mdx) to match the canonical interface.
- Repair malformed 4-backtick fence and inconsistent indentation in
  the landing page's quick-start example.
- Reconcile 5 MiB outbound limit between examples/email-attachments
  and platform/limits.
- Convert in-handler auto-reply examples from env.EMAIL.send() to
  message.reply(), restoring DMARC and threading semantics. Queue
  consumer sites stay on env.EMAIL.send() with a note explaining why.

Terminology and structure
- Rename 'Custom addresses' -> 'Routing rules' and 'Catch-all
  address' -> 'Catch-all rule' throughout
  configuration/email-routing-addresses; preserve the
  #destination-addresses anchor used by inbound links.
- Rewrite Domain management in configuration/domains: per-service
  removal flows; clarify locked-record semantics; correct that only
  Email Routing records can be unlocked, via the Email Routing
  settings page.
- Update dashboard URLs to account/per-domain reality and replace
  'Add destination/subdomain' button language with inline-form
  wording where the dashboard no longer exposes a button.
- Restructure observability/logs status lists (Handled, Delivery
  failed, Error) and remove the inaccurate 'Email headers' section.
- Add Email Routing datasets to observability/metrics-analytics.
- Split Workers binding limits into Free/Paid columns and link to
  the canonical Workers platform limits page.

Cleanups
- Resolve 23 TODO markers across 6 files.
- Remove stray 'type: example' frontmatter from 3 files.
- Replace stale 'compatibility_date: "2024-01-01"' with $today.
- Resolve sidebar.order collisions at the root (concepts=3,
  configuration=4, observability=8, reference=9, platform=10).
- Drop the orphan partials/email-service/domain-setup partial.
- Standardise Discord URL to https://discord.cloudflare.com.
- Fix 'Suppressions lists' title typo and rename
  'Observability & Logs' -> 'Observability and logs'.
- Trailing slash on internal links; relative paths for /api/
  references in REST API docs.
- Remove unused component imports from 6 files.
Add 301 redirects from every old /email-routing/ URL to its
/email-service/ equivalent (including section anchors where the
content moved within a page) and a /email-routing/* catch-all in
the Dynamic redirects section for anything not enumerated.
Flatten the 4 pre-existing legacy redirect chains so each points
directly at its final /email-service/ destination instead of
chaining through /email-routing/.

Within email-service, this commit also updates internal links and
the MTA-STS image references that previously pointed at
/email-routing/ paths. Move the MTA-STS images from
assets/images/email-routing/ to assets/images/email-service/ so
their new path matches the email-service product directory.

Inbound links to /email-routing/ in files outside email-routing/
and email-service/ are left to a follow-up; the wildcard
redirect keeps them functional in the meantime.
Remove the legacy /email-routing/ documentation tree. Every page is
covered by a redirect to its /email-service/ replacement, added in
the previous commit, and every inbound link across the repo has
already been pointed at the new locations.

- Delete all 26 pages under src/content/docs/email-routing/.
- Delete the orphan partials email-routing-definition.mdx and
  enable-create-worker.mdx (no longer referenced after the pages
  are gone). Keep send-emails-workers-intro.mdx and types-bindings.mdx
  because they are still rendered by
  src/content/docs/workers/wrangler/configuration.mdx.
- Delete src/content/directory/email-routing.yaml; the existing
  email-service.yaml entry covers the merged product.
Move the four historical Email Routing changelog entries into the
email-service product folder so they continue to be indexed after
the email-routing directory entry was retired.

src/util/changelog.ts:getChangelogs() requires every changelog
entry's containing folder to map to a valid src/content/directory/
entry. Deleting directory/email-routing.yaml in the previous commit
broke that invariant for these files and made the page builder
throw 'Unable to render Content because it is undefined' on any
page that calls getChangelogs() (the index, per-product changelog
pages, and per-post pages).

- Move src/content/changelog/email-routing/*.mdx into
  src/content/changelog/email-service/ via git mv (4 files).
  The per-post URL pattern uses only the file slug, so existing
  /changelog/post/<slug>/ URLs continue to resolve.
- Move src/assets/images/changelog/email-routing/subaddressing.png
  to src/assets/images/changelog/email-service/ to match. The
  referencing changelog entry already points at the new path.
- Add redirects from /changelog/product/email-routing/ (and any
  paginated subpath) to /changelog/product/email-service/ so
  anyone with the old per-product changelog URL bookmarked lands
  on the merged product's changelog.
- Rename 'Rules per domain' -> 'Routing rules per domain' and drop
  'custom address' from the row's note to align with the
  terminology refactor applied to the rest of the docs.
- Correct the 200-address limit: it covers destination addresses,
  which are account-level (shared across domains), not custom
  addresses configured per domain. Rename the row and update its
  note accordingly.
- Reword the paragraph below the table for the same terminology
  pass: 'custom address' -> 'email pattern'.
A final-pass audit caught a handful of malformed code blocks and
admonitions left over from earlier prettier-induced reformatting.

- index.mdx: close the wrangler.jsonc Tab's code fence with three
  backticks instead of four, so it renders as a fenced block
  instead of leaking raw backticks into the page.
- get-started/send-emails.mdx: redent the <WranglerConfig> block
  in step 2 so it lines up with the surrounding ordered list.
  Prettier had spread the opener at 4 spaces and the content at
  12 spaces, producing visible leading whitespace in the rendered
  jsonc.
- get-started/route-emails.mdx: align the closing ::: of the
  'Update configuration' caution block with its opener so it
  parses as an admonition rather than rendering the closing
  marker as text.
- local-development/sending.mdx: add a txt language tag to the
  bare code fence containing the ArrayBuffer error message.
- local-development/routing.mdx: drop the send_email binding
  from the wrangler config in the inbound-routing example. The
  worker only calls message.forward() and never uses
  env.EMAIL.send(), so the binding was misleading.
- get-started/index.mdx: convert the empty stub page into a
  navigation page that renders <DirectoryListing /> so the
  Get started landing surfaces its children.
…rendering

Final-pass audit touch-ups.

- Resolve sidebar.order collisions within email-service
  subdirectories: configuration/email-routing-addresses (1->2)
  and subdomains (5->3), reference/troubleshooting (2->3) and
  faq (2->4). Remaining intra-directory collisions are with
  hideIndex'd index pages and have no effect on rendered order.
- Replace stale 'go to **Email Routing**' phrasing with the full
  'Compute > Email Service > Email Routing' dashboard path in
  five places across get-started/route-emails.mdx and
  configuration/email-routing-addresses.mdx.
- Rename 'Create a forwarding rule' / 'Test your forwarding rule'
  headings in get-started/route-emails.mdx to 'routing rule' to
  match the dashboard's labelling. Update the redirect for
  /email-routing/get-started/test-email-routing/ so the anchor
  still resolves.
- Update two 'custom address' references in reference/postmaster
  to 'email pattern' / 'routing rule email pattern' to match the
  terminology refactor applied earlier in this branch.
- Fix the 2025-07-21 subaddressing changelog typo 'learn on to
  enable' -> 'learn how to enable'.
- Remove unused DashButton and Render imports from
  concepts/deliverability.
- Sentence-case headings: configuration/domains TabItem labels
  (MX Records, SPF Record, DKIM Record, DMARC Record, DNS
  Propagation, Record Conflicts) and bold subheadings (SPF/MX/
  DKIM Conflicts) and the 'Existing SPF Records' aside title.
  Also examples/email-sending/email-attachments (PDF/Inline
  Images/File Uploads), reference/faq (Limits and Usage), and
  concepts/email-lifecycle (Stage Details:).
- Promote 'More resources' from H3 to H2 on the landing page so
  it's a peer of 'Related products' rather than nested under it.
- Convert the bold '**Next steps:**' paragraph at the end of
  api/route-emails/email-handler to a proper '## Next steps' H2.
- Change ':::warning' to ':::caution' in
  configuration/email-routing-addresses. Starlight only supports
  note/tip/caution/danger asides; warning rendered as raw text.
Two build-blocking issues surfaced after the previous push.

1. The retired src/content/directory/email-routing.yaml left two
   files with dangling 'products: email-routing' frontmatter
   references that the docs schema rejects:
   - analytics/graphql-api/tutorials/querying-email-routing.mdx
   - workflows/examples/send-invoices.mdx
   Update both to 'email-service'. Body links in
   send-invoices.mdx still need migration and remain deferred in
   .agents/EMAIL-SERVICE-FOLLOWUPS.md.

2. Three DashButton calls pointed at deeplinks that are not in
   src/content/dash-routes/, so the DashButton component threw
   '[DashButton] No route found for ...' at build time:
   - /?to=/:account/email-service (used in domains.mdx and
     metrics-analytics.mdx as the top-level Email Service entry)
   - /?to=/:account/email-service/routing/destination-addresses
     (used in email-routing-addresses.mdx and route-emails.mdx)
   The bare /email-service URL is not a real dashboard
   destination (Email Service is a sidebar grouping with Routing
   and Sending children). Remove the two ambiguous DashButtons;
   the surrounding prose already describes the navigation. Fall
   back to the registered /email-service/routing parent URL for
   the destination-addresses links since the prose already
   directs users to the Destination Addresses tab from there.
   Drop the now-unused DashButton import from
   metrics-analytics.mdx.
Documentation issues addressed in a single pass after a comprehensive review:

Correctness fixes:
- Replace regex-based MIME parser example with postal-mime
- Replace O(n^2) stream reader pattern with Response().arrayBuffer()
- Replace btoa(String.fromCharCode(...)) with direct ArrayBuffer for
  attachments (avoids RangeError on files >64 KB)
- Correct forward()/reply() return type from Promise<void> to
  Promise<EmailSendResult> per workerd source
- Add ArrayBufferView to Attachment.content type
- Fix swapped REST API error codes (email.invalid / email.too_big)
- Add complete Email Routing dimensions table to metrics-analytics
  (only sending was documented)

Content additions:
- Inbound (Email Routing) lifecycle section in concepts/email-lifecycle
  with mermaid diagram and stage details
- Subdomain limit (50 per zone) on platform/limits and subdomains
- Email Sending audit log actions on observability/audit-logs
- MTA-STS testing-mode caution and DMARC cross-domain rua note
- Next steps sections on 9 pages that lacked them
- Intro paragraphs on 8 bare DirectoryListing index pages

Structure and consistency:
- Rename ambiguous 'Workers API' titles to 'Email handler' and 'Send
  method' to disambiguate routing vs sending
- Reorganize mta-sts.mdx into two H2 sections with content properly
  indented under list items
- Demote catch-all and subaddressing to H3 under Routing rules
- Renumber concepts sidebar order consecutively
- Replace REST API stubs to conform to Dev Platform norm: routing
  REST API is now a navigation stub to /api/resources/email_routing/,
  send-emails has 'Send REST API' (the one carve-out with full content
  for the send endpoint) and a 'Configuration REST API' stub to
  /api/resources/email_sending/

Style and convention:
- whitelist -> allowlist in headers reference
- company.com placeholders -> example.com (destinations) or
  yourdomain.com (senders) per repo convention
- <ACCOUNT_ID> -> {account_id} in get-started
- Remove emoji from console.log strings and HTML email body examples
- Replace smart quotes with straight quotes
- ARC link http -> https

Cleanup:
- Remove placeholder 'Configuration details' section from postmaster
  in favor of cross-links
- Remove 'Clean up leftover DKIM records' subsection (unclear scope)
- Remove stale 'Ranges last updated: December 13th, 2023' date stamp
- Replace vague daily-limits text with practical guidance
- Clarify pricing (per-account, monthly, bounces count, suppressed don't)
- Rename troubleshooting page from 'Troubleshoot SPF, DKIM and DMARC'
  to plain 'Troubleshooting' to match actual scope
@ttoino ttoino force-pushed the joaop/email-service-cleanup branch from 21282b1 to 60188ef Compare May 29, 2026 10:18
@ttoino ttoino requested a review from rita3ko as a code owner May 29, 2026 10:18
Sending to verified destination addresses is free on all plans,
including when only Email Routing is configured, and never counts
toward the monthly quota or daily sending limits. The docs previously
implied the opposite: the pricing page stated Email Sending was
Workers Paid only, and the limits page framed verified-recipient
sending as a temporary testing affordance.

- pricing: split the plan gating (arbitrary recipients require Workers
  Paid; verified destination addresses are free on all plans) and add
  a quota carve-out for verified sends
- limits: rewrite the verified-addresses section with the correct gate
  (before onboarding a sending domain you can only send to verified
  addresses; after onboarding you can send to anyone immediately) and
  document that verified sends are free, quota- and limit-exempt, and
  must originate from your routing domains
- email-routing-addresses: note that destination addresses can be used
  as free send targets via the REST API or Workers binding
- index: surface the free verified-recipient send path on the landing
  page

Standardize terminology on 'verified destination addresses'.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant