Skip to content

feat(email): permanent short alias on the shared mail domain - #553

Merged
Brooooooklyn merged 3 commits into
mainfrom
feat/email-project-alias
Oct 22, 2026
Merged

Brooooooklyn merged 3 commits into
mainfrom
feat/email-project-alias

Conversation

@Brooooooklyn

Copy link
Copy Markdown
Member

Fixes #511

What this adds

A project owner can give the project one short, permanent name on the platform's shared mail domain:

void email alias acme          # asks to confirm; --yes skips the prompt
void email alias               # shows the address and inbound status
before                               after (slug acme-store, alias acme)
──────                               ─────
acme-store@mail.example.com          acme-store@mail.example.com   ← still works
acme-store+support@…                 acme-store+support@…          ← still works
                                     acme@mail.example.com         ← new
                                     acme+support@…                ← new

Both names send and receive. Handlers route the same way either way: acme+support@… and acme-store+support@… both reach email/support.ts.

Answers to the three questions

1. Alias field, or another way?

A field: projects.email_alias (nullable, migration 0062).

projects
  slug         acme-store   (unique, as before)
  email_alias  acme         (unique when set)

one namespace: a trigger refuses
  alias = any slug     slug = any alias     alias = own slug
  • The DB trigger (void_project_mailbox_name_guard, SQLSTATE 23505) keeps slugs and aliases apart in both directions, including on older runtimes after a rollback.
  • The alias follows the slug rules: shape, 56 characters, reserved names, no preview-. Names of five characters or fewer need the same plan permission as short slugs.
  • Only the owner can set it (PUT /projects/:id/email/alias).

2. Permanent, or changeable?

Permanent. Set once, never changed or removed, and freed only when the project is deleted.

  • Reply addresses people have saved (acme+ticket-42@…) keep working.
  • No swap logic, so no window where mail bounces.
  • Because it is permanent, the route checks Cloudflare before saving: it refuses a name that an existing rule already routes (including plus addresses), and refuses when the domain has no room for the rules this project will need.

3. Does the old slug keep working?

Yes, for sending and receiving. An alias project uses two Cloudflare literal rules:

                         Cloudflare Email Routing (shared domain)
mail to acme-store+x@ ─▶ rule: acme-store@  ─┐
mail to acme+x@       ─▶ rule: acme@        ─┴─▶ email gateway ─▶ resolve-ingress
                                                     │  finds project by slug OR alias
                                                     │  checks readiness of THAT name
                                                     ▼
                                                  proxy router ─▶ project worker
                                                  (prefix must equal the name used)
  • Default from stays <slug>+noreply@…. Apps that want the alias as sender set from.
  • Admission accepts From with either name. Replies and forwards can use either name, whatever address the mail came in on.
  • Deploys only wait for the slug rule. A failing alias rule shows in void email alias but never blocks a deploy.

Rollback

upgrade ──▶ alias rules created
rollback ─▶ older runtime ignores email_alias; its reconcile removes alias rules;
            slug mail keeps working
upgrade ──▶ next reconcile restores the alias rows and rules
            (including a removal that stopped part way)
  • compatibility.json needs no change: no release contains 0061, and the existing 1.1.11–1.2.1 entries now cover 0062 (covered by a test).
  • Known rollback gap, written down in platform/meta/operations.md: on an older runtime, creating a project whose slug equals an existing alias returns 500 (the trigger refuses it), not slug already taken.

Rule limit

Each receiving alias project uses one more rule out of Cloudflare's default 200 per domain. The admin docs say so.

Tests

  • Migration: upgrade from the 1.1.11–1.2.1 schema, trigger refusals, older-runtime inserts.
  • Shared recipients: second rule, removed / half-removed row restore, per-name readiness, cron pickup, room and conflict checks, deploy not blocked by the alias.
  • Route: owner only, validation, plan check, taken, Cloudflare conflict, settings.
  • Ingress, admission, proxy router (mailbox compare, reply pins), gateway validation, generated handleEmail prefix strip.
  • CLI: args, help, the command.

Local: typecheck, lint, fmt, vp run test (affected) pass. Browser tests can't run on this machine (TLS proxy blocks the Playwright download).

🤖 Generated with Claude Code

…mail domain

A project owner can run `void email alias <name>` to give the project a
second name on the platform's shared mail domain. The alias is set once and
freed only when the project is deleted. The slug keeps sending and
receiving, so a receiving project with an alias uses two Cloudflare
routing rules.

- projects.email_alias (migration 0062) with a trigger that keeps every
  slug and alias distinct, also for older runtimes after a rollback
- PUT /projects/:id/email/alias: owner only, slug name rules, plan check
  for short names, and a Cloudflare check for conflicts and room before
  saving
- ingress, admission, proxy router and gateway accept either name;
  replies may use either name; deploys only wait for the slug rule
- reconcile restores alias rows an older runtime removed or half removed
- docs for the CLI, sending, receiving, administration and rollback

Fixes #511

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T14:06:54.363580Z b44a000 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1a4673c293

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread platform/packages/api/src/routes/email.ts
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://feat-email-project-alias-void-cloud.voidzero-docs.workers.dev (commit b44a000)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://b17f8ed4-void-cloud.voidzero-docs.workers.dev b44a000 2026-10-22T14:03:25.937Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://01dc4fe7-void-cloud.voidzero-docs.workers.dev 131fbc1 2026-10-22T13:56:14.509Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://27d79476-void-cloud.voidzero-docs.workers.dev 1a4673c 2026-10-22T13:18:20.684Z Visit the dashboard ↗

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 131fbc11a2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread platform/packages/api/src/routes/email.ts
A retry after a lost response used to get 409 although the alias was
saved. The same name now returns the saved state; a different name still
gets 409.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn
Brooooooklyn merged commit 3d8f848 into main Oct 22, 2026
20 checks passed
@Brooooooklyn
Brooooooklyn deleted the feat/email-project-alias branch October 22, 2026 14:39
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.

Platform email: short, unique mail alias per project on the shared sender domain, independent of the project slug

1 participant