Support multiple domains per inbox - #49
Open
PierreFouquet wants to merge 2 commits into
Open
PierreFouquet wants to merge 2 commits into
PierreFouquet wants to merge 2 commits into
Conversation
A single instance can already serve multiple domains by setting DOMAINS to a comma-separated list: the /api/v1/config endpoint splits it, and the New Mailbox dialog renders a domain picker when more than one is configured. This was undocumented, so users assumed it was unsupported (issue cloudflare#8). Make it a documented, first-class feature and add a matching backend guard: - Add a parseDomains() helper and reuse it in /api/v1/config. - Reject mailbox creation on domains outside the configured DOMAINS, mirroring the front-end picker. Explicit EMAIL_ADDRESSES entries bypass the check, so the auto-create flow (which may span domains) is unaffected. No DOMAINS configured means no restriction, preserving existing behavior. - Document multi-domain setup in the README ("Using multiple domains"), wrangler.jsonc, and the deploy-time DOMAINS binding description. Closes cloudflare#8 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Serving more than one domain from one instance does not work reliably on the receive side. receiveEmail picks parsedEmail.to[0] -- the first address in the To: header -- as the mailbox. That is not the address the message was actually delivered to, so with several domains configured: - a message addressed to an external recipient first is filed against that address, finds no mailbox, and is silently dropped - a Bcc'd message, whose recipient never appears in the headers, is dropped - a forged To: header can steer a message at another mailbox Resolve the mailbox from the SMTP envelope recipient (event.to) instead, which is the address Email Routing delivered to and the only signal that separates domains correctly. Header addresses (To/Cc/Bcc) remain a fallback for events that carry no envelope, such as local dev or a replayed message, and that fallback now prefers an address on a configured domain over header order. The envelope path is deliberately not filtered by DOMAINS: a stale DOMAINS value must not silently drop mail that Email Routing was configured to deliver to the Worker. Mailbox existence in R2 remains the real gate. The decision is extracted into resolveMailboxId() in lib/email-helpers so the rule sits in one readable place, and IncomingEmailEvent widens the handler's event type to carry the envelope fields. Single-domain deployments are unaffected: with one configured domain there is only one address the envelope can name. Verified: typecheck and build pass; resolveMailboxId exercised against 15 cases covering two domains, Bcc-only delivery, forged headers, the EMAIL_ADDRESSES allow-list, stale DOMAINS, and the no-envelope fallback. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UJmpmeXN1r87S3c2rJ7V8Y
PierreFouquet
pushed a commit
to PierreFouquet/agentic-inbox
that referenced
this pull request
Sep 3, 2026
…uting Brings in the full contents of cloudflare#49 as it now stands: - parseDomains() over the comma-separated DOMAINS var, mailbox creation restricted to the configured domains, and multi-domain setup documented in the README, package.json binding description, and wrangler.jsonc - inbound mail resolved by SMTP envelope recipient rather than the first To: header address, which is what actually keeps domains apart Conflict in wrangler.jsonc resolved by keeping our DOMAINS value ("pierrefouquet.co.uk") and our custom route block, while taking the upstream explanatory comments. No domain was added and no route changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UJmpmeXN1r87S3c2rJ7V8Y
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Resolves #8 ("Connect Multiple domain in 1 worker?").
While investigating, I found that a single instance can already serve multiple domains — it was just undocumented, which is why it was assumed unsupported:
GET /api/v1/configalready splits theDOMAINSvar on commas (workers/index.ts).Selectwhen more than one domain is configured (app/routes/home.tsx).validateSender) and receiving (receiveEmail) are domain-agnostic, andEMAIL_ADDRESSEScan already span domains.This PR makes multi-domain a documented, first-class feature and adds a matching backend guard.
Changes
workers/index.ts— add aparseDomains()helper (reused by/api/v1/config), and reject mailbox creation on domains outside the configuredDOMAINS, mirroring the front-end picker. ExplicitEMAIL_ADDRESSESentries bypass the check (they are the authoritative allow-list and may legitimately span domains), and an emptyDOMAINSimposes no restriction — so existing behavior is preserved.README.md— new "Using multiple domains" section; clarified the singular "your domain" wording.wrangler.jsonc— comments documenting thatDOMAINSaccepts a comma-separated list and thatEMAIL_ADDRESSESmay span domains.package.json— expanded the deploy-timeDOMAINSbinding description.No change to send/receive behavior.
How to use
Set
DOMAINSto a comma-separated list and add a catch-all Email Routing rule (and verified sending) per domain:Testing
Verified locally with a multi-domain config (
DOMAINS="example.com,another.com") injected via a local-only Wrangler config (per the approach in #44), runningnpm run dev:GET /api/v1/config{"domains":["example.com","another.com"],"emailAddresses":[]}✅hello@example.com(1st domain)201✅hi@another.com(2nd domain)201✅x@notconfigured.com400 "Mailbox domain must be one of the configured DOMAINS"✅npm run typecheck🤖 Generated with Claude Code