If you need to create email domain infrastructure for testing, the goal is not “run your own mail server.” It is to make inbound email routable, isolated, and automatable so CI, QA suites, and LLM agents can receive verification links, OTPs, and notifications deterministically.
The two levers that matter most are:
- A dedicated subdomain (so testing email never contaminates production).
- MX records (so the internet knows where to deliver mail for that subdomain).
This guide explains subdomain layouts and MX basics, with practical DNS patterns you can apply whether you use a hosted inbound provider or an internal ingestion service.
Start with the right mental model: domain, subdomain, and “who receives mail?”
Email routing is domain-based. When someone sends mail to [email protected], the sender’s mail server asks DNS:
- “What are the MX records for
your-subdomain.example.com?” - “Which mail server(s) should I connect to (and in what priority order)?”
The important implication: you can create an email domain for testing without changing anything about your production domain by using a subdomain.
Why a subdomain is the default choice for testing
Using a subdomain like test.example.com or mail.test.example.com gives you:
- Isolation: reputation, allowlists, and mistakes stay in the test boundary.
- Clean ownership: you can delegate DNS changes without touching production records.
- Environment separation: staging vs preview vs CI can be separate zones under one parent domain.
Apex-domain MX (for example.com) can be high-risk because it changes where real company mail is delivered. Subdomains avoid that.
Subdomain patterns that scale in CI and agent workflows
A good layout makes it obvious which environment an address belongs to, and reduces accidents (like pointing a staging app at production email).
Pattern A: One subdomain per environment
This is the most common setup:
| Environment | Suggested subdomain | Example address |
|---|---|---|
| CI (ephemeral) | ci-mail.example.com |
[email protected] |
| Staging | staging-mail.example.com |
[email protected] |
| Preview apps | preview-mail.example.com |
[email protected] |
Benefits:
- Clear environment boundary
- Easy allowlisting for third-party SaaS (“allow
*@staging-mail.example.com”) - Simple DNS management
Pattern B: Delegate the entire test subdomain
If your DNS provider supports it, you can delegate mail.example.com or test.example.com to a separate DNS zone managed by your team, without giving broad access to production DNS.
This is useful in larger orgs where platform/QA owns test infra and IT owns the parent zone.
Pattern C: “Subdomain per tenant” (only if you really need it)
Sometimes you need subdomain-level separation for customer-like simulations:
customer-a.test-mail.example.comcustomer-b.test-mail.example.com
This can work, but it increases DNS complexity. Most teams are better off using one environment subdomain and isolating at the inbox level (inbox-per-run, inbox-per-attempt) rather than multiplying DNS entries.
MX basics: what an MX record does (and what it does not)
An MX record answers a single question: which hostnames accept SMTP for this domain?
- MX points to hostnames, not IP addresses.
- Those hostnames then resolve via A/AAAA records.
- Mail senders try the lowest priority number first (higher preference).
From the perspective of testing workflows, the main thing you need is: the domain is routable and reliably received.
MX priority and multiple records
You might see multiple MX records like:
- Priority 10:
mx1.provider.net - Priority 20:
mx2.provider.net
That means:
- Try
mx1first. - If
mx1fails, trymx2.
For testing, multiple MX records can improve availability, but your provider decides what’s correct. Do not invent your own redundancy unless you operate your own infrastructure.
“Do I need SPF/DKIM/DMARC for inbound testing?”
Usually, no, not for receiving.
- MX is required for inbound.
- SPF/DKIM/DMARC mainly affect outbound sending and how recipients validate mail claiming to be from your domain.
If your testing strategy includes sending emails from your subdomain to real inboxes (deliverability tests), then SPF/DKIM/DMARC become relevant. For pure inbound verification (your app sends mail via its existing provider and you only need a controlled inbox), MX is the key DNS record.
Here’s a quick reference:
| Record | Typical purpose | Needed for inbound-only test domain? |
|---|---|---|
| MX | Routes inbound mail | Yes |
| TXT (SPF) | Declares authorized outbound senders | Usually no |
| TXT (DKIM) | Cryptographic signing for outbound mail | Usually no |
| TXT (DMARC) | Policy for SPF/DKIM alignment | Usually no |
Step-by-step: create an email domain for testing with a subdomain
This is provider-agnostic. Your inbound provider (or your own SMTP ingestion service) will give you the exact values.
1) Pick a subdomain that will never be confused with production
Good examples:
ci-mail.example.comtest-mail.example.comstaging-mail.example.com
Avoid:
-
mail.example.comif humans might assume it is corporate mail -
example-test.comif it could be mistaken for a real product domain
2) Add MX record(s) for that subdomain
In your DNS provider, create MX records for the subdomain.
Important details to get right:
- Ensure the MX is attached to the subdomain, not the apex domain.
- Use the exact priority and hostname(s) provided by the inbound service.
- Remove default placeholder MX records if your DNS provider adds them.
3) Wait for DNS propagation, then verify with dig
You do not need to guess whether DNS is live. Check it.
# Replace with your actual subdomain
dig MX ci-mail.example.com +short
You should see the provider’s MX hostnames returned.
If you get nothing back:
- You likely created the record at the wrong name (common mistake).
- Or your DNS changes have not propagated yet.
4) Send a test message and confirm receipt
At this point the internet knows where to deliver mail, but you still need an end-to-end check.
A minimal test is: send an email to a new address under that subdomain and confirm the inbound provider receives it.
For automation and agent workflows, the best practice is to consume mail as structured data (JSON) rather than scraping HTML.

