Skip to content

Blog

How to block disposable email addresses

The honest version: a static list is the weakest of four available checks, it is stale within weeks of publication, and the usual implementation blocks customers you wanted.

VerifyInbox · published 11 September 2026 · 5 min read

Match the domain against a maintained blocklist, then check whether the mailbox actually exists — the two questions are independent and you need both. Refresh the list weekly, because new throwaway domains appear constantly, and decide deliberately what happens on a match rather than defaulting to a hard rejection.

The rest of this page is about the part the how-to articles leave out: why your blocklist keeps missing new domains, what the other three layers are, and which legitimate signups the standard implementation quietly throws away.

Why the blocklist keeps missing new domains

A disposable address provider needs one thing to defeat your list: a domain you have not heard of. Registering one costs a few dollars and pointing it at an existing throwaway service costs nothing, so the supply side of this problem is effectively free and the defence side is a subscription to a list somebody else maintains.

That asymmetry has a specific consequence, and it is worth stating plainly rather than selling around: any static list is out of date the week it ships. Ours included. We vendor 8,714 domains and refresh them weekly, and the honest claim that supports is "this catches the providers people actually use", not "this catches everything".

There is a second failure mode that costs more than the first, and almost every implementation has it. Here is a blocklist check written the way it is usually written, run on Python 3.13:

disposable.py — the naive check, and what it misses
BLOCKLIST = {"mailinator.com", "guerrillamail.com", "yopmail.com"}


def domain_of(address: str) -> str | None:
    _, _, domain = address.rpartition("@")
    return domain.strip().rstrip(".").lower() or None


def is_listed(address: str) -> bool:
    domain = domain_of(address)
    return domain in BLOCKLIST if domain else False


for candidate in [
    "[email protected]",
    "[email protected]",
    "[email protected]",
    "[email protected]",
    "[email protected]",
]:
    print(f"{candidate:36} {is_listed(candidate)}")

Line three is the interesting one. mail.mailinator.com is a subdomain of a domain that is on the list, and to an exact-match lookup it is simply a different string, so it passes. Any provider that hands out subdomains defeats the check for free. Matching the registrable domain rather than the literal string is the fix, and it is the single highest-value improvement to a list-based check.

Line four is the one no amount of list maintenance fixes. A domain registered this morning is on nobody’s list this morning.

The four layers, strongest last

Ways to detect a disposable address, with what each one misses
LayerWhat it catchesWhat it misses
1. Domain blocklistThe providers people actually use, instantly and offlineAnything registered since the list was built; subdomains, if matched as strings
2. MX fingerprintNew domains pointing at a known throwaway provider’s mail serversA provider running its own MX per domain
3. Mailbox verificationWhether the address exists at all — independent of whether it is throwawayNothing about intent. A burner mailbox that exists is deliverable
4. Behaviour after signupThe accounts that never confirm, never return, never convertEverything up to that point — it is a measurement, not a gate

Layer two is the one most implementations skip and it is cheap: resolve the MX records for an unknown domain and compare the mail hosts with those of providers you already know. A throwaway service running a thousand domains off one mail cluster is visible in DNS whatever it calls itself this week. The MX lookup tool shows you the records for any domain.

What the verifier returns, and the rule behind it

Every verification classifies the address as part of the same call, so the disposable answer arrives with the verdict rather than needing a second lookup. The result carries disposable: true, mailboxType: "disposable", and the verdict the mail server actually gave.

That last part is a deliberate rule and it surprises people: a classifier never changes the verdict. A disposable address whose mailbox exists comes back as deliverable with the flag set, because the mail server said the mailbox is there and that is a fact. A disposable address whose mailbox does not exist comes back as undeliverable with reason: mailbox_not_found, because a hard rejection outranks every classifier.

The alternative — folding "disposable" into "invalid" — would be more convenient and less true. It would also make the two signals impossible to separate at the point where you need them separated, which is when you decide what your signup form does next.

The disposable email checker runs both halves with no account, three checks a day per IP.

What to do on a match — and why not always a hard block

The reflex is to reject the signup. For a paid product with a free trial that is usually right. For a free product it throws away people you wanted, and three groups in particular:

  • People using a burner for privacy rather than abuse. Signing up to a newsletter with a throwaway address is a reasonable thing for a careful person to do, and it is not fraud.
  • People using an alias service, which is not the same thing as a disposable provider. An alias forwards to a permanent mailbox and lasts as long as the owner wants it to. Some blocklists conflate the two; if yours does, you are blocking privacy-conscious paying customers.
  • Plus-addressed and dotted addressesjane+signup@, j.a.n.e@. These are not disposable at all, they are one person organising their own inbox, and a surprising number of signup forms reject them outright.

A middle path costs almost nothing to implement and is what we would suggest by default: allow the signup, require a confirmation click before anything is granted, and hold back the expensive parts — a trial extension, a credit grant, an invite quota — until the address has been confirmed. A throwaway mailbox nobody checks fails that quietly, and a real person passes it in ten seconds.

What we do, and what it does not cover

Our own signup runs the same check: 8,714 vendored disposable domains, refreshed weekly from upstream and committed as a diff, applied to both password and OAuth signups. Publishing the number rather than an adjective is the point — it is a fact you can hold us to and it will be different next quarter.

What it does not cover, stated so you do not find out later: a domain registered after the last refresh. A provider running its own mail servers per domain, where the MX fingerprint gives nothing away. And spam traps — we have no spam-trap detection and do not claim any, because a trap address is indistinguishable from a real mailbox at the protocol level. Anyone selling you certainty about traps is selling you a guess with a label on it.

If a disposable domain is missing from the list, tell us — it is a vendored file and adding one is a commit, not a roadmap item.

Terms used on this page

Sources

Primary sources checked 11 September 2026. If something here is out of date, tell us and we will correct it.

More: How to find someone's email address · How to validate an email address in Python · Email validation regex: the pattern and its limits · Gmail, Yahoo and Microsoft sender requirements

Questions

How do I block disposable email addresses?

Match the registrable domain — not the full string, or subdomains slip through — against a blocklist you refresh at least weekly, then verify whether the mailbox exists, because the two questions are independent. For anything the list does not know, compare the domain’s MX records with providers you already recognise.

How to block disposable email addresses on signup forms

Check on submit rather than on keypress, and prefer a confirmation requirement over a hard rejection unless the product is paid. Allow the signup, require a confirmation click before granting anything valuable, and log every block you do apply — a form that hard-blocks on a list is always rejecting somebody it should not.

How can I detect disposable or temporary email addresses in Python?

A set lookup against a maintained list is the first layer and it is a few lines, but match the registrable domain rather than the exact string or subdomains of listed domains pass. Add a DNS lookup of the MX records for domains the list does not know, and an SMTP verification for whether the mailbox exists at all — which no local library can answer.

Ask the mail server, not a pattern

100 credits every month, free and renewing. Three verdicts, never two, with the SMTP evidence attached to each one.