Every web form that collects an email address faces the same question: is this email real? A simple regex might catch obvious typos, but it won't tell you if the mailbox exists, if the domain has working mail servers, or if the address is a throwaway that'll never be checked again.
In this guide, we'll walk through the layers of email validation — from basic syntax checks to real-time API verification — and show you how to implement each one with code examples.
Why Regex Isn't Enough
Most developers start with a regex pattern like /^[^\s@]+@[^\s@]+\.[^\s@]+$/. It catches missing @ signs and blank fields, but that's about it. Here's what regex can't do:
- Check if the domain actually exists (
user@fakdomain.xyzpasses regex) - Detect disposable/temporary email providers
- Verify the mailbox is real and accepting mail
- Identify role-based addresses (
info@,admin@) that shouldn't receive marketing - Catch typos in popular domains (
user@gmial.com)
A proper validation pipeline has multiple layers. Let's go through each one.
Layer 1: Syntax Validation
This is your first line of defense — fast, client-side, catches obvious mistakes. Use it for instant feedback in forms:
function isValidEmailSyntax(email: string): boolean {
// RFC 5322 simplified — covers 99.9% of real addresses
const re = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
return re.test(email);
}
Good for UX. Useless for data quality.
Layer 2: DNS and MX Record Checks
A domain might have valid syntax but no mail server. Checking for MX (Mail Exchange) records tells you whether the domain can receive email at all:
# Quick MX check from the command line
dig +short MX gmail.com
# 5 gmail-smtp-in.l.google.com.
# 10 alt1.gmail-smtp-in.l.google.com.
dig +short MX definitelynotarealdomain123.com
# (empty — no mail servers)
If there are no MX records (and no A record fallback), the email address is guaranteed to bounce.
Layer 3: Disposable Email Detection
Services like Guerrilla Mail, Tempmail, and Mailinator provide throwaway addresses. Users sign up, grab a verification link, and never return. There are over 5,000 known disposable email providers, and new ones appear weekly.
Maintaining your own blocklist is a losing battle. This is where an API shines.
Layer 4: Real-Time API Verification
The most reliable approach combines all the above checks — syntax, DNS, disposable detection, and mailbox verification — in a single API call. Here's how to do it with the MailCheck API:
Using curl
curl -X POST https://api.mailcheck.dev/v1/verify \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your_api_key" \
-d '{"email": "test@example.com"}'
Response:
{
"email": "test@example.com",
"valid": false,
"reason": "mailbox_not_found",
"disposable": false,
"mx_found": true,
"did_you_mean": null
}
Using JavaScript (fetch)
async function verifyEmail(email) {
const response = await fetch('https://api.mailcheck.dev/v1/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your_api_key',
},
body: JSON.stringify({ email }),
});
const result = await response.json();
if (!result.valid) {
console.log(`Invalid: ${result.reason}`);
// Show user-friendly error in your form
}
return result;
}
// Example: validate on form submit
document.querySelector('#signup-form').addEventListener('submit', async (e) => {
e.preventDefault();
const email = document.querySelector('#email').value;
const check = await verifyEmail(email);
if (check.valid) {
// Proceed with signup
} else if (check.did_you_mean) {
// Suggest correction: "Did you mean user@gmail.com?"
} else {
// Show error
}
});
Where to Validate: Frontend vs Backend vs Both
The best approach is both:
- Frontend (client-side): Syntax check only. Instant feedback, no API calls for obvious typos.
- Backend (server-side): Full API verification before storing the email. Never trust client-side validation alone — it's trivially bypassable.
// Backend example (Node.js / Express)
app.post('/api/signup', async (req, res) => {
const { email, password } = req.body;
// Verify email before creating account
const check = await fetch('https://api.mailcheck.dev/v1/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + process.env.MAILCHECK_API_KEY,
},
body: JSON.stringify({ email }),
}).then(r => r.json());
if (!check.valid) {
return res.status(400).json({
error: 'invalid_email',
reason: check.reason,
suggestion: check.did_you_mean || null,
});
}
// Email is valid — create the account
await createUser(email, password);
res.json({ success: true });
});
Common Pitfalls to Avoid
- Don't verify on every keystroke. Debounce or validate on blur/submit to avoid burning API calls.
- Handle API errors gracefully. If the verification service is down, don't block signups — fall back to syntax validation and verify later.
- Cache results. If the same email is submitted twice in a session, don't call the API again.
- Respect privacy. Don't store verification results longer than needed. The email's validity can change over time anyway.
The Validation Checklist
For any production signup or email collection form, make sure you cover:
- ✅ Syntax validation (instant, client-side)
- ✅ MX record verification (does the domain accept mail?)
- ✅ Disposable email detection (is it a throwaway?)
- ✅ Mailbox verification (does this specific address exist?)
- ✅ Typo suggestions (
gmial.com→gmail.com)
You can implement layers 1-2 yourself, but layers 3-5 are where an API like MailCheck saves you weeks of work and ongoing maintenance.
MailCheck gives you all five validation layers in a single API call. 100 free verifications per day, no credit card required. Sign up and get your API key →