Common MX and subdomain pitfalls (and how to avoid flaky tests)
Pitfall 1: Confusing the SMTP envelope with the visible “To:” header
In SMTP, the true routing destination is the envelope recipient (RFC 5321), not what shows up in the message headers. Forwarders and mailing systems can rewrite headers.
Practical takeaway for tests:
- Filter and correlate based on stable identifiers (inbox handle, message IDs, correlation tokens), not just “To:” rendering.
Pitfall 2: Catch-all domains without isolation
A catch-all setup (where any local-part is accepted) can be convenient, but it is also an easy way to create collisions in parallel CI runs.
If you use catch-all addressing, pair it with deterministic isolation, for example:
- One inbox per run/attempt
- Narrow matchers for message selection
- Explicit time budgets (avoid fixed sleeps)
Pitfall 3: Treating “email received” as a single event
Email delivery frequently involves retries and duplicates (greylisting, webhook retries, pipeline retries). In automation, you need idempotency.
A reliable harness should assume:
- You might receive the same message more than once.
- You might receive multiple similar messages (resends).
Pitfall 4: Overexposing raw HTML to LLM agents
If LLM agents consume inbound email, treat it as untrusted input. Emails can contain:
- Malicious links (open redirects, SSRF targets)
- Prompt injection attempts in the body
- Tracking pixels and external resources
Prefer to extract minimal artifacts (OTP or verification URL) from a normalized representation, and keep the agent interface constrained.
Choosing an inbound approach: DIY SMTP vs inbound API provider
You can point MX records to:
- Your own SMTP receiver (Postfix, Haraka, custom server)
- A hosted inbound provider
For modern CI and LLM agent workflows, the differentiator is not “can it receive SMTP?” but:
- Can you provision isolated inboxes programmatically?
- Can you receive messages as structured JSON?
- Can you get real-time events (webhooks) with a polling fallback?
- Can you verify authenticity (signed payloads) and handle duplicates?
If you want those properties out of the box, an inbound API provider is usually the fastest path.
How Mailhook fits: programmable inboxes + custom domains
Mailhook provides programmable, disposable inboxes via API and delivers received email as structured JSON. It supports both shared domains and custom domain support (including subdomains you control), plus real-time webhooks, polling APIs, and signed payloads.
If you want to create a testing subdomain and point MX records correctly, use the canonical Mailhook integration reference here: llms.txt. It is the best place to find the exact current setup requirements (including any DNS and API details) without relying on outdated snippets.
You can also start from the product overview at Mailhook if you are evaluating whether an inbox API is a better fit than managing SMTP infrastructure.
A practical “done” definition for your test email domain
Before you call it complete, validate these outcomes:
- The subdomain’s MX records resolve correctly from the public internet.
- You can send a message to a fresh address under the subdomain and it is received.
- Your automation can wait deterministically (webhook-first, polling fallback).
- Your harness deduplicates and is safe to retry.
- Your agent or CI only consumes the minimal artifacts it needs, not raw untrusted HTML.
When those are true, you have not only created an email domain for testing, you have created one that will stay reliable under parallel CI and autonomous agents.