Developer Guides
Email Verification API: How to Integrate It into Signups and List Cleaning
An email verification API takes an address and returns a verdict, usually valid, risky, or invalid, along with the checks behind it. You integrate it in two places: at the point of capture (signup, checkout, lead forms), where you need a fast answer with a fallback, and in batch jobs that clean existing lists. This guide shows both with tested code, using the API on this site as the example, and explains how to turn each result into a decision.
What an email verification API actually checks
Before writing code, be clear on what you’re buying. Most verification APIs layer some or all of these checks:
| Check | What it tells you | Can be wrong when |
|---|---|---|
| Syntax | The address is well formed | Rarely; but well-formed isn’t the same as real |
| Domain / MX | The domain exists and publishes mail servers (or an implicit MX per RFC 5321 section 5.1), and isn’t a null MX (RFC 7505) | DNS is temporarily failing |
| Disposable | The domain belongs to a throwaway inbox service | New disposable domains appear constantly |
| Role | The local part is a function like info@ or support@ | Some small businesses only have role addresses |
| Free provider | The domain is a consumer mailbox provider | Not an error; useful for lead scoring |
| Typo suggestion | gmial.com probably meant gmail.com | Rare legitimate lookalike domains |
| SMTP mailbox probe | The receiving server accepted RCPT TO for the address | Catch-all domains, greylisting, providers that accept everything at RCPT and bounce later |
The API on this site does the first six. It deliberately does not do SMTP mailbox probing, and you should be skeptical of how much any provider’s SMTP results mean; how SMTP email verification works explains why.
The API at a glance
Two endpoints, both authenticated with an X-API-Key header:
# Single address
curl -s "https://bookmyspot.us/tools/email-verifier/api/v1/verify?email=jane@gmial.com" \
-H "X-API-Key: $VERIFIER_API_KEY"
# Up to 100 addresses per request
curl -s -X POST "https://bookmyspot.us/tools/email-verifier/api/v1/verify/bulk" \
-H "X-API-Key: $VERIFIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"emails": ["jane@gmial.com", "info@example.org", "sam@mailinator.com"]}'
A single-address response looks like this (values are illustrative):
{
"email": "jane@gmial.com",
"status": "risky",
"reason": "possible_typo",
"checks": {
"syntax": true,
"mx": true,
"disposable": false,
"role": false,
"free_provider": false
},
"suggestion": "jane@gmail.com",
"mx": ["mx.gmial.com"]
}
statusis"valid","risky", or"invalid".reasonis a short machine-readable explanation.checksbreaks out each individual test.suggestionis a corrected address when a typo is likely, otherwisenull.mxlists the domain’s mail hosts.
The bulk endpoint returns a results array with one object of the same shape per address. Free keys with a daily limit come from the API documentation page. When you exceed the limit, the API answers with HTTP 429.
Integration 1: Real-time verification at signup
At signup, the API sits between the user and your database. Three rules matter more than anything else:
- Call it from your server, never the browser. A key in client-side JavaScript is a public key. Your frontend calls your own endpoint, which calls the verifier. If your provider supports it, restrict keys by IP or scope too; we cover that in locking down API keys.
- Set a short timeout and fail open. If the verifier is slow or down, accept the address and flag it for a later check. A verification outage shouldn’t become a signup outage.
- Run cheap checks first. Validate syntax locally before spending an API call on obvious garbage.
Here’s a small Node 22 client that follows those rules:
// verifier-client.mjs (Node 22+, no dependencies)
const BASE = 'https://bookmyspot.us/tools/email-verifier/api/v1';
const API_KEY = process.env.VERIFIER_API_KEY;
export async function verifyEmail(email, { timeoutMs = 4000 } = {}) {
const url = `${BASE}/verify?email=${encodeURIComponent(email)}`;
try {
const res = await fetch(url, {
headers: { 'X-API-Key': API_KEY },
signal: AbortSignal.timeout(timeoutMs),
});
if (!res.ok) {
const { error } = await res.json().catch(() => ({}));
return { email, status: 'unknown', reason: `http_${res.status}`, error };
}
return await res.json();
} catch (err) {
// Timeout or network failure: don't block the user on our dependency.
return { email, status: 'unknown', reason: err.name === 'TimeoutError' ? 'timeout' : 'network_error' };
}
}
console.log(await verifyEmail(process.argv[2] ?? 'jane@gmial.com'));
Note the client invents a fourth status, unknown, for its own failures. Keeping that separate from the API’s invalid matters: you never want a network blip to reject a customer.
Turning results into signup decisions
| Result | Suggested action at signup |
|---|---|
invalid | Block with a clear message (“We couldn’t find mail servers for that domain”) |
risky with a suggestion | Ask: “Did you mean jane@gmail.com?” Let the user accept or keep their entry |
risky with checks.disposable | Block or allow based on your product’s policy; see blocking disposable emails at signup |
risky with checks.role | Usually allow for B2B; consider a softer path for consumer products |
valid | Continue, then send your confirmation email |
unknown (your timeout) | Allow, mark unverified, re-check in a background job |
Whatever the verdict, still send a confirmation email. An API can tell you a domain accepts mail; only a clicked link tells you this person owns this inbox. The UX side of this, including where to show suggestions and how to word errors, is covered in real-time email verification for signup forms.
Integration 2: Bulk verification for existing lists
For a list you already have, use the bulk endpoint, which takes up to 100 addresses per request. This Python script reads a CSV, de-duplicates, verifies in batches, and writes a results file. It uses only the standard library:
# bulk_verify.py (Python 3.12+, standard library only)
# Usage: VERIFIER_API_KEY=... python bulk_verify.py input.csv output.csv
import csv
import json
import os
import sys
import time
import urllib.error
import urllib.request
BASE = "https://bookmyspot.us/tools/email-verifier/api/v1"
API_KEY = os.environ["VERIFIER_API_KEY"]
BATCH = 100 # the API's per-request maximum
def verify_batch(emails: list[str]) -> list[dict]:
body = json.dumps({"emails": emails}).encode()
req = urllib.request.Request(
f"{BASE}/verify/bulk",
data=body,
method="POST",
headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=60) as resp:
return json.load(resp)["results"]
def main(src: str, dst: str) -> None:
with open(src, newline="", encoding="utf-8") as f:
emails = list(dict.fromkeys( # de-duplicate, keep order
row[0].strip() for row in csv.reader(f) if row and "@" in row[0]
))
with open(dst, "w", newline="", encoding="utf-8") as f:
out = csv.writer(f)
out.writerow(["email", "status", "reason", "suggestion", "disposable", "role"])
for i in range(0, len(emails), BATCH):
chunk = emails[i : i + BATCH]
try:
results = verify_batch(chunk)
except urllib.error.HTTPError as e:
if e.code == 429:
sys.exit(f"Daily limit reached after {i} addresses; partial results are in {dst}.")
raise
for r in results:
c = r["checks"]
out.writerow([r["email"], r["status"], r["reason"], r["suggestion"] or "",
c["disposable"], c["role"]])
time.sleep(0.5) # be polite between batches
if __name__ == "__main__":
main(sys.argv[1], sys.argv[2])
The script expects the email address in the first column and skips rows without an @, which also skips a header row. The dict.fromkeys trick removes exact duplicates while keeping order; normalize case first if you also want to catch Jane@ and jane@ as the same address.
What to do with bulk results
- Invalid: remove before your next send. These are the addresses most likely to hard-bounce.
- Risky, typo suggestion: don’t silently rewrite someone’s address. For existing subscribers, the safest move is to suppress the address; for recent signups, you might email the suggested address asking them to confirm.
- Risky, disposable or role: segment rather than delete. Send to them only if they’ve engaged.
- Valid: keep, but remember verification can’t spot a mailbox that was closed last month on a catch-all domain. Bounce handling still matters; see processing bounce webhooks.
The broader process of cleaning a large list without wrecking your sender reputation is in bulk email verification.
Production checklist
Timeouts. Real-time calls should time out in a few seconds. Bulk calls can wait longer since DNS lookups for 100 different domains take time.
Caching. The same address submitted twice in a minute doesn’t need two API calls. Cache results briefly by normalized address. Don’t cache for months: domains expire and disposable lists change.
Rate limits. Treat HTTP 429 as “stop for now”, not “retry immediately”. For bulk jobs, persist your progress so you can resume.
Logging. Store the status and reason alongside the address with a timestamp. When a valid address bounces later, you’ll want to know what the verifier said and when.
Privacy. Email addresses are personal data. Send only the address, use HTTPS, and keep your key in an environment variable or secrets manager.
Don’t over-trust any verdict. Valid means “no reason found to reject”, not “guaranteed to deliver”. Mailboxes close, and catch-all domains accept everything until they bounce.
To see the API’s output for a single address before you write any code, paste it into the free email verifier. Then grab a key from the API documentation.
FAQ
What does an email verification API return?
Typically a status (here, valid, risky, or invalid), a reason code, and the individual checks behind the verdict. The API on this site also returns the domain’s MX hosts and a typo suggestion when one applies. Some providers add an SMTP-based mailbox result, which this API does not.
Should I call an email verification API from the browser?
No. Anything in client-side JavaScript is visible to users, including your API key. Call your own backend endpoint from the browser and have the backend call the verifier with a short timeout.
What should happen if the verification API is down?
Fail open: accept the address, mark it unverified, and re-check it later in a background job. Blocking signups because a third-party check timed out costs you real users.
How many emails can I verify per request?
The bulk endpoint accepts up to 100 addresses per request. Free keys also have a daily limit, and the API returns HTTP 429 once it’s reached, so bulk jobs should stop and resume later.
Does email verification replace double opt-in?
No. Verification filters out addresses that can’t or shouldn’t receive mail. Only a confirmation email proves the person who typed the address controls the inbox, which also protects you from someone signing up with another person’s address.
Free email verifier: syntax, MX, disposable and role-based addresses, typos. Paste up to 100 at once.