DEV Community
How to check if an email address is real without sending an email
Sooner or later every app that collects email addresses needs to know whether an address is real. You can get a surprisingly long way without sending a single message. This guide builds four checks in Node, from the cheapest to the most invasive, and it is honest about where each one stops being reliable. All the snippets are ES modules, so save them as .mjs files and run them on a current Node release. A short syntax check catches most typos Skip the giant regex. The full email grammar is enormous and almost nobody needs it. Check the few rules that catch real mistakes: exactly one @, a sensible length, no spaces or brackets in the name, and a domain with at least one dot. // parse-email.mjs
import { domainToASCII } from 'node:url';
export function parseEmail(input) {
if (typeof input !== 'string') return null;
const address = input.trim();
if (address.length > 254) return null;
const parts = address.split('@');
if (parts.length !== 2) return null;
const [local, rawDomain] = parts;
if (local.length === 0 || local.length > 64) return null;
if (/[\s"(),:;<>[\]]/.test(local)) return null;
if (local.startsWith('.') || local.endsWith('.') || local.includes('..')) return null;
// Turns "bücher.example" into the "xn--" form DNS uses; invalid domains become "".
const domain = domainToASCII(rawDomain.toLowerCase());
const labels = domain.split('.');
if (domain === '' || labels.length < 2) return null;
const labelOk = (label) =>
/^[a-z0-9-]{1,63}$/.test(label) && !label.startsWith('-') && !label.endsWith('-');
if (!labels.every(labelOk) || /^\d+$/.test(labels.at(-1))) return null;
return { local, domain };
}
This accepts [email protected] and rejects name@@gmail.com, name@gmail and anything with a space in it. It does not prove the mailbox exists. It only proves the text is shaped like an address. The domain has to have a mail server, so look it up An address can be perfectly formatted and still point nowhere. DNS tells you where a domain receives mail, through MX records, and Node ships the lookup in node:dns/promises. // find-mail-servers.mjs
import { resolveMx, resolve4, resolve6 } from 'node:dns/promises';
const NO_ANSWER = new Set(['ENODATA', 'ENOTFOUND']);
// "No records" is an answer. Timeouts and server failures are not, so they are rethrown.
async function lookup(resolver, domain) {
try {
return await resolver(domain);
} catch (err) {
if (NO_ANSWER.has(err.code)) return [];
throw err;
}
}
export async function findMailServers(domain) {
const mx = await lookup(resolveMx, domain);
if (mx.length > 0) {
// A lone "." target is a null MX (RFC 7505): the domain refuses all mail.
const usable = mx.filter((record) => record.exchange !== '' && record.exchange !== '.');
return usable.sort((a, b) => a.priority - b.priority).map((record) => record.exchange);
}
// No MX records at all: RFC 5321 says to fall back to the domain's own address records.
const [v4, v6] = await Promise.all([lookup(resolve4, domain), lookup(resolve6, domain)]);
return v4.length + v6.length > 0 ? [domain] : [];
}
Two details matter here. Lower priority numbers are tried first, so the list is sorted by them. And a domain with no MX records is not automatically dead: RFC 5321 says a sender should fall back to the domain's A or AAAA record, which is called an implicit MX. A domain that publishes a single null MX has said it accepts no email at all, so the function returns an empty list for it. Run it against gmail.com and you get back a short list of mail hosts. Run it against a made-up domain and you get an empty list. Disposable domains are worth flagging at signup A throwaway inbox passes every check above, because its domain is real and has a mail server. A maintained public list of disposable domains, kept as a plain text file with one domain per line, lets you flag them. Download it on a schedule rather than copying it once, because the list keeps growing. // is-dispo...
Powered by Readybot.io
External Link
Platform
You are about to visit:
https://example.comThis will open in a new tab. Make sure you trust this link.