How to Detect Disposable Email Addresses in Form Submissions
Disposable emails fuel spam and fake signups. Learn detection methods from blocklists to APIs, with code examples for JavaScript and Python.
Someone signs up on your site. Email looks normal: random letters at mailinator.com. Five minutes later, they’re gone. The email address? Already expired. Your welcome email bounces. Your CRM has another junk entry.
Disposable email addresses are everywhere. Services like Temp Mail, Guerrilla Mail, and 10 Minute Mail generate throwaway addresses in seconds. Users get a working inbox for a few minutes or hours, then it vanishes. For privacy-conscious users avoiding spam, they’re useful. For your business trying to build a real user base, they’re a headache.
The scale is bigger than most people realize. Some services report that 20% or more of signups come from disposable emails. That’s one in five users who never intended to stick around.
Why People Use Disposable Emails
Not everyone using a disposable email is a spammer. The motivations break down into a few categories:
Privacy protection. Users worried about data breaches or aggressive marketing use throwaway addresses to shield their real inbox. They want the content but not the relationship.
Avoiding email lists. People download a free resource, access gated content, or sign up for a trial. They know they’ll get bombarded with follow-ups. A disposable email lets them grab what they want and disappear.
Testing and development. Developers creating test accounts on their own apps often use disposable emails rather than burning their real addresses or setting up test infrastructure.
Abuse and fraud. Spammers create fake accounts in bulk. Fraudsters claim multiple free trials or promotions. Bad actors sign up to abuse referral programs. Disposable emails let them scale these operations without ever using a traceable address.
The first three use cases are annoying but arguably legitimate. The last one costs you real money. Either way, if your business model depends on genuine user relationships, you need a way to identify these addresses.
The Problem With Static Blocklists
The most obvious approach: maintain a list of known disposable email domains and block them.
GitHub hosts a popular disposable-email-domains repository with thousands of known domains. You can download it, integrate it into your validation logic, and reject any email that matches.
Here’s a basic implementation in JavaScript:
const disposableDomains = new Set([
'mailinator.com',
'guerrillamail.com',
'10minutemail.com',
'tempmail.com',
'throwaway.email',
// ... thousands more
]);
function isDisposableEmail(email: string): boolean {
const domain = email.split('@')[1]?.toLowerCase();
if (!domain) return false;
return disposableDomains.has(domain);
}
// Usage
if (isDisposableEmail(formData.email)) {
return { error: 'Please use a permanent email address' };
}
Simple, free, and fast. What’s wrong with it?
The list goes stale immediately. New disposable email providers launch constantly. Existing providers spin up new domains to evade blocklists. Some services add 50+ new domains per day. The moment you download that list, it starts becoming obsolete.
Subdomains and variations slip through. Many disposable services let users create custom subdomains or use wildcards. anything.guerrillamail.com would bypass a check for just guerrillamail.com unless you handle it specifically.
Maintenance is tedious. You need to regularly update the list, monitor for new providers, and test your validation. That’s ongoing work with no end in sight.
False positives happen. Some legitimate email providers get incorrectly flagged. Corporate domains that happen to share infrastructure with disposable services can trigger false matches.
Static blocklists work as a baseline, but they’re not enough on their own.
DNS and MX Record Analysis
Here’s where things get more interesting. Disposable email services often share infrastructure across hundreds of domains. Even when the domains look completely different, they route mail through the same servers.
You can detect this by analyzing MX (Mail Exchange) records. When you query the DNS for a domain’s MX record, you find out which mail server handles its email. Many disposable services manage dozens or hundreds of domains but run them all through a handful of shared servers.
import dns from 'dns';
import { promisify } from 'util';
const resolveMx = promisify(dns.resolveMx);
// Known disposable email MX servers
const suspiciousMxHosts = new Set([
'mail.guerrillamail.com',
'mx.yopmail.com',
'mail.tempmail.net',
// Add more as you discover them
]);
async function checkMxRecords(email: string): Promise<boolean> {
const domain = email.split('@')[1];
if (!domain) return false;
try {
const mxRecords = await resolveMx(domain);
for (const record of mxRecords) {
if (suspiciousMxHosts.has(record.exchange.toLowerCase())) {
return true; // Likely disposable
}
}
} catch {
// Domain doesn't exist or has no MX records
return true; // Suspicious
}
return false;
}
This approach catches domains that aren’t in public blocklists yet but share infrastructure with known disposable services. The downside: you need to maintain the MX server list, and sophisticated operators can change their infrastructure.
You can also resolve MX hostnames to IP addresses. Domains with seemingly unrelated MX records might ultimately route to the same IP. That shared backend is a strong signal.
Pattern Detection
Disposable email services often generate addresses with distinctive patterns. Random strings of characters, sequential usernames, or unusual length distributions can indicate throwaway addresses.
function hasRandomUsername(email: string): boolean {
const username = email.split('@')[0];
// Check for long random strings
if (username.length > 20 && /^[a-z0-9]+$/i.test(username)) {
// High ratio of numbers to letters suggests randomness
const numbers = (username.match(/\d/g) || []).length;
const ratio = numbers / username.length;
if (ratio > 0.4) return true;
}
// Sequential patterns like user001, test123
if (/^(user|test|temp|fake|demo)\d+$/i.test(username)) {
return true;
}
// UUID-like patterns
if (/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(username)) {
return true;
}
return false;
}
Pattern detection is fuzzy. Plenty of legitimate users have random-looking usernames, and plenty of disposable addresses look perfectly normal. Use it as one signal among many, not a definitive check.
WHOIS and Domain Age
Disposable email providers often register domains in bulk with recent creation dates. A domain that was registered last week is more suspicious than one that’s been around for years.
async function checkDomainAge(domain: string): Promise<number> {
// You'd typically use a WHOIS API service here
const response = await fetch(`https://api.example.com/whois/${domain}`);
const data = await response.json();
if (data.createdDate) {
const created = new Date(data.createdDate);
const now = new Date();
const ageInDays = Math.floor((now.getTime() - created.getTime()) / (1000 * 60 * 60 * 24));
return ageInDays;
}
return -1; // Unknown
}
// Usage
const age = await checkDomainAge('suspicious-domain.com');
if (age >= 0 && age < 30) {
// Very new domain - add weight to spam score
}
Domain age alone doesn’t prove anything. Legitimate businesses launch new domains all the time. But combined with other signals, a brand-new domain using shared mail infrastructure is highly suspicious.
Real-Time API Solutions
Maintaining your own detection logic is a lot of work. API services handle the heavy lifting: they maintain constantly-updated blocklists, analyze infrastructure patterns, and use machine learning to catch new disposable domains as they appear.
Services like IPQualityScore, DeBounce, and MailboxValidator offer disposable email detection APIs. You send an email address, they return whether it’s disposable, along with other validation data like whether the domain exists and accepts mail.
interface EmailValidationResult {
disposable: boolean;
valid: boolean;
mxRecords: boolean;
role: boolean; // info@, support@, etc.
}
async function validateEmail(email: string): Promise<EmailValidationResult> {
const response = await fetch(
`https://api.emailvalidation.example/check?email=${encodeURIComponent(email)}`,
{
headers: { 'Authorization': `Bearer ${process.env.EMAIL_API_KEY}` }
}
);
return response.json();
}
// Usage in form handler
export async function handleFormSubmission(data: FormData) {
const validation = await validateEmail(data.email);
if (validation.disposable) {
return {
error: 'Please use a permanent email address',
code: 'DISPOSABLE_EMAIL'
};
}
if (!validation.valid || !validation.mxRecords) {
return {
error: 'This email address appears to be invalid',
code: 'INVALID_EMAIL'
};
}
// Process the form
}
The advantage of API solutions: they’re always up to date. When a new disposable email provider launches, the service adds it to their detection logic. You don’t need to do anything.
The downside: you’re adding an external dependency and API call to your form submission flow. There’s latency and potential for service outages. And you’re paying per request at scale.
Python Implementation
For Python backends, the approach is similar. Here’s a basic implementation using a static list plus DNS checking:
import dns.resolver
from typing import Set
DISPOSABLE_DOMAINS: Set[str] = {
"mailinator.com",
"guerrillamail.com",
"10minutemail.com",
"tempmail.com",
"throwaway.email",
# Load from file in production
}
SUSPICIOUS_MX_HOSTS: Set[str] = {
"mail.guerrillamail.com",
"mx.yopmail.com",
}
def is_disposable_email(email: str) -> bool:
"""Check if email is from a known disposable provider."""
try:
domain = email.split("@")[1].lower()
except IndexError:
return True # Invalid email format
# Direct domain match
if domain in DISPOSABLE_DOMAINS:
return True
# Check MX records
try:
mx_records = dns.resolver.resolve(domain, "MX")
for record in mx_records:
mx_host = str(record.exchange).lower().rstrip(".")
if mx_host in SUSPICIOUS_MX_HOSTS:
return True
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
return True # No MX records is suspicious
except Exception:
pass # DNS lookup failed, don't block on this alone
return False
For production use, you’d want to load the domain list from an external source that you update regularly, cache DNS lookups, and combine with an API service for comprehensive coverage.
Combining Signals for Accuracy
No single detection method is foolproof. The best approach combines multiple signals and generates a score rather than a binary yes/no decision.
interface EmailScore {
score: number; // 0-10, higher = more likely spam
signals: {
disposableDomain: boolean;
suspiciousMx: boolean;
newDomain: boolean;
randomUsername: boolean;
noMxRecords: boolean;
};
}
async function scoreEmail(email: string): Promise<EmailScore> {
const domain = email.split('@')[1]?.toLowerCase();
const signals = {
disposableDomain: false,
suspiciousMx: false,
newDomain: false,
randomUsername: false,
noMxRecords: false,
};
let score = 0;
// Check static blocklist (+3 points)
if (DISPOSABLE_DOMAINS.has(domain)) {
signals.disposableDomain = true;
score += 3;
}
// Check MX records (+2 points)
const mxResult = await checkMxRecords(email);
if (mxResult.suspicious) {
signals.suspiciousMx = true;
score += 2;
}
if (mxResult.noRecords) {
signals.noMxRecords = true;
score += 3;
}
// Check domain age (+1 point)
const age = await checkDomainAge(domain);
if (age >= 0 && age < 30) {
signals.newDomain = true;
score += 1;
}
// Check username patterns (+1 point)
if (hasRandomUsername(email)) {
signals.randomUsername = true;
score += 1;
}
return { score: Math.min(score, 10), signals };
}
With a score-based system, you can set different thresholds for different actions. Score above 5? Block outright. Score between 3 and 5? Flag for manual review or require additional verification. Score below 3? Let it through.
This approach reduces false positives while still catching the obvious cases.
What To Do When You Detect One
Blocking disposable emails is only half the problem. How you handle the rejection matters.
Be clear but not preachy. A simple message works: “Please use a permanent email address to complete signup.” Don’t lecture users about why disposable emails are bad.
Consider the user experience. Some users have legitimate reasons for not wanting to share their real email. If your service truly requires ongoing communication, explain why. “We’ll send your order updates to this email” gives context.
Don’t reveal too much. If you tell users exactly which domains are blocked, sophisticated abusers will just use ones you haven’t caught yet. Keep your detection logic private.
Offer alternatives. Some users genuinely don’t trust you with their real email. Consider whether you can offer value without requiring signup, or whether alternative verification methods make sense.
Log and learn. Track which disposable domains you’re seeing most often. If the same domain appears repeatedly in spam attempts, add it to your blocklist. If you’re seeing false positives from a legitimate provider, investigate and whitelist.
FormShield Handles This Automatically
Building and maintaining disposable email detection is ongoing work. You need updated blocklists, DNS lookups, pattern matching, and API integrations. Then you need to tune thresholds, handle edge cases, and monitor for new providers.
FormShield includes disposable email detection as part of its unified spam detection API. When you send a form submission to our endpoint, we check the email against constantly-updated blocklists, analyze MX records, examine domain reputation, and combine it with other signals like IP intelligence and content analysis.
You get back a single spam score with a detailed breakdown:
{
"verdict": "spam",
"score": 7.2,
"signals": {
"email": {
"disposable": true,
"domain": "mailinator.com",
"mx_valid": true
},
"ip": { "datacenter": false },
"content": { "suspicious_phrases": 0 }
},
"ruleMatches": ["disposable_email"]
}
No need to maintain your own blocklists or integrate multiple services. One API call, one score, transparent reasoning for every decision.
The free tier handles 1,000 requests per month, enough to test the integration. Paid plans scale to 100,000+ requests for high-volume forms.
Check out how it works or sign up for free to add disposable email detection to your forms in minutes.
Moving Forward
Disposable emails aren’t going away. New providers launch regularly, existing ones add domains to evade detection, and users continue finding reasons to avoid sharing their real addresses.
Your options: accept the pollution in your database, build and maintain custom detection logic, or use a service that handles it for you. The right choice depends on your volume, your resources, and how much you value clean user data.
For most teams, the math favors outsourcing. The time spent maintaining blocklists and tuning detection logic is time not spent building your product. And a single API call is a lot simpler than stitching together five different email validation services.
Whatever you choose, disposable email detection should be one layer in a broader spam defense strategy. It catches a specific attack vector but doesn’t address IP-based spam, content-based spam, or sophisticated bot attacks. The best defense combines multiple signals and adapts as attackers evolve.