
A single typo like user@gmial.com sails right past your regex, lands in your database, and quietly bounces every email you ever send it. Multiply that across thousands of signups and you have a sender reputation problem, skewed analytics, and wasted ad spend. The good news: adding real email verification in Node.js takes about ten lines of code, and this guide walks you through every layer—from a single check to Express middleware, bulk processing, retries, and caching.
By the end, you'll have a production-ready pattern you can drop into any Node.js or Express app to catch invalid, disposable, and risky addresses before they cost you anything.
Prerequisites and Project Setup
You'll need Node.js 18 or later (so you get the built-in fetch), a terminal, and a 1Lookup API key. If you don't have one yet, grab a free key after you sign up—no credit card required to start testing.
Spin up a fresh project:
mkdir email-verify-demo && cd email-verify-demo
npm init -y
npm install dotenv axios express
axios and express are optional—we'll show a native fetch variant too—but they're handy for the middleware example. Store your key in an environment variable rather than hardcoding it:
# .env
LOOKUP_API_KEY=YOUR_API_KEY
Then load it once at the top of your entry file:
import "dotenv/config";
const API_KEY = process.env.LOOKUP_API_KEY;
const BASE_URL = "https://api.1lookup.io/v1";
Add .env to your .gitignore immediately. Leaked API keys are one of the most common ways credits get burned.
Validate a Single Email
The core endpoint is POST /validate/email. Send a JSON body with one email field and you get back a rich verdict. Here's the raw shape with curl so you can see exactly what's on the wire:
curl -X POST https://api.1lookup.io/v1/validate/email \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com"}'
Using Node 18+ fetch
No dependencies required. The native fetch is the cleanest way to do email verification in Node.js today:
async function validateEmail(email) {
const res = await fetch(`${BASE_URL}/validate/email`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
});
if (!res.ok) {
throw new Error(`Validation failed: ${res.status}`);
}
return res.json();
}
const result = await validateEmail("user@example.com");
console.log(result.valid, result.deliverable);
Using axios
If your codebase already standardizes on axios, the equivalent is just as short and gives you nicer error objects:
import axios from "axios";
const client = axios.create({
baseURL: BASE_URL,
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
timeout: 5000,
});
async function validateEmail(email) {
const { data } = await client.post("/validate/email", { email });
return data;
}
A typical response looks like this:
{
"email": "user@example.com",
"valid": true,
"deliverable": true,
"disposable": false,
"role_account": false,
"free_email": false,
"mx_found": true,
"smtp_check": true,
"did_you_mean": null,
"fraud_score": 8,
"reason": "accepted_email"
}
Understanding the Response Fields
Don't just check valid and move on. The real value is in the supporting fields, which let you make nuanced decisions instead of a blunt accept/reject. Here's what each one tells you:
valid— The address is correctly formatted and the domain exists. This is the floor, not the ceiling.deliverable— The mailbox can actually receive mail (mx_foundplussmtp_check). This is the field that protects your bounce rate.disposable— A throwaway address from a service like Mailinator or 10minutemail. Almost always a red flag for signups. For a deeper dive, see our guide on disposable email detection to prevent fake signups.role_account— A shared inbox likeinfo@orsales@. Fine for B2B contact, risky for transactional sends to a person.free_email— A consumer provider (Gmail, Yahoo). Useful as a signal in B2B lead scoring.did_you_mean— A typo correction suggestion, e.g.gmail.comforgmial.com. Surface this directly to users.fraud_score— A 0–100 risk number you can threshold against.
A sensible default policy: accept when valid && deliverable && !disposable, prompt the user when did_you_mean is set, and flag for review when fraud_score is high.
Add Validation to an Express Signup Route
Real apps validate at the edge. The cleanest pattern is Express email validation middleware that runs before your signup handler ever touches the database:
import express from "express";
const app = express();
app.use(express.json());
// Reusable middleware
async function requireValidEmail(req, res, next) {
const { email } = req.body;
if (!email) {
return res.status(400).json({ error: "Email is required" });
}
try {
const result = await validateEmail(email);
if (!result.valid || !result.deliverable) {
return res.status(422).json({
error: "Please enter a deliverable email address",
suggestion: result.did_you_mean || undefined,
});
}
if (result.disposable) {
return res.status(422).json({
error: "Disposable email addresses are not allowed",
});
}
req.emailMeta = result; // pass enriched data downstream
next();
} catch (err) {
// Fail open so a validation outage never blocks signups
console.error("Email validation error:", err.message);
next();
}
}
app.post("/signup", requireValidEmail, (req, res) => {
// req.emailMeta is available here when validation ran
res.json({ ok: true });
});
app.listen(3000);
Notice the fail-open strategy in the catch block. If the validation service is briefly unreachable, you let the signup through rather than locking real users out. For high-fraud flows you might fail closed instead—pick based on your risk tolerance.
Real-Time Form Validation Before Submit
Middleware protects your backend, but the best user experience catches typos while someone is still typing. Expose a thin endpoint your frontend can call on blur:
app.post("/api/check-email", async (req, res) => {
try {
const result = await validateEmail(req.body.email);
res.json({
ok: result.valid && result.deliverable && !result.disposable,
suggestion: result.did_you_mean,
});
} catch {
res.json({ ok: true }); // never block the form on errors
}
});
Your client debounces input (300ms is a good starting point), hits this endpoint, and shows the suggestion inline—"Did you mean user@gmail.com?". If you're on React, our email validation React component guide shows the full hook and component wiring end to end.
Bulk Validation to Clean Existing Lists
For onboarding an existing database or cleaning a CSV export, validating one address at a time is slow and wasteful. Use the bulk endpoint, POST /validate/email/bulk, which accepts an array:
async function validateBulk(emails) {
const res = await fetch(`${BASE_URL}/validate/email/bulk`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ emails }),
});
return res.json();
}
const report = await validateBulk([
"a@example.com",
"b@gmial.com",
"temp@mailinator.com",
]);
const clean = report.results
? report.results.filter((r) => r.deliverable && !r.disposable)
: [];
console.log(`Kept ${clean.length} of ${report.results?.length ?? 0}`);
Chunk very large lists (say, 1,000 addresses per request) and process chunks sequentially or with light concurrency so you stay within rate limits. If you're cleaning lists at scale, the advanced email validation API implementation guide covers queueing patterns and throughput tuning in depth.
Error Handling, Timeouts, and Retries
Network calls fail. A resilient client treats validation as a flaky dependency, not a guarantee. Three rules to live by:
- Always set a timeout. An
axiosinstance withtimeout: 5000or anAbortControlleronfetchprevents one slow request from hanging a signup. - Retry only on transient errors. Retry on
429(rate limit) and5xx, never on4xxvalidation errors—those won't change. - Back off exponentially. Wait longer between each attempt to avoid hammering the service.
Here's a compact retry wrapper:
async function withRetry(fn, attempts = 3) {
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (err) {
const status = err.response?.status;
const retryable = !status || status === 429 || status >= 500;
if (!retryable || i === attempts - 1) throw err;
await new Promise((r) => setTimeout(r, 2 ** i * 500));
}
}
}
const result = await withRetry(() => validateEmail("user@example.com"));
Caching Results to Save Credits
Most APIs—1Lookup included—bill per validation, so re-checking the same address wastes money. Cache verdicts and you'll often cut your call volume by half or more on repeat traffic.
const cache = new Map();
const TTL = 24 * 60 * 60 * 1000; // 24 hours
async function validateCached(email) {
const key = email.trim().toLowerCase();
const hit = cache.get(key);
if (hit && Date.now() - hit.at < TTL) {
return hit.data;
}
const data = await withRetry(() => validateEmail(key));
cache.set(key, { data, at: Date.now() });
return data;
}
A Map works for a single process; swap in Redis for multi-instance deployments so the cache is shared. Use a TTL of 24 hours to a few days—mailbox status drifts over time, so you don't want to cache forever. Normalize the key (trim and lowercase) so User@Example.com and user@example.com hit the same entry.
A quick pre-launch checklist
- API key loaded from an environment variable, never committed
- Timeouts set on every validation call
- Retries with backoff on
429/5xxonly - Middleware fails open (or closed) deliberately, not by accident
- Results cached with a sane TTL
-
did_you_meansurfaced to users in real time
Beyond Verification: Enriching and Scoring
Once clean emails are flowing in, you can do more with them. Pair validation with email append to fill in missing contact records, or feed the fraud_score into a broader fraud detection pipeline to catch coordinated fake-signup attacks before they hit your funnel. Verification is the entry point; enrichment is where the data becomes an asset.
Next Steps
You now have everything needed for robust email verification in Node.js: single and bulk checks, Express middleware, real-time form feedback, retries, and caching. Start small—drop the middleware into your signup route today—then layer in caching and bulk cleaning as your volume grows.
Ready to put it into production? Explore the full email validation API to see every field and rate tier, check transparent per-validation rates on our pricing page, and sign up for a free API key to run your first verification in the next five minutes.
Meet the Expert Behind the Insights
Real-world experience from building and scaling B2B SaaS companies

Robby Frank
Head of Growth at 1Lookup
"Calm down, it's just life"
About Robby
Self-taught entrepreneur and technical leader with 12+ years building profitable B2B SaaS companies. Specializes in rapid product development and growth marketing with 1,000+ outreach campaigns executed across industries.
Author of "Evolution of a Maniac" and advocate for practical, results-driven business strategies that prioritize shipping over perfection.