DEV Community

Tolga Cakular
Tolga Cakular

Posted on

How to verify email addresses in real time with a Node.js API

Every signup form is a magnet for bad email addresses — typos like jhon@gmial.com, throwaway inboxes, and addresses that simply don't exist. Let them into your list and you pay for it later: bounces, a wrecked sender reputation, and your real emails landing in spam.

The fix is to verify an address before you trust it. Here's how to do it in real time from Node.js.

What "verifying" actually means

A good check goes well beyond a regex. It looks at:

  • Syntax — is it a well-formed address?
  • MX records — does the domain actually accept mail?
  • SMTP — does the specific mailbox exist?
  • Disposable — is it a 10-minute throwaway?
  • Role-basedinfo@, support@ (low engagement)
  • Catch-all — the domain accepts everything, so you can't be 100% sure
  • Typosgmial.com should probably be gmail.com

Calling the API

// verify.js
const API_KEY = process.env.CLEARBOUNCE_API_KEY;

async function verifyEmail(email) {
  const res = await fetch("https://api.clearbounce.net/api/v1/verify", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": API_KEY,
    },
    body: JSON.stringify({ email }),
  });

  if (!res.ok) throw new Error(`Verify failed: ${res.status}`);
  return res.json();
}
Enter fullscreen mode Exit fullscreen mode

A response looks like this:

{
  "status": "deliverable",
  "score": 98,
  "isDeliverable": true,
  "checks": {
    "syntax": true,
    "mxRecords": true,
    "smtpValid": true,
    "isDisposable": false,
    "isRoleBased": false,
    "isCatchAll": false,
    "hasTypo": false
  }
}
Enter fullscreen mode Exit fullscreen mode

status is one of deliverable, undeliverable, risky, or unknown, and score (0–100) tells you how confident the result is.

Gating a signup with it

app.post("/signup", async (req, res) => {
  const { email } = req.body;
  const result = await verifyEmail(email);

  if (result.status === "undeliverable") {
    return res.status(400).json({ error: "That inbox doesn't exist — please double-check it." });
  }

  if (result.checks?.isDisposable) {
    return res.status(400).json({ error: "Please use a permanent email address." });
  }

  // "risky" (catch-all / role) → let them in, but flag it for a later re-check
  await createUser({ email, emailRisk: result.status === "risky" });
  res.json({ ok: true });
});
Enter fullscreen mode Exit fullscreen mode

Two habits that keep your list clean

  1. Verify at the point of entry (real time, like above) so junk never gets in.
  2. Re-clean the whole list periodically (bulk) — even good addresses go stale over time.

Full disclosure: I build ClearBounce, the verification API I used in the examples (it has a free tier if you want to try it). But the ideas matter more than the vendor — the same flow works with any real-time verification API.

Top comments (0)