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 pad your numbers with
accounts that never convert. @venelinkochev
put it well: block these at signup, add Cloudflare Turnstile for the bots, and
signup quality jumps for very little work.
The trick is almost embarrassingly simple. There's a community-maintained list of disposable domains, and you 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 demo above runs in your browser 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, so stack a few cheap ones. Turnstile (or hCaptcha) filters out bots before they submit. The disposable-domain block filters out throwaway humans. And a verification email still requires a real, owned inbox at the end. Each is minutes of work, and together they cut a surprising amount of junk.
What I'd flag: use a maintained list, not a hand-rolled regex of domains you remember. The regex goes stale the day after you write it; the list doesn't.
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 well 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