DEV Community

Jonas Hämmerle
Jonas Hämmerle

Posted on

How to validate email addresses properly in 2026

"Just use a regex" is the most common wrong answer to email validation. Here's what actually matters, in the order it actually matters.

1. Syntax — but a permissive one. The RFC 5322 spec technically allows things like quoted strings and comments in the local part, which almost no real mail provider uses. Don't implement the full spec; use a pragmatic check instead:

function looksLikeEmail(input) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input);
}
Enter fullscreen mode Exit fullscreen mode

This deliberately under-validates. A regex that's too strict will reject real addresses (plus-addressing, unusual-but-valid TLDs) more often than it catches typos — false rejections cost you signups, false acceptances just mean you fall through to the next check.

2. MX record lookup. Syntax passing doesn't mean the domain can receive mail:

import { resolveMx } from "node:dns/promises";

async function canReceiveMail(domain) {
  try {
    return (await resolveMx(domain)).length > 0;
  } catch {
    return false;
  }
}
Enter fullscreen mode Exit fullscreen mode

Catches typo'd domains (gmial.com) and abandoned/fake domains for basically zero added latency.

3. Disposable/temp-mail detection. Syntax-valid, MX-valid, and still worthless for your product: throwaway addresses from 10minutemail, guerrillamail, mailinator, and hundreds of similar services that spin up new domains constantly. There's no algorithmic tell — you need a maintained domain blocklist (the community-run disposable-email-domains list is a solid free start, just budget time to keep it synced).

4. What you're deliberately not doing: an SMTP handshake to check if the specific mailbox exists. It's slow, unreliable, and most mail servers now treat probing behavior as spam reconnaissance and silently no-op it. Don't build on a foundation that stopped being reliable.

Put together, that's syntax (fast, always run) → MX (cheap, catches typos) → disposable-domain check (catches throwaway signups) — each step only worth running if the previous one passed. I built exactly this pipeline as endpoints on Validate if you'd rather not maintain the MX/disposable-list plumbing yourself.

Top comments (0)