Skip to content
Engineering

Address Routing for Inbound Email: Keys, Aliases, Catch-All

| | 10 min read
Address Routing for Inbound Email: Keys, Aliases, Catch-All
Address Routing for Inbound Email: Keys, Aliases, Catch-All

Inbound email looks deceptively simple: point MX records at a provider, then wait for messages. In practice, the hard part is address routing: deciding which logical inbox should receive a message when the only thing you get from SMTP is a recipient address.

When you are building verification flows, QA automation, or LLM-driven agents, routing mistakes show up as flakes, collisions, “wrong email in the wrong run,” and painful debugging. The good news is that most reliable systems converge on three routing strategies that scale:

  • Keys: encode routing data in the local-part (stateless routing)
  • Aliases: store an explicit recipient-to-inbox mapping table (stateful routing)
  • Catch-all: accept any recipient at a domain and route via correlation rules (flexible but dangerous without guardrails)

This guide explains how each works, when to use it, and the implementation details that keep routing deterministic.

What “address routing” really means (and where it breaks)

Inbound email routing has three layers:

  1. DNS routing (MX): which server receives email for a domain.
  2. SMTP routing: which recipient the SMTP transaction says the message is for.
  3. Application routing: how your system maps that recipient to a logical inbox, workflow run, or tenant.

Most automation bugs happen in layers 2 and 3.

Envelope recipient vs the To: header

For routing, the most reliable signal is the SMTP envelope recipient (the address used in the RCPT TO command), not the To: header. The To: header can be missing, rewritten, or misleading (for example, Bcc). If your inbound provider exposes envelope recipients, prefer them.

If you want the standards background, see RFC 5321 (SMTP) and RFC 5322 (message format).

Why routing is harder for CI and agents

Automation introduces constraints that human inboxes do not have:

  • Parallelism: many runs at once, often across branches and environments.
  • Retries: duplicate sends and duplicate deliveries are normal.
  • Determinism: you must reliably pick the “right” message for a given attempt.
  • Safety: inbound email is untrusted input, especially if an LLM will read it.

So routing is not just “where should the email go,” it is “how do I guarantee the email goes to the correct isolated container every time.”

The routing goals: a quick checklist

A routing scheme is “good” if it is:

  • Collision-resistant: two concurrent attempts do not land in the same inbox.
  • Deterministic: the same address always maps to the same inbox (within an intended TTL).
  • Debuggable: you can explain and reproduce why a message landed where it did.
  • Safe by default: rejects unexpected recipients instead of silently accepting them.
  • Operationally cheap: does not require constant manual cleanup or brittle state.

The rest of this article evaluates keys, aliases, and catch-all against these goals.

Strategy 1: Keys (encoded local-parts)

Keys route by embedding a routing key inside the local-part, for example:

Your router parses the local-part and maps it to an inbox (or directly to a workflow identifier).

Why keys are the default for automation

Keys are the closest thing to “stateless routing.” If the local-part contains everything you need, you can route without a database lookup.

Practical benefits:

  • Great for CI parallelism and per-attempt isolation.
  • Easy to replay and debug: the address itself explains the routing.
  • Scales well: routing is just parsing and validation.

Design rules for key-based routing

A key format that survives real-world email systems should follow a few rules:

  • Keep it short: some systems have recipient length limits.
  • Use a safe character set: letters, digits, and a small set of delimiters (-, _, .). Avoid exotic characters.
  • Version your format: prefix with v1, so you can evolve parsing rules.
  • Include an integrity check: a checksum or HMAC-like tag lets you reject random garbage instead of creating accidental routes.
  • Treat parsing as untrusted input: do not “best-effort” parse. Fail closed.

Simple diagram of encoded local-part routing: an email address local-part contains segments like v1, tenant, and attempt token; a router validates the pattern and resolves it to an inbox_id.

A minimal “key resolver” contract

Even if you use a provider, you still benefit from writing down a small internal interface:

  • Input: envelope recipient address
  • Output: routing_result (inbox handle/id, tenant, environment, correlation token)
  • Behavior: strict validation, explicit rejection reasons

This contract becomes the foundation for routing tests.

Strategy 2: Aliases (recipient mapping tables)

Aliases route by storing an explicit mapping from a recipient address to an inbox.

Example:

When aliases are the right tool

Aliases are ideal when the address must be stable or human-friendly:

  • A partner requires allowlisting a specific address.
  • You need a long-lived operational inbox (still consumed by code).
  • You are migrating from a legacy address scheme.

The hidden cost: state and lifecycle

Alias routing is stateful, which introduces operational requirements:

  • Provisioning: who creates aliases, when, and how.
  • Cleanup: how you delete expired aliases to prevent buildup.
  • Concurrency control: avoid accidentally mapping the same alias to multiple inboxes.
  • Auditability: log “alias changed from X to Y” so debugging is possible.

If you use aliases for automation runs, you typically still want a second layer of correlation (for example, an attempt token in the subject or a custom header) because a stable alias can receive multiple runs’ emails.

Strategy 3: Catch-all (accept-any recipient)

A catch-all domain accepts any recipient at a domain (or subdomain). Routing happens after receipt.

Catch-all is attractive because it looks like zero setup: you can mint any address on the fly.

Catch-all failure modes

