Block disposable emails at signup
If you let anyone sign up with any email, a chunk of your "users" will be
throwaway inboxes — mailinator.com, 10minutemail.com, and hundreds more.
They're how people farm free trials, dodge bans, and inflate your numbers with
accounts that will never convert. @venelinkochev
put it well: blocking these at signup, plus Cloudflare Turnstile for the bots,
is a big lift in signup quality for very little work.
The trick is almost embarrassingly simple: there's a community-maintained list of disposable domains, and you just check the signup email's domain against it.
How it works
The disposable/disposable-email-domains
repo publishes a domains.json — tens of thousands of known throwaway domains,
updated continuously. Fetch it (cache it for a day), drop it in a Set, and
membership-test the domain after the @:
const LIST =
"https://rawcdn.githack.com/disposable/disposable-email-domains/master/domains.json";
let cache: Set<string> | null = null;
let fetchedAt = 0;
async function disposableDomains() {
if (cache && Date.now() - fetchedAt < 86_400_000) return cache; // 1 day
const res = await fetch(LIST);
cache = new Set<string>(await res.json());
fetchedAt = Date.now();
return cache;
}
export async function isDisposableEmail(email: string) {
const domain = email.split("@")[1]?.toLowerCase().trim();
if (!domain) return false;
return (await disposableDomains()).has(domain);
}Then guard the signup handler:
if (await isDisposableEmail(email)) {
return new Response("Please use a permanent email address.", { status: 422 });
}A Set lookup is O(1), and the daily-cached fetch means you're not hammering
the CDN. On an edge runtime like Workers, keep the cache in module scope (or KV)
so it survives between requests.
Do it server-side
The check belongs on the server, at the signup endpoint — never trust a client-only check, since anyone can skip it. The interactive demo above runs in your browser only because it's a demo; in production this is a few lines in your API route.
Layer it
No single signal is enough on its own. Stack cheap ones:
- Turnstile (or hCaptcha) to filter out bots before they submit.
- Disposable-domain block to filter out throwaway humans.
- Verification email so a real, owned inbox is still required.
Each is minutes of work; together they cut a surprising amount of junk.
Takeaways
- Disposable inboxes are a top source of fake/abuse signups — block the domain at signup against a maintained list, not a hand-rolled regex.
- Fetch
domains.jsononce a day into aSet; the check is O(1) and runs server-side. - Combine with Turnstile and email verification for layered, low-effort defense.
I put a live checker and the copy-paste guard on the Freebies page.
Ask your agent to implement this
Read the full writeup at https://seangeng.com/writing/block-disposable-emails.md and implement it in my project.
It covers: Block disposable emails at signup — Throwaway inboxes are how spam, trial-abuse, and fake accounts get in. Checking the email domain against a maintained disposable-domains list at signup takes minutes and pairs perfectly with Cloudflare Turnstile.
Requirements:
- Follow the technique/approach exactly as described in the writeup.
- Adapt names, colors, and styling to my project's existing conventions.
- If it's a component, make it reusable with sensible props and TypeScript types.
- Keep it accessible: semantic HTML, keyboard support, and respect prefers-reduced-motion.
- When done, tell me which files you created or changed and how to use it.Paste into Claude Code, Codex, Cursor, or any agent. view raw .md