Skip to content

Route RFC 5233 subaddressed mail to the base local part - #644

Merged
kody-bot merged 1 commit into
mainfrom
cursor/auto-username-email-inbox-8ab0
Jul 6, 2026
Merged

kody-bot merged 1 commit into
mainfrom
cursor/auto-username-email-inbox-8ab0

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Jul 6, 2026 •

Copy link
Copy Markdown
Owner

Follow-up to #643: subaddressing (plus addressing) now routes. Mail to {username}+{tag}@inbox.heykody.dev reaches {username}'s inbox, and support+{tag}@heykody.dev reaches the corresponding operator system inbox.

Why

The Cloudflare zone-level subaddressing toggle only affects Cloudflare's own rule matching. Our catch-all hands the worker the full local part, and the worker previously looked up username+tag as a literal username — which fails the username format check (+ is not a valid username character) — so subaddressed mail bounced as "Unknown Kody email address." This unlocks a useful package pattern: an email.message.received handler that only processes mail addressed to its tag (e.g. {username}+invoices@…).

What changed

  • New splitEmailLocalPart helper in email/address.ts: splits at the first + (user+a+b → base user, tag a+b).
  • handleInboundEmail routes on the base local part for both paths (user subdomain and apex system inboxes). All gates — reserved locals, unknown usernames, system-local matching — evaluate the base, so a tag can never smuggle past them.
  • The stored message keeps the full tagged address in to_addresses (it comes from the parsed message, untouched), so package handlers can dispatch on the tag. No schema or payload changes needed.
  • Docs: email-primitives.md addressing model documents the behavior and the package dispatch pattern.

Tests

  • address.node.test.ts: splitter cases (no tag, tag, multi-+, empty tag, empty base).
  • inbound.workers.test.ts: {username}+billing@ routes to the same auto-provisioned inbox with the tagged address preserved in to_addresses; help+tag@ still rejects as reserved; missing+tag@ still rejects as unknown.
  • system-email.workers.test.ts: support+ticket-123@<apex> stores under the operator support inbox with the tagged address preserved.

npm run validate green locally.

System recap — composes existing primitives (low risk)

Mode: recap · Base: main @ cc9a9275 · Head: 022aec9e

Classification: composes — inbound routing normalizes the local part before the existing lookups; no contracts, schemas, or payloads change.

Primitives touched

Primitive Group Impact
email assistant composes — subaddress tags strip to the base local part for routing

System map

flowchart LR
	cfRouting["Cloudflare Email Routing (catch-all)"]:::untouched
	email["email inbound routing"]:::touched
	packageRuntime["package-runtime (email.message.received)"]:::untouched
	cfRouting --> email
	email -->|"to_addresses keeps +tag"| packageRuntime
	classDef touched fill:#1a7f37,color:#fff
	classDef extended fill:#9a6700,color:#fff
	classDef added fill:#cf222e,color:#fff
	classDef untouched fill:#57606a,color:#fff
Loading

Before / after

kentcdodds+invoices@inbox.heykody.dev  →  before: rejected (unknown address) · after: routes to kentcdodds
support+ticket@heykody.dev             →  before: rejected (unknown address) · after: routes to operator support inbox
help+tag@inbox.heykody.dev             →  rejected (reserved) — unchanged, tags can't bypass gates
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Inbound email now supports plus-tagged addresses like user+tag@domain, while still routing to the correct inbox.
    • Stored recipient details now preserve the full tagged address for downstream handling.
  • Bug Fixes

    • Tagged addresses can no longer bypass reserved-name or unknown-user checks.
    • System and user inbox routing now treat tagged variants consistently with their base address.
  • Documentation

    • Updated email usage docs to explain plus-tagged address behavior and routing.

user+tag@<platform domain> now routes to user's inbox and
support+tag@<apex> to the corresponding operator system inbox: the base
local part (before the first +) is what routes, so tags can never
bypass the reserved or unknown-username checks. The full tagged address
stays in the stored message's to_addresses so email.message.received
package handlers can dispatch on the tag.
@coderabbitai

coderabbitai Bot commented Jul 6, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 90bf8944-0f5f-4428-86c6-75e18dd582e2

📥 Commits

Reviewing files that changed from the base of the PR and between cc9a927 and 022aec9.

📒 Files selected for processing (6)
  • docs/use/email-primitives.md
  • packages/worker/src/email/address.node.test.ts
  • packages/worker/src/email/address.ts
  • packages/worker/src/email/inbound.ts
  • packages/worker/src/email/inbound.workers.test.ts
  • packages/worker/src/email/system-email.workers.test.ts

📝 Walkthrough

Walkthrough

Adds an exported splitEmailLocalPart helper that splits an email local-part at the first + into a base and subaddress tag, and wires it into handleInboundEmail for system-inbox routing, reserved-username checks, and identity lookups while preserving the full tagged address in storage. Includes tests and documentation updates.

Changes

Subaddressing feature

Layer / File(s) Summary
splitEmailLocalPart helper and unit tests
packages/worker/src/email/address.ts, packages/worker/src/email/address.node.test.ts
New exported splitEmailLocalPart(localPart) splits a local-part at the first + into {base, subaddress}, returning subaddress: null when absent or empty; unit tests cover no-+, single-+, multi-+, trailing-+, and leading-+ cases.
Inbound routing uses local base for checks
packages/worker/src/email/inbound.ts
handleInboundEmail derives localBase from the tagged local part and uses it for system-inbox eligibility, handleSystemInboundEmail dispatch, reserved-username rejection, and public identity lookup, while preserving the full tagged address elsewhere.
Worker tests and docs for tagged routing
packages/worker/src/email/inbound.workers.test.ts, packages/worker/src/email/system-email.workers.test.ts, docs/use/email-primitives.md
Tests confirm tagged addresses route to the correct inboxes, preserve tagged toAddresses in stored messages, and still trigger reserved/unknown rejections; docs describe the new subaddressing behavior.

Estimated code review effort: 2 (Simple) | ~12 minutes

Possibly related PRs

  • kentcdodds/kody#636: Both PRs modify handleInboundEmail in packages/worker/src/email/inbound.ts, one adding subaddress splitting/routing and the other adding quota enforcement/metering in the same handler.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: routing RFC 5233 subaddressed mail by the base local part.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/auto-username-email-inbox-8ab0

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-644.kentcdodds.workers.dev

Worker: kody-pr-644
D1: kody-pr-644-db
KV: kody-pr-644-oauth-kv

Mocks:

@kody-bot
kody-bot merged commit 83f9622 into main Jul 6, 2026
5 checks passed
@kody-bot
kody-bot deleted the cursor/auto-username-email-inbox-8ab0 branch July 6, 2026 16:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants