Email validation in Node.js: from regex to mailbox checks
Create a free accountPlans and credits
Email validation in Node.js usually stops at a regex, and a regex cannot tell whether a mailbox exists. This guide adds the missing step: a format check first, then a mailbox check with the Easy Email Verification API, using the fetch built into Node.js 18 and later (no dependencies). It ends with an Express signup route. Every example works with the free sandbox key eev_sandbox_key, without an account.
1. Check the format with a regular expression
A simple pattern is enough to reject obvious mistakes. Leave everything else to the mailbox check:
const EMAIL_FORMAT = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export function looksLikeEmail(address) {
return EMAIL_FORMAT.test(address);
}
looksLikeEmail("jane@example.com"); // true
looksLikeEmail("jane.doe@example"); // false: no dot in the domain
looksLikeEmail("jane doe@example.com"); // false: space
Run the same check in the browser for instant feedback, but always repeat it on the server: browser checks are easy to skip.
Why a regex is not enough
These addresses pass any reasonable regex and still bounce:
| Address | Passes the regex | Problem |
|---|---|---|
jane@gmial.com | yes | The domain is a typo and does not exist |
former.employee@example.com | yes | The mailbox was closed |
jane@example-site.com | yes | The domain has no MX record, so it receives no mail |
x7k2@temp-inbox.example | yes | Disposable inbox that disappears in a few minutes |
Catching them takes a DNS lookup for the MX record and an SMTP conversation with the mail server. Node.js can do the DNS part (dns.promises.resolveMx), but the SMTP part is unreliable from your own server: cloud providers often block outgoing port 25, and mail servers greylist or block unknown hosts that probe mailboxes. An email verification API runs those checks from servers built for it, without sending any email.
2. Verify the mailbox with the API
Call GET /v1/verify with the address and the API key in the X-API-Key header. Keep the key on the server, in EEV_API_KEY; never put it in browser code.
const API_URL = "https://api.easyemailverification.com/v1/verify";
const API_KEY = process.env.EEV_API_KEY ?? "eev_sandbox_key";
export async function verifyEmail(address) {
const url = `${API_URL}?email=${encodeURIComponent(address)}`;
const response = await fetch(url, {
headers: { "X-API-Key": API_KEY },
signal: AbortSignal.timeout(30_000),
});
const data = await response.json();
if (!response.ok) {
throw new Error(`EEV error ${response.status}: ${data.message}`);
}
return data;
}
console.log(await verifyEmail("valid@sandbox.easyemailverification.com"));
encodeURIComponent matters: without it, a + in an address such as jane+news@example.com would arrive as a space. The response:
{
"email": "valid@sandbox.easyemailverification.com",
"result": "valid",
"reason": "accepted_email",
"disposable": false,
"accept_all": false,
"role": false,
"free": false,
"user": "valid",
"domain": "sandbox.easyemailverification.com",
"mx_record": "mx.sandbox.easyemailverification.com",
"mx_domain": "easyemailverification.com",
"safe_to_send": true,
"did_you_mean": "",
"success": true,
"message": "Sandbox response: no credits used",
"http_code": "200"
}
Errors return an HTTP status and a message: 401 for an unknown key, 402 when the account has no credits left.
3. Decide what to do with the result
export function decide(result) {
if (result.did_you_mean) return "suggest"; // ask the user: did you mean ...?
if (result.result === "valid" && result.safe_to_send) return "accept";
if (result.result === "invalid") return "reject";
return "review"; // unknown, catch-all or disposable: your policy decides
}
- accept: the mail server accepted the mailbox and no risk signal advises against it.
- reject: the address cannot receive mail. The reason says why (
rejected_email,invalid_domain,no_mx_record…). - suggest:
did_you_meanhas a correction, for exampletypo@gmail.comfortypo@gmial.com. - review: an unknown result, a catch-all domain or a disposable address. Unknown is not invalid: the server timed out or asked to try later.
4. Use it in an Express signup route
The route below rejects badly written and undeliverable addresses, offers the typo correction, and never blocks a signup just because the verification could not run:
import express from "express";
import { looksLikeEmail, verifyEmail, decide } from "./verify.mjs";
const app = express();
app.use(express.json());
app.post("/signup", async (req, res) => {
const email = String(req.body.email ?? "").trim();
if (!looksLikeEmail(email)) {
return res.status(400).json({ error: "Please enter a valid email address." });
}
let result;
try {
result = await verifyEmail(email);
} catch {
result = null; // verification unavailable: do not block the signup
}
const decision = result ? decide(result) : "review";
if (decision === "reject") {
return res.status(400).json({ error: "This email address cannot receive mail." });
}
if (decision === "suggest") {
return res.status(400).json({ error: `Did you mean ${result.did_you_mean}?` });
}
// "accept" or "review": create the account; for "review", confirm the address by email
res.json({ ok: true, needsConfirmation: decision === "review" });
});
app.listen(3000);
5. Verify up to 50 addresses in one request
export async function verifyEmails(addresses) {
const response = await fetch(API_URL, {
method: "POST",
headers: { "X-API-Key": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ emails: addresses }), // up to 50 per request
signal: AbortSignal.timeout(120_000),
});
const data = await response.json();
if (!response.ok) {
throw new Error(`EEV error ${response.status}: ${data.message}`);
}
return data; // one result per address, same format as above
}
For files with thousands of addresses, use the bulk endpoints or the dashboard upload instead of a loop: see bulk email verification and the API reference.
Test addresses
With the key eev_sandbox_key, these addresses return fixed answers and use no credits:
| Address | Answer |
|---|---|
valid@sandbox.easyemailverification.com | valid, accepted_email |
invalid@sandbox.easyemailverification.com | invalid, rejected_email |
unknown@sandbox.easyemailverification.com | unknown, timeout |
catchall@sandbox.easyemailverification.com | valid, accept_all: true, safe_to_send: false |
disposable@sandbox.easyemailverification.com | valid, disposable: true, safe_to_send: false |
typo@gmial.com | invalid, did_you_mean: typo@gmail.com |
quota@sandbox.easyemailverification.com | HTTP 402 (no credits) |
When it works, create a free account, generate a key under API settings and set it in EEV_API_KEY. Each verified address uses one credit; the free plan includes 50 verifications a day.
Next steps
- Prefer a library? The official package wraps everything above (single, batch, bulk, credits, sandbox):
npm install easyemailverification(npm). - Email Verification API: plans, limits and the other endpoints.
- Result codes: every
result,reasonand risk signal. - Examples in other languages on GitHub.
More ways to verify emails
- How to validate an email address in Python
- Email validation in Java with Spring Boot
- Validate email addresses in PHP without sending a message
- What every result code means
- API reference
- Check a single address for free
- All integrations
Using an AI assistant? It can follow this guide with you: give it validate-email-nodejs.md.