Numbered MX rotation disposable detection
A simple disposable-mail blocklist looks at the apex domain: mailinator.com, tempmail.com, wuuvo.com. Block on those exact strings. The maintainer adds new strings as new operators show up. The npm disposable-email-domains package works exactly this way. So does most of the public blocklist ecosystem.
The pattern that defeats this approach: numbered MX rotation. An operator stands up a single MX backend (mx.add5000.com), then registers 50 different apex domains (disposable1.com, inboxfreeforever.net, quickmail.xyz, …) and points all 50 of their MX records at numbered variants of the backend (mx1.add5000.com, mx2.add5000.com, mx3.add5000.com, …). The 50 apex domains look unrelated to a blocklist maintainer. The mechanical reality is one operator running one MX server with 50 frontends.
This post documents the 12 parent-domain MX patterns we maintain in production to catch this. All 12 pulled live from our detection table. The same machinery underlies the operator-graph approach to disposable email checker API.
How parent-MX detection works
Tier 2b of our detection stack does one DNS lookup per check, ~50ms, and tests three things:
- Exact match against 302 known operator MX hostnames (
gourmet.spamgourmet.com,park-mx.above.com,smtp.spamex.com, etc.). - Parent-domain match against 12 regex patterns. If the MX is
mx99.add5000.com, the rule looks at the parent —add5000.com— and tests that against the pattern table. - The same TTL/cache treatment as Tier 2a — once we’ve classified an MX hostname, future verifies on domains with the same MX hit the in-memory cache at single-digit microseconds.
The parent-domain test catches numbered MX variants without needing to enumerate every possible MX hostname. mx1.add5000.com, mx7.add5000.com, backupmx.add5000.com — all match the add5000.com pattern. Adding a new variant takes zero work.
The 12 parent-domain patterns
Pulled from the production disposable_mx_patterns table:
| Parent domain | Verdict | Notable operator |
|---|---|---|
| hostedmxserver.com | disposable | infra cluster |
| add5000.com | disposable | budget temp-mail backend |
| yopmail.com | disposable | (legacy yopmail.com cluster) |
| above.com | alias-forwarder | yopmail.co (4 brands, 654 domains) |
| discard.email | disposable | self-described temp-mail brand |
| trashmail.com | disposable | mature temp-mail provider |
| mail-temporaire.fr | disposable | French market temp-mail |
| papierkorb.me | disposable | German market temp-mail |
| guerrillamail.com | disposable | one of the older free providers |
| tempmail.net | disposable | generic-name temp-mail |
| zoho.in | disposable | India-region cluster |
| mailmomy.com | disposable | budget temp-mail backend |
Each pattern matches any MX hostname whose parent domain is the listed value. So mx1.guerrillamail.com, mx2.guerrillamail.com, pop.guerrillamail.com all hit the guerrillamail.com rule. Worth noting: this isn’t *.guerrillamail.com glob matching — it’s parent-domain extraction (strip the subdomain, compare the apex), which is more selective.
Why this beats a static MX list
Three numbers:
- 302 exact MX hostnames in our Tier-2a table. Caught by string match.
- 12 parent-domain patterns in Tier-2b. Catch any subdomain variant.
- Unbounded future subdomains caught by the patterns.
If add5000.com ships a new MX backend tomorrow called mxbackup-emergency-failover.add5000.com, the pattern catches it without us updating anything. If the operator registers a new apex domain and points its MX at the same backend, we catch the new apex on the first verify. That’s the property a static MX list can’t replicate.
Real examples from the patterns
Here’s what each pattern actually catches in production traffic:
add5000.com
A budget temp-mail back-end used by multiple “different” temp-mail brands. Apexes routing through it include domains we’ve seen in customer bounce reports under names like tempmailo.com, mailbase.net, and similar generic-temp-mail branding. The operator pattern is: register apex, point MX at mx1.add5000.com, ship. We catch every variant.
above.com
The Yopmail cluster — covered in depth in our Yopmail empire post. 654 disposable mail domains all routing through Above.com’s domain-parking MX infrastructure. The pattern is the single biggest catcher in the table by sheer domain count.
guerrillamail.com
GuerrillaMail is one of the oldest free temp-mail brands, dating to 2006. They run several variants — guerrillamailblock.com, pokemail.net, spam4.me, etc. — and the MX records all point through guerrillamail.com subdomains. The pattern catches every variant the operator runs without us enumerating them.
trashmail.com
Another mature temp-mail brand. The parent-domain pattern catches the main brand plus the various spinoff apex domains the operator has registered over the years.
mail-temporaire.fr and papierkorb.me
Non-English-market temp-mail. The pattern table is intentionally multilingual — operators in French- and German-speaking markets follow the same numbered-MX pattern as English-market operators, just with different brand names. Same detection, same verdict.
Why static lists keep missing these
Three reasons the static-list approach can’t keep up with numbered-MX operators:
-
Domain count outpaces maintenance. An operator adding their 51st apex registers it, points MX at the existing backend, and ships the same day. A blocklist maintainer typically batches updates monthly. The new apex sees 30 days of free run before any list catches up.
-
Apex names don’t telegraph their relationship.
wuuvo.com,1clck2.com,your-domain.comlook like unrelated random domains. The relationship is in the MX record, which apex-only blocklists don’t track. -
Numbered variants of the same backend look like new infrastructure. If a maintainer adds
mx1.add5000.comto a list, the operator switches tomx2.add5000.comand the maintainer adds that too. The pattern-based approach inverts the cost: we add one pattern that catches all 100 numbered variants, future-proof.
How to spot numbered MX without our detection table
If you’re auditing your own signup form’s traffic and want to detect this pattern by hand, the heuristic is:
# For an address bob@example.com that you suspect:
$ dig +short MX example.com
1 mx7.suspicious-backend.com.
# Strip the subdomain to get the parent:
$ echo mx7.suspicious-backend.com | awk -F. '{print $(NF-1)"."$NF}'
suspicious-backend.com
# Cross-reference against your own list, or our /v1/check API.
If suspicious-backend.com is a known operator MX (or a domain whose entire purpose is hosting mail backends), the address is disposable regardless of how the apex was branded. The full check is what our API does in 50ms; the manual version takes a minute per address.
What this looks like at the verify call
When /v1/check hits a numbered-MX disposable on the Tier-2b path:
{
"result": "undeliverable",
"reason": "disposable",
"reason_message": "This email provider doesn't deliver mail reliably. Please use a real address.",
"disposable": true,
"score": 0.05,
"detection_source": "mx-pattern:add5000.com"
}
The detection_source field carries the rule that fired — useful for debugging or auditing why a specific verdict landed. Tier-2b matches show the pattern name explicitly. Tier-1 (exact apex match) shows the blocklist source. Tier-3 (CIDR-range) shows the operator CIDR.
How patterns get added
A pattern enters the table when we see enough numbered-variant traffic on the same parent backend to justify it. Threshold: ≥5 distinct apex domains routing MX through X.parent.com for X matching a numbered pattern (mx[0-9]+, inbound[0-9]+, srv[0-9]+, etc.). Once the threshold trips, the pattern enters review queue; an operator analyst confirms the cluster isn’t a false positive (legitimate ESP, shared hosting accidentally matching pattern), and the parent rule promotes to production.
The list stays small on purpose — 12 patterns versus 302 exact-match hostnames. We bias toward exact match when possible and reach for the regex approach only when an operator’s infrastructure literally requires it.
FAQ
Why only 12 patterns? Couldn’t you catch more?
We could write a pattern for every operator that runs numbered MX. We don’t, because false-positive risk grows quickly. mx.example.com is a legitimate naming convention for thousands of small businesses’ mail servers. A pattern that catches “any domain with an MX subdomain that has a number in it” would over-block massively. The 12 patterns target operators whose entire MX domain is dedicated to disposable infrastructure.
What happens if a legitimate domain’s MX matches one of these patterns?
Tier 0 wins. Our 380-domain allowlist overrides any disposable claim, so gmail.com returns legitimate even if some weird DNS misconfiguration pointed it at a flagged parent. The allowlist is the safety net.
Can I add my own MX patterns to vrfymail’s table?
Not directly — the table is curated centrally to avoid the false-positive amplification problem. If you’ve identified a numbered-MX cluster we don’t have, reporting it via support gets it into the review queue and typically into production within a week.
Does this work for IPv6 MX records?
Yes. The MX lookup returns hostnames regardless of underlying address family. The parent-domain rule operates on the hostname string, not on the resolved IP. IPv6-only operators are rare today but the detection is forward-compatible.
What about ESPs that legitimately offer multi-tenant MX (Cloudflare, Mailgun, etc.)?
Those don’t trigger the patterns because we never add ESP-shared infrastructure to the table — that would be a massive false-positive risk. The Tier-3 IP-range table has explicit is_cdn=1 flags for known CDN/ESP ranges to keep them out of matching even if an operator’s MX A-record falls into the range.
Catch every numbered-MX variant in one API call
Tier-2b pattern matching runs at ~50ms p50, parallel with the disposable database lookup. Free tier: 5,000 verifies/month, no card. Get an API key →