Catch-all has predictable problems:

  • Noise and abuse: random recipients generate random traffic.
  • Ambiguity: if you do not enforce patterns, multiple workflows can overlap.
  • Hard debugging: “why did this address work yesterday but not today?” becomes common.

Making catch-all safe enough for automation

If you must use catch-all, constrain it aggressively:

  • Only accept recipients matching a strict pattern (for example, ^run_[A-Z0-9]{10}$).
  • Require a correlation token that you can validate (checksum or signature).
  • Prefer dedicated subdomains per environment to avoid cross-talk.
  • Add rate limits and explicit rejection metrics.

A good mental model is: catch-all is acceptable when it behaves like “keys,” just without the up-front provisioning step.

Choosing between keys, aliases, and catch-all

Here is a practical comparison for inbound email automation.

Strategy How it routes Operational burden Best for Common pitfall
Keys Parse local-part into routing data Low CI, retries, per-attempt isolation Weak validation creates “ghost” inboxes
Aliases DB table mapping recipient → inbox Medium to high Allowlisting, stable addresses, migrations Stale mappings and cleanup debt
Catch-all Accept any recipient, route after receipt Medium Rapid prototyping, flexible address minting Noise, abuse, non-determinism without strict patterns

If you are building for QA suites or LLM agents, keys are usually the default, aliases are for compatibility constraints, and catch-all is a last resort that needs strict guardrails.

A reference routing pipeline (implementation-oriented)

Most production-grade routers implement the same stages.

1) Normalize the recipient

Normalization prevents subtle mismatches:

  • Lowercase the domain part.
  • Normalize whitespace and angle-bracket forms if you ingest header recipients.
  • Be conservative about local-part casing. In theory it can be case-sensitive, in practice most systems treat it as case-insensitive. Pick a policy and stick to it.

If you can, route on the envelope recipient rather than parsing headers.

2) Validate against an allow-policy

Before you resolve anything, decide what your system will accept:

  • Allowed domains and environments.
  • Allowed patterns for local-parts.
  • Maximum lengths.

This is where catch-all setups either become safe or become a liability.

3) Resolve recipient to inbox

Resolution depends on your strategy:

  • Keys: parse and validate, then compute inbox routing.
  • Aliases: lookup recipient in mapping table.
  • Catch-all: apply strict pattern rules, then route like keys.

4) Emit a routing record

Store enough to debug later:

  • Envelope recipient
  • Routing strategy used (key, alias, catch-all)
  • The resolved inbox identifier
  • A correlation token (attempt/run id)
  • A stable message or delivery identifier (from your provider)

This record is often more valuable than the email body when debugging flaky tests.

Architecture sketch of inbound email routing: DNS MX routes mail to an inbound provider, provider normalizes to JSON, router resolves recipient using keys/aliases/catch-all, then delivers to a webhook endpoint with polling as fallback.

Routing in LLM and agent pipelines: extra guardrails

When an LLM agent consumes inbound email, routing correctness is only step one. The bigger risk is letting untrusted content influence tool calls.

Recommended guardrails:

  • Minimize what the model sees: provide only the fields needed for the task (for example, extracted OTP or a single verification URL), not the entire HTML.
  • Treat sender-claimed fields as untrusted: From, To, subject, and body can be manipulated.
  • Verify webhook authenticity: if your provider posts messages to your webhook, verify signatures and reject replays.
  • Constrain link handling: validate domains, prevent open redirects, and protect against SSRF.

Mailhook is designed around these automation and agent constraints by delivering inbound email as structured JSON and supporting signed webhook payloads.

Where Mailhook fits (without locking you into one routing style)

If you want routing that behaves well in CI and agent workflows, the simplest approach is to combine:

  • A programmable inbox primitive (so each attempt can be isolated)
  • A routing scheme (keys, aliases, or constrained catch-all)
  • Deterministic delivery to code (webhook-first, polling fallback)

Mailhook provides:

  • Disposable inbox creation via API
  • Real-time webhook notifications and a polling API
  • Structured JSON email output
  • Shared domains and custom domain support
  • Signed payloads and batch email processing

For exact API details and the canonical integration contract, use Mailhook’s published spec: llms.txt.

Frequently Asked Questions

What is address routing for inbound email? Address routing is the logic that maps an inbound recipient address (ideally the SMTP envelope recipient) to a logical inbox, tenant, or workflow run in your system.

Which routing strategy is best for CI and QA automation? Keys (encoded local-parts) are usually best because they are deterministic, low-ops, and scale to parallel runs without relying on shared mailbox state.

When should I use alias-based routing instead of keys? Use aliases when you need stable, human-readable addresses, especially for allowlisting requirements or migrations from legacy systems.

Is catch-all routing safe? It can be, but only if you enforce strict recipient patterns, validate correlation tokens, and fail closed. Unconstrained catch-all routing is a common source of noise and misrouting.

Do I need the To: header to route emails? Prefer the SMTP envelope recipient. The To: header can be missing or misleading (for example, Bcc), so it is not a reliable routing source by itself.

Build deterministic inbound routing (and make it agent-friendly)

If your inbound email routing is causing flakes, collisions, or “wrong run” failures, treat routing as a first-class part of your automation stack: pick a strategy (keys, aliases, or constrained catch-all), validate aggressively, and design for deterministic consumption.

To implement this with programmable disposable inboxes and machine-readable messages, explore Mailhook and use the canonical spec at llms.txt to integrate safely and correctly.

Related Articles