If you are building automation that “waits for an email,” the hard part is rarely parsing HTML. The hard part is routing: how a message addressed to something@your-domain deterministically lands in the right API inbox, every time, under retries, concurrency, and weird SMTP edge cases.
This guide breaks down how domains and emails interact in API inbox products, what actually happens during SMTP delivery, and the routing patterns that make inbox-based workflows reliable for AI agents and QA.
For Mailhook-specific request/response shapes and the canonical contract, reference the project’s llms.txt.
Email routing 101: the domain controls delivery
An email address has two parts:
-
Local part: everything before
@(for examplesignup-test-42) -
Domain: everything after
@(for exampleexample.net)
When a sender transmits a message, the domain determines where the email is delivered on the internet, via DNS.
MX records decide which server receives mail
For inbound email, sending mail servers look up MX records for the recipient domain. MX records point to mail exchangers (hostnames) that accept mail for that domain.
- If
yourdomain.comhas MX records pointing at an API inbox provider, messages to[email protected]will be delivered to that provider. - If there are no MX records, many senders fall back to the domain’s A/AAAA record (behavior defined in SMTP standards and implemented by MTAs), but you should not rely on fallback behavior for production routing.
If you want the standards context, see RFC 5321 (SMTP) for how SMTP delivers to domains and how the “envelope” works.
Envelope recipients matter more than headers
SMTP delivery is based on the envelope (the SMTP transaction), not on what the email “says” in its visible headers.
Two fields are commonly confused:
-
Envelope recipient: what was provided to the SMTP server in
RCPT TO:<recipient@domain> - To header: what is inside the message headers, which can include multiple addresses, display names, or even be absent in some automated messages
For automation, routing is almost always keyed off the envelope recipient (or a normalized equivalent), because it is the most direct representation of “which address received this message.”
How routing works in API inboxes
Once the provider receives the message for a domain it controls, it needs to answer a simple question:
Which inbox resource should this email be stored under?
That’s where “API inbox routing” differs from consumer email. In consumer email, routing ends at a mailbox. In an automation-first inbox API, routing ends at a programmable inbox handle (often exposed as an inbox ID) that your code can use to fetch the message as JSON, subscribe to webhook events, or batch-process messages.
The three routing layers
Most API inbox systems can be understood as three layers:
- Domain-level routing (DNS): gets the message to the provider
-
Recipient-to-inbox mapping (application logic): maps
local-part@domainto an inbox resource - Delivery to your code (API/webhooks): exposes the stored message to your systems
Mailhook is designed around this automation model, providing API-created disposable inboxes, normalized JSON output, webhook notifications, and polling access (see the llms.txt contract for exact endpoints and payload shapes).
💡 Skip the SMTP infrastructure and get straight to reliable routing
Instead of building your own mail servers and routing logic, start with Mailhook’s programmable inboxes that handle envelope normalization, JSON conversion, and webhook delivery automatically. Start building with the API →

Common recipient-to-inbox mapping patterns
There are a few proven patterns for mapping an incoming recipient address to an inbox. The right choice depends on whether your product wants stable addresses, rotating addresses, or multiple addresses per inbox.
Pattern A: “Encoded inbox key” in the local part
The recipient local part directly encodes a routing key, such as an inbox ID or a short token that can be looked up.
Conceptually:
- You create an inbox via API
- The API returns an email address like
[email protected] - When mail arrives for
[email protected], the provider decodes or looks upk9f3p2and routes the message to inboxk9f3p2
Why it’s popular:
- Fast routing with minimal database lookups (depending on implementation)
- Addresses can be generated safely and uniquely
- Works well for “one inbox per run” automation
What to watch:
- If your local part is treated as an identifier, you want it to be unambiguous under normalization rules (for example, case folding, whitespace trimming, plus addressing). Providers typically define strict rules here.
Pattern B: Alias table (address registry)
The provider stores a mapping table from full email address (or normalized recipient) to an inbox.
Why it’s popular:
- Supports multiple addresses per inbox
- Supports “rename,” “rotate,” or “attach new address” behaviors without changing the inbox handle
What to watch:
- Mapping tables must be indexed and resilient under concurrency
- You need a clear normalization policy for what counts as “the same address”
Pattern C: Catch-all domain with per-message tagging
A catch-all domain accepts any local part and routes messages based on additional metadata (for example, a token added by your app in a header, or a structured part of the recipient).
Why it’s used:
- Useful for certain ingestion workflows (for example, “intake@…” style addresses)
What to watch:
- Correlation shifts from “address uniquely identifies inbox” to “message contains a correlator,” which can be less deterministic unless you control the sender.
Shared domains vs custom domains: what changes in routing
API inbox providers usually offer two domain strategies:
- Shared domains: the provider owns and operates domains you can use immediately
- Custom domains: you route a domain you control to the provider (usually by configuring MX records)
Mailhook supports shared domains and custom domain setups (again, see llms.txt for the authoritative capabilities and integration details).
A practical decision matrix
| Choice | What you control | Typical fit | Operational tradeoffs |
|---|---|---|---|
| Shared domain | Just the inbox lifecycle and retrieval | CI tests, QA automation, short-lived agent tasks | Fastest setup, but domain reputation and deliverability constraints are shared with other tenants in many systems |
| Custom domain | DNS for inbound mail (MX), plus your domain reputation | Production-like staging, branded flows, stricter deliverability requirements | More setup, but clearer domain ownership boundaries and fewer surprises with allowlists |
Notes:
- Deliverability is complex and depends on sender behavior, receiver policy, and domain reputation. Routing a custom domain mainly gives you control, not a guarantee.
- For testing, shared domains are often ideal because they reduce setup friction and keep environments disposable.
What actually happens after the provider receives the message
Once SMTP delivery completes, most automation-first inbox APIs run an ingestion pipeline that looks like this:
- Accept SMTP for a recipient on a managed domain
- Normalize and parse the message (headers, MIME structure, text and HTML bodies, attachments)
- Store the message and index it under an inbox handle
- Notify downstream consumers (webhook events), and/or expose it via a retrieval API
Mailhook’s model (at a high level) is: receive inbound email, convert it into structured JSON, then make it available via webhooks and a polling API, with signed payloads to help you validate authenticity.
Webhooks vs polling is not routing, but it affects “wait semantics”
Routing decides which inbox gets the message. Webhooks and polling decide how your code learns the message arrived.
In automation, the most robust approach is usually:
- Webhook-first for low latency and event-driven flow
- Polling fallback to recover from transient webhook failures, deploys, or network issues
This is where signed webhook payloads matter: the inbox provider is telling you “a message for inbox X exists,” so your receiver should verify signatures before treating the event as real.
If you want a deeper design discussion, RFCs define email transport and message format (for example RFC 5322 for message headers and body structure), but webhook delivery semantics are application-level.
Routing failure modes you should design for
Even with perfect DNS, routing can fail at the application layer in ways that make tests flaky or agents unreliable.
1) Collisions in shared inboxes (the “wrong message” problem)
If you reuse the same recipient address across parallel runs, you can get:
- Multiple matching messages
- An old OTP reused by accident
- A message from a different run that happens to satisfy a loose matcher
The fix is routing design: create an isolated inbox per run (or per attempt), then route to it using a unique address that maps to that inbox.
2) Ambiguous normalization rules
Some systems treat User@domain and user@domain as the same recipient. Others do not. Some treat plus addressing as a distinct local part, others strip it.
For deterministic automation, you want a provider (or internal policy) that is explicit about:
- Case handling
- Plus addressing behavior
- Allowed character set for local parts
- Maximum local-part length
If you are generating addresses programmatically, keep local parts conservative: lowercase, unambiguous, and free of characters that could be transformed by intermediate systems.
3) Multiple recipients and forwarding
A single email can have multiple visible recipients (in To:/Cc:) and can also be forwarded or re-sent.
For routing correctness in an API inbox, prefer using:
- The envelope recipient (the actual address on your domain that received the email)
- A narrow inbox scope (inbox-per-run)
- A message matcher that includes at least one stable criterion you control (for example, a correlation token inserted by your app)
4) Retries, duplicates, and out-of-order delivery
SMTP senders retry. Your app may send multiple verification emails. Users or test runners may click resend. Providers may deliver webhook events more than once.
Routing gets the message into the correct inbox, but your consumer must still be robust:
- Treat webhook events as “at least once” unless documented otherwise
- Deduplicate using stable message identifiers in the JSON payload
- Prefer explicit “wait for a message matching X within time budget” rather than fixed sleeps
Agent-friendly routing: treat the inbox handle as the source of truth
For LLM agents, the best reliability upgrade is to stop thinking “the email address is the identifier” and instead think:
- The inbox handle (inbox ID) is the identifier
- The email address is just a routable pointer that delivers mail into that inbox
This avoids a common anti-pattern: searching a shared mailbox for “the latest email” and hoping it belongs to your run.
A minimal interface that keeps agents safe
When you expose email to agents, keep the tool surface area small and deterministic. A practical pattern looks like:
-
create_inbox()returns an inbox handle and a routable address -
wait_for_message(inbox_id, matcher, timeout)blocks until a match arrives -
get_message(inbox_id, message_id)fetches the normalized JSON
Then keep the LLM focused on only what it must extract (OTP, magic link), not on rendering HTML or exploring raw content.
Mailhook is built around these primitives (disposable inbox creation, JSON output, webhooks, polling, signed payloads), so you can implement this style of “email as a tool” without running your own SMTP stack. For exact fields and endpoints, use Mailhook’s llms.txt.
💡 Give your AI agents deterministic email tools
Implement the inbox-per-run pattern with Mailhook’s programmable disposable inboxes. Your agents get clean JSON messages, webhook notifications, and isolated routing—no shared mailbox collisions. See the AI agent integration guide →
A routing checklist before you ship
Use this as a sanity check when you wire domains and emails into an automation harness:
- Decide domain strategy: shared domain for speed, custom domain for control.
- Ensure DNS routing is correct: MX records for your custom domain point where you expect.
- Use inbox-per-run (or inbox-per-attempt) to eliminate cross-talk.
- Treat envelope recipient as the routing key, not the
To:header. - Verify webhook signatures before acting on events.
- Implement polling fallback so a missed webhook does not break a run.
- Deduplicate messages and write matchers that are narrow enough to avoid false positives.
- Keep retention and logging minimal, email often contains sensitive data.
Where Mailhook fits
If you want to build this routing model without operating mail servers, Mailhook provides programmable disposable inboxes via API, delivers received emails as structured JSON, and supports real-time webhooks plus a polling API, with signed payloads for security and batch email processing.
To integrate without guessing, start with the canonical spec: Mailhook llms.txt. You can also explore the product at Mailhook.