Dev.to Security πŸ” Cybersecurity πŸ‘ 0 πŸ“– 4 min read

I ran MX lookups on all 9,963 disposable email domains. 78% would pass your signup form.

In my MCP server tutorial I tested a simple email check (syntax plus MX record) against a random sample of 500 known disposable email domains. 68.4% passed. The obvious next question: does that hold for the whole list?

In my MCP server tutorial I tested a simple email check (syntax plus MX record) against a random sample of 500 known disposable email domains. 68.4% passed.

The obvious next question: does that hold for the whole list? So I ran it on all of them. Same result, plus a couple of things a sample couldn't show.

Full write-up with charts: We checked 9,963 disposable email domains on the MailValid blog.

The setup

The source is disposable-email-domains, an open-source (CC0) blocklist on GitHub. On 29 October 2026 it had 9,963 domains (latest commit 27 Oct).

For each domain I asked one question: does DNS say this domain can receive mail? That's exactly what a typical signup-form MX check asks.

The whole thing is dig and a few lines of Python. It takes about 3 minutes with 64 parallel lookups.

# 1. Get the list
curl -sO https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/main/disposable_email_blocklist.conf

# 2. One MX lookup per domain: domain <TAB> DNS status <TAB> MX records
cat > mx.sh <<'EOF'
#!/bin/bash
out=$(dig +time=3 +tries=2 +noall +comments +answer MX "$1" @1.1.1.1 2>&1)
st=$(echo "$out" | grep -o 'status: [A-Z]*' | head -1 | cut -d' ' -f2)
mx=$(echo "$out" | awk '$4=="MX"{print $5":"$6}' | sort -n | paste -sd,)
printf '%s\t%s\t%s\n' "$1" "${st:-TIMEOUT}" "$mx"
EOF
chmod +x mx.sh
grep -v '^\s*$' disposable_email_blocklist.conf | xargs -P 64 -n1 ./mx.sh > mx.tsv

Then classify each row:

import collections, subprocess

def has_a_record(domain):
    out = subprocess.run(["dig", "+short", "A", domain, "@1.1.1.1"],
                         capture_output=True, text=True).stdout
    return any(line[0].isdigit() for line in out.split())

counts = collections.Counter()
for line in open("mx.tsv"):
    domain, status, mx = line.rstrip("\n").split("\t")
    if status == "NXDOMAIN":
        counts["domain doesn't exist"] += 1
    elif status != "NOERROR":
        counts["lookup failed"] += 1
    elif mx == "0:.":
        counts["null MX (refuses mail)"] += 1
    elif mx:
        counts["has MX"] += 1
    elif has_a_record(domain):
        counts["no MX, A record fallback"] += 1
    else:
        counts["no MX, no A"] += 1

total = sum(counts.values())
for k, v in counts.most_common():
    print(f"{k:28} {v:6,} {v/total:6.1%}")

Two details matter here:

  • Null MX. A domain can publish MX 0 . (RFC 7505) to say "I never accept mail". A naive len(mx_records) > 0 check treats that as valid.
  • A record fallback. If a domain has no MX record, mail servers fall back to its A record (RFC 5321, section 5.1). So "no MX" doesn't mean "can't receive mail".

The results

DNS result Domains Share
Has an MX record 6,896 69.2%
No MX, A record fallback 894 9.0%
Can receive mail per DNS 7,790 78.2%
Domain doesn't exist (NXDOMAIN) 1,740 17.5%
No MX, no A 203 2.0%
Null MX 128 1.3%
Lookup failed 102 1.0%

69.2% with MX, against 68.4% in the 500-domain sample. The sample held up.

The interesting part is what the MX check does reject. Mostly it's domains that no longer exist at all. The disposable services that are still running pass, because receiving mail is their entire job.

Finding 1: seven servers handle half of them

Next I took each domain's primary MX host (lowest preference value) and resolved it to an IPv4 address.

The 6,833 domains that resolved share just 640 IP addresses. Seven of those IPs receive mail for 3,436 domains, 50.3%. The busiest single server handles 1,104 domains.

That's the useful signal for validation. Disposable services register domains cheaply and rotate them constantly, but running mail servers is more work, so they reuse them. A domain registered yesterday won't be on any blocklist, but if its MX resolves to a server that already serves hundreds of known disposable domains, you know what it is.

One caveat: 308 disposable domains route mail through Cloudflare Email Routing and 82 through Google. You obviously can't block those mail servers, so for that slice you still need the domain list.

Finding 2: new domains are live, and look legitimate

I'd downloaded the same list on 1 October for the sample, so I could diff the two:

comm -13 <(sort -u old.conf) <(sort -u disposable_email_blocklist.conf) > added.txt

776 domains were added in those four weeks. 766 of them (98.7%) had a working MX record. New disposable domains aren't dead weight; they're in active use.

648 of the 776 arrived in a single commit on 12 October. Their names read like small local businesses, mostly air-conditioning and HVAC repair, and they all receive mail on the same small network. I'm not naming them, since I haven't independently confirmed each one, but you'd never flag them by eye.

I pulled registration dates over RDAP for a random sample of 40 (39 answered). 34 were registered in August 2026, 28 of them on 13 August. They reached the blocklist on 12 October. That's roughly 60 days in which a blocklist-only check would have accepted them.

What I'd do in a signup form

Don't treat MX as a disposable check. It catches typos and dead domains. That's still worth doing, but it's a different job.

Keep the blocklist, but don't rely on it alone. It lags new domains by weeks.

Check where the mail goes. Resolve the MX host and compare it against infrastructure you already know serves disposable domains. That catches domains no list has named yet.

Handle null MX and A fallback correctly if you write your own check. Both are easy to get wrong.

Limits

  • No SMTP connections were made (port 25 is blocked from where I ran this), so "can receive mail" means "DNS publishes a mail server", the same thing an MX check tests.
  • Registration dates come from a sample of 40, not all 648.
  • Which domains count as disposable is the blocklist's classification, not mine.

If you want to test individual domains without running anything, there's a free disposable email checker on our site. Disclosure: I work at MailValid, which sells email verification.

Questions about the method are welcome in the comments.

πŸ“° Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.