A custom domain makes automated email feel professional and controlled, but it also creates a routing problem. Once CI jobs, QA suites, signup verification flows, and LLM agents all start creating addresses on the same domain, a simple catch-all mailbox quickly becomes a source of collisions, stale messages, and hard-to-debug failures.
The scalable approach is to treat routing as a system, not as a string-generation trick. A domain email address should resolve to a known inbox, a known workflow, and a known delivery contract. That matters even more for AI agents, because an agent should not be searching through a shared mailbox or reading raw email content to decide what happened. Your application should route, verify, normalize, and extract before the model sees anything.
This guide breaks down email routing patterns that scale in custom domains, with practical guidance for automation, CI, QA, and agent workflows.
The three layers of scalable custom domain routing
Every reliable custom-domain email setup has three routing layers. If one layer is ambiguous, the entire workflow becomes flaky.
| Layer | What it answers | Typical owner | Common failure when ignored |
|---|---|---|---|
| DNS and MX routing | Which inbound email provider receives mail for this domain or subdomain? | Platform or DevOps | Mail never reaches your automation system |
| Recipient-to-inbox routing | Which inbox owns this exact recipient address? | Application or inbox API | Messages land in the wrong test, run, or agent session |
| Delivery-to-code routing | Which webhook, queue, poller, or worker processes the message? | Backend team | Duplicate processing, missed events, unsafe agent input |
DNS only gets the message to the right mail receiver. It does not tell your test harness which login attempt the message belongs to. Likewise, a webhook only delivers an event to your code. It does not solve recipient identity unless the payload contains stable routing identifiers such as an inbox ID, recipient, message ID, and delivery ID.
For standards context, SMTP routing is ultimately recipient-based, as described in RFC 5321. Application-level determinism has to be added above SMTP by your routing model.

Start with subdomain boundaries, not address formats
Before choosing local-part patterns like [email protected], decide how your domain space should be separated. A dedicated subdomain gives you a clean operational boundary without affecting human email on your root domain.
For automation, prefer subdomains such as qa.example.com, ci.example.com, agents.example.com, or staging-mail.example.com. This lets you change MX records, rotate providers, isolate environments, and apply different retention or security rules without touching employee mail.
A good custom-domain layout usually separates at least three dimensions: environment, ownership, and risk. Production-like staging flows should not share the same routing namespace as experimental agent runs. Customer-facing client operations should not share the same catch-all policy as throwaway CI tests.
| Domain layout | Best fit | Why it scales |
|---|---|---|
ci.example.com |
Parallel automated tests | Easy to expire, reset, and observe without impacting users |
staging-mail.example.com |
Pre-production app testing | Mirrors production flows while staying isolated |
agents.example.com |
LLM and AI agent workflows | Lets you apply stricter safety and retention policies |
tenant-a.test.example.com |
Tenant-specific QA or demos | Reduces cross-tenant routing mistakes |
Mailhook supports custom domain workflows alongside instant shared domains, so teams can prototype quickly and move domain routing into their own namespace when governance, allowlisting, or environment isolation becomes important. For exact integration details, use the Mailhook llms.txt contract as the machine-readable reference.
Pattern 1: Encoded local-parts for stateless routing
Encoded local-parts put routing information directly into the email address before the @ sign. Instead of storing a large alias table, your resolver can decode the local-part and map it to a run, attempt, tenant, or inbox.
A typical encoded address might include a compact version marker, environment, run identifier, and checksum. For example, the conceptual format could be [email protected]. The exact format is up to your application, but the goal is to make addresses deterministic and collision-resistant without exposing secrets.
This pattern works well when you generate many short-lived addresses and want routing to remain fast at high volume. CI systems, batch agent runs, and signup verification tests often benefit from encoded local-parts because every address is self-describing enough for a resolver to recover context.
The trade-off is format discipline. Local-parts have length and character constraints, and many external services have stricter validation than the RFCs allow. Keep addresses lowercase, ASCII, short, and boring. Avoid quoted local-parts, unusual punctuation, and anything that looks like a token or secret.
A practical encoded local-part should follow these rules:
- Include a version prefix so the format can evolve safely.
- Use compact, non-secret identifiers rather than raw database IDs or credentials.
- Add a checksum or signature fragment if you need to reject malformed recipients early.
- Keep the local-part well under SMTP limits and under common SaaS form limits.
- Store the final generated address with the inbox descriptor for debugging.
Encoded local-parts are strongest when paired with a disposable inbox API. The address may be decodable, but your code should still resolve it to an explicit inbox resource rather than searching a shared mailbox.
Pattern 2: Alias tables for explicit control
Alias-table routing stores each recipient address in a database and maps it to an inbox, workflow, or owner. It is more stateful than encoded local-parts, but it gives you precise control over lifecycle, ownership, and revocation.
This pattern is often better for long-lived automation addresses, client operations, tenant-specific workflows, or any flow where humans may need to audit who created an address and why. You can add metadata such as created_by, purpose, tenant_id, expires_at, and allowed_senders.
Alias tables scale when you treat them as a control plane. The mail receiver or inbox service receives the message, your resolver looks up the normalized recipient, and the result determines the inbox ID and delivery destination. This is easy to reason about, easy to revoke, and easy to debug.
The downside is operational complexity. You need uniqueness constraints, cleanup jobs, migration logic, and careful handling of address normalization. If an alias is deleted too early, late verification emails may become unroutable. If it lives forever, stale messages can surprise retries or agents.
| Alias-table design choice | Recommendation |
|---|---|
| Primary key | Normalized full recipient address |
| Routing target | Stable inbox ID, not just a webhook URL |
| Lifecycle | Active, draining, expired, tombstoned |
| Expiry handling | Keep a short drain window for late email arrivals |
| Observability | Log alias ID, inbox ID, message ID, and workflow ID |
For automation, alias-table routing is especially useful when combined with one inbox per attempt. Instead of reusing [email protected], create a new alias or inbox for each attempt, then expire it after the workflow reaches a terminal state.
Pattern 3: Catch-all routing with strict correlation
A catch-all domain accepts mail for any recipient under a domain or subdomain. It is convenient, especially during early development, because you do not have to pre-create every address. But at scale, catch-all routing can become dangerous if every message falls into a shared bucket.
Catch-all is acceptable when it is paired with strict correlation. The recipient address should contain enough information to identify the expected workflow, and the message should still be matched against a narrow set of criteria. Your code should not ask an LLM to inspect a catch-all inbox and choose the right email.
Good catch-all usage looks like this: create or reserve an inbox descriptor, generate a unique recipient address for that descriptor, trigger the external email, then accept only messages sent to that exact recipient within a defined deadline. The catch-all is a routing convenience, not a shared mailbox strategy.
Catch-all is risky when teams use it as a dumping ground. Common failure modes include stale OTP selection, duplicate magic links, cross-run collisions, and hard-to-reproduce CI failures. These problems are amplified under parallel test execution.
Use catch-all routing for prototyping, low-risk test domains, and transitional migrations. For high-volume agent runs or regulated client operations, prefer explicit inbox creation, encoded local-parts with validation, or alias records with lifecycle controls.
Pattern 4: Subdomain-per-environment or tenant
Subdomain routing moves a dimension of identity into DNS. Instead of encoding everything before the @, you can route by subdomain: [email protected], [email protected], or [email protected].
This pattern scales organizationally because it creates clean boundaries. You can apply different MX records, provider settings, webhook destinations, retention rules, and monitoring alerts per subdomain. It is also useful when third-party systems allowlist domains or when a tenant expects mail to come from a recognizable namespace.
The drawback is DNS and configuration overhead. Too many subdomains can become difficult to manage, and DNS changes are not instant. Use subdomains for stable boundaries like environment or tenant class, not for every individual test attempt.
A strong layout is usually hybrid: subdomain for environment, local-part for run or inbox identity. For example, [email protected] keeps DNS simple while preserving per-attempt uniqueness.
Pattern 5: Inbox-first routing for agents and CI
The most reliable pattern is not address-first. It is inbox-first.
In inbox-first routing, your application creates or reserves a disposable inbox, receives an inbox ID and email address, then passes that address into the external workflow. When email arrives, your code routes by inbox ID and message metadata, not by searching a mailbox.
This model is well suited to LLM agents because it gives the agent a small, deterministic tool contract. The agent can ask for an inbox, trigger a workflow, and wait for a message, but your backend remains responsible for signature verification, deduplication, parsing, link validation, and artifact extraction.
A minimal provider-agnostic interface might look like this:
create_inbox(domain, purpose, ttl) -> { inbox_id, email, expires_at }
wait_for_message(inbox_id, matcher, deadline) -> { message_id, received_at, json }
extract_artifact(message_id, policy) -> { type, value }
expire_inbox(inbox_id) -> { status }
Mailhook is designed around this kind of programmable temporary inbox model. It provides disposable inbox creation via API, structured JSON email output, real-time webhooks, polling access, signed payloads, and custom domain support. Use the Mailhook llms.txt file for the exact API and payload contract rather than guessing endpoint details.
Webhook-first delivery, polling fallback
Routing does not end when the message reaches the correct inbox. Your delivery path also has to scale.
For most automation systems, webhooks should be the primary path. They reduce latency, avoid wasteful polling, and let you push each message into a queue or workflow engine as soon as it arrives. Polling remains valuable as a fallback for local development, webhook outages, or reconciliation jobs.
A scalable delivery contract should include idempotency from the start. Webhooks can be retried, network calls can fail after processing, and email itself can arrive more than once. Your handler should verify the webhook payload before doing any work, dedupe by delivery or message identifiers, and then enqueue processing.
| Delivery concern | Scalable pattern |
|---|---|
| Low latency | Use webhook-first delivery |
| Recovery | Add bounded polling fallback by inbox ID |
| Duplicate events | Deduplicate at delivery and message layers |
| Security | Verify signed payloads before parsing or processing |
| Agent safety | Extract minimal artifacts before model handoff |
| Debugging | Store inbox ID, recipient, message ID, and processing state |
Signed webhook verification is especially important for agent workflows. An email may have DKIM or SPF signals at the message layer, but your application also needs to know that the HTTP request delivering the JSON was authentic and not replayed. Mailhook supports signed payloads for this purpose.
Address generation rules that prevent future pain
Many routing failures start with address generation shortcuts. A team begins with random strings, then later discovers collisions, unreadable logs, overlong local-parts, or addresses rejected by third-party signup forms.
Use boring, deterministic conventions. Lowercase ASCII local-parts are more portable than clever RFC edge cases. Keep identifiers compact. Do not put secrets, raw access tokens, customer names, or full prompt context in email addresses. If you need integrity, use a short checksum or server-side alias lookup rather than exposing sensitive data.
A good generated address should answer four questions in logs without leaking sensitive data: which environment created it, which workflow owns it, which inbox receives it, and when it expires. The full answer can live in your database. The address only needs enough structure to route or debug.
For high-volume systems, also reserve room for versioning. A prefix like v1 may feel unnecessary on day one, but it gives you a safe migration path when you later change checksum logic, ID lengths, or routing semantics.
Make routing safe for LLM agents
LLM agents change the threat model. A human tester might ignore a malicious email, but an autonomous toolchain may parse it, summarize it, click a link, or feed it into another tool. Routing must therefore be paired with content minimization.
The safe pattern is to keep routing and security decisions in code. The model should not decide whether a webhook is authentic, whether a link is allowed, or whether a message belongs to the current attempt. Your backend should expose a minimized result such as an OTP, a validated magic link, or a status object.
For agent-facing workflows, apply these guardrails:
- Create one disposable inbox per attempt or task.
- Match only messages sent to the expected inbox or recipient.
- Verify webhook signatures before processing the JSON payload.
- Prefer structured JSON fields over rendered HTML scraping.
- Validate links against an allowlist before any agent can use them.
- Redact tokens and raw message bodies from agent-visible logs.
This keeps the agent focused on workflow progress while your system handles trust boundaries. Mailhook’s structured JSON output and webhook or polling delivery model fit this separation because your code can consume normalized email data before handing a minimal artifact to the model.
Observability: know where a message went
A routing pattern is only scalable if failures are explainable. When a CI run says the verification email never arrived, you need to know whether DNS failed, the recipient was wrong, the inbox expired, the webhook failed, or the parser rejected the message.
Track routing as a state machine. An inbox is created, an address is issued, the external action is triggered, delivery is observed, the message is normalized, an artifact is extracted, and the inbox is expired. Each transition should leave a small, searchable record.
| Signal | Why it matters |
|---|---|
inbox_id |
Connects address creation, delivery, polling, and cleanup |
| Recipient address | Confirms the external service sent to the intended route |
| MX verification status | Separates DNS setup issues from application bugs |
| Webhook delivery ID | Enables retry-safe processing and replay detection |
| Message ID | Helps dedupe duplicate or retried emails |
| Workflow or run ID | Makes CI and agent failures reproducible |
| Expiry state | Explains late arrivals and cleanup behavior |
Do not rely on screenshots of an inbox or manual mailbox login as the primary debugging method. For automated workflows, structured logs and JSON payloads are faster, safer, and easier to attach to CI artifacts.
Choosing the right routing pattern
There is no single best pattern for every custom domain. The right choice depends on volume, lifecycle, audit requirements, and how much operational state you are willing to manage.
| Pattern | Best for | Avoid when |
|---|---|---|
| Encoded local-part | High-volume short-lived CI and agent attempts | You cannot enforce a stable address format |
| Alias table | Audited workflows, tenant mapping, client operations | You want zero state or minimal cleanup work |
| Catch-all with correlation | Prototypes and flexible test domains | Messages share a mailbox without strict matchers |
| Subdomain boundaries | Environment, tenant, or risk isolation | You need per-attempt routing at DNS level |
| Inbox-first API | Deterministic tests and LLM-safe automation | You only need non-routable validation strings |
A pragmatic default for 2026 automation is simple: use a dedicated custom subdomain, create a disposable inbox per attempt, deliver webhooks first with polling fallback, and expose only structured JSON or extracted artifacts to agents. This avoids most shared-mailbox failure modes while keeping domain ownership and routing visible.
Where Mailhook fits
Mailhook provides programmable temporary inboxes for teams that want custom-domain email routing without operating their own inbound mail stack. You can create disposable inboxes through an API, receive emails as structured JSON, use real-time webhooks or polling, verify signed payloads, and support shared or custom domain workflows.
That means your application can focus on routing policy: which domain to use, how to generate addresses, how long inboxes live, which messages match, and what artifact should be exposed to a test or agent. Mailhook handles the inbox and delivery primitives needed to make that policy executable.
For implementation details, payload fields, and agent-readable integration guidance, start with the Mailhook llms.txt reference.
Frequently Asked Questions
What is the best email routing pattern for custom domains in CI? For CI, the most reliable pattern is usually a dedicated subdomain plus one disposable inbox per test attempt. Use webhook-first delivery, polling fallback, narrow message matching, and structured JSON assertions.
Is catch-all routing safe for automation? Catch-all routing can be safe for low-risk automation if every generated recipient is unique and your code matches messages strictly by recipient, inbox, deadline, and workflow. It becomes unsafe when used as a shared mailbox.
Should AI agents read raw email from a custom domain inbox? No. Agents should receive minimized, structured outputs such as an OTP, validated URL, or status object. Your backend should verify webhooks, route messages, parse JSON, and enforce link policies before model handoff.
Do I need a custom domain for disposable inboxes? Not always. Shared domains are useful for fast prototyping. Custom domains are better when you need allowlisting, environment separation, auditability, or stronger control over routing policy.
How do webhooks and polling work together for email routing? Webhooks provide low-latency push delivery. Polling is a bounded fallback for recovery, local development, or reconciliation. Both should route by inbox ID and use deduplication to avoid double processing.
Build custom-domain routing around inboxes, not shared mailboxes
Custom domains become much easier to scale when every address resolves to a short-lived, observable inbox. Instead of letting tests or agents search through shared mail, create an inbox, route a single workflow to it, consume the message as JSON, extract the minimal artifact, and expire it.
If you are building email automation for CI, QA, or LLM agents, Mailhook gives you the core primitives: programmable disposable inboxes, custom domain support, structured JSON emails, webhooks, polling, signed payloads, and batch-friendly workflows. Review the Mailhook integration contract or visit Mailhook to start designing a routing model that stays deterministic as your automation grows.