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:

AddressPasses the regexProblem
jane@gmial.comyesThe domain is a typo and does not exist
former.employee@example.comyesThe mailbox was closed
jane@example-site.comyesThe domain has no MX record, so it receives no mail
x7k2@temp-inbox.exampleyesDisposable 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
}

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:

AddressAnswer
valid@sandbox.easyemailverification.comvalid, accepted_email
invalid@sandbox.easyemailverification.cominvalid, rejected_email
unknown@sandbox.easyemailverification.comunknown, timeout
catchall@sandbox.easyemailverification.comvalid, accept_all: true, safe_to_send: false
disposable@sandbox.easyemailverification.comvalid, disposable: true, safe_to_send: false
typo@gmial.cominvalid, did_you_mean: typo@gmail.com
quota@sandbox.easyemailverification.comHTTP 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

By using this website, you automatically accept that we use cookies.