You've decided to add email verification to your application. Good call — it's the single highest-impact change you can make to improve signup quality, reduce bounces, and prevent fraud.
This guide gives you production-ready code for integrating email verification into any stack. Python, JavaScript, PHP, cURL — pick your language and copy the code.
Why Server-Side Verification Matters
Before we write any code, let's be clear about one thing: client-side email validation is not enough.
// This is NOT verification — it's just syntax checking
const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
// "thisdoesnotexist8472@gmail.com" → true (syntax is fine, mailbox doesn't exist)
Client-side regex catches formatting errors. It doesn't tell you whether the mailbox actually exists. For that, you need SMTP-level verification — and that requires a server-side API call.
Getting Your API Key
First, sign up at email-validaton.com and grab your API key from the dashboard. Free accounts get 100 verifications/month.
Keep your API key secret — it should never appear in client-side JavaScript. Always call the verification API from your backend.
Single Email Verification
Python
import requests
from typing import Optional
def verify_email(email: str, api_key: str) -> dict:
"""Verify a single email address."""
resp = requests.get(
"https://email-validaton.com/api/v1/verify",
params={"email": email},
headers={"Authorization": f"Bearer {api_key}"},
timeout=15,
)
resp.raise_for_status()
return resp.json()
# Usage
result = verify_email("user@example.com", "YOUR_API_KEY")
print(f"Status: {result['status']}")
print(f"Score: {result['score']}/10")
print(f"Confidence: {result.get('confidence', 'N/A')}")
if result["status"] == "valid":
# Proceed with account creation
create_user(email)
elif result["status"] in ("invalid", "disposable"):
# Reject or flag
raise ValueError(f"Invalid email: {result['reason']}")
JavaScript (Node.js)
async function verifyEmail(email, apiKey) {
const url = new URL("https://email-validaton.com/api/v1/verify");
url.searchParams.set("email", email);
const resp = await fetch(url, {
headers: { Authorization: `Bearer ${apiKey}` },
});
if (!resp.ok) throw new Error(`API error: ${resp.status}`);
return resp.json();
}
// Usage
const result = await verifyEmail("user@example.com", process.env.EV_API_KEY);
console.log(`Status: ${result.status}, Score: ${result.score}/10`);
if (result.status === "valid") {
await db.users.insert({ email, verified: true });
} else {
throw new Error(result.reason || "Email verification failed");
}
PHP
function verify_email(string $email, string $api_key): array {
$url = "https://email-validaton.com/api/v1/verify?email=" . urlencode($email);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $api_key"],
CURLOPT_TIMEOUT => 15,
]);
$body = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code !== 200) {
throw new RuntimeException("API returned $http_code");
}
return json_decode($body, true);
}
// Usage
$result = verify_email("user@example.com", getenv("EV_API_KEY"));
echo "Status: {$result['status']}, Score: {$result['score']}/10\n";
cURL (CLI / Shell Scripts)
#!/bin/bash
API_KEY="your_api_key"
EMAIL="user@example.com"
curl -s -H "Authorization: Bearer $API_KEY" \
"https://email-validaton.com/api/v1/verify?email=$EMAIL" | python3 -m json.tool
Understanding the Response
Every verification returns a consistent JSON structure:
{
"email": "user@gmail.com",
"status": "valid",
"score": 9,
"confidence": "high",
"reason": "Mailbox exists and accepts mail",
"mx_server": "gmail-smtp-in.l.google.com",
"duration_ms": 287,
"disposable": false,
"role_account": false,
"free_provider": true
}
Key Fields Explained
| Field | Values | Meaning |
|---|---|---|
status |
valid, invalid, catch_all, unknown, risky, disposable, over_quota, retry, error |
The verification result |
score |
0–10 | Composite quality score. 8+ = excellent, 5-7 = moderate, <5 = risky |
confidence |
high, medium, low |
How reliable the result is |
reason |
Human-readable string | Why the status was assigned |
disposable |
boolean | Temporary email address |
role_account |
boolean | info@, support@, admin@, etc. |
free_provider |
boolean | Gmail, Yahoo, Outlook, etc. |
Integration Patterns
Pattern 1: Registration Form Validation (Most Common)
@app.route("/register", methods=["POST"])
def register():
email = request.form["email"]
password = request.form["password"]
# 1. Syntax check (fast, local)
if "@" not in email or "." not in email.split("@")[-1]:
return jsonify({"error": "Invalid email format"}), 400
# 2. SMTP verification (API call)
try:
result = verify_email(email, current_app.config["EV_API_KEY"])
except requests.RequestException:
# API is down — don't block signups, just flag for review
create_user(email, password, flags=["unverified"])
return redirect(url_for("dashboard"))
# 3. Business logic based on result
if result["status"] == "valid":
create_user(email, password, verified=True)
return redirect(url_for("welcome"))
if result["status"] == "disposable":
return jsonify({
"error": "Please use a permanent email address"
}), 400
if result["status"] == "invalid":
return jsonify({
"error": "This email address doesn't appear to exist"
}), 400
# Catch-all, unknown, risky — allow but flag
create_user(email, password, flags=[result["status"]])
return redirect(url_for("welcome"))
Pattern 2: Async Verification (Non-Blocking)
For production at scale, you don't want users waiting for the API call:
import threading
def verify_async(email: str, api_key: str, user_id: int):
"""Verify email in background and update user record."""
try:
result = verify_email(email, api_key)
db.users.update(user_id, {
"email_status": result["status"],
"email_score": result["score"],
"verified_at": datetime.utcnow(),
})
except Exception as e:
logger.error(f"Background verification failed for {user_id}: {e}")
# In your registration handler:
user = create_user(email, password)
threading.Thread(
target=verify_async,
args=(email, api_key, user.id),
daemon=True,
).start()
Pattern 3: Bulk List Processing
For cleaning large lists via the bulk upload endpoint:
import requests
import time
def bulk_verify(file_path: str, api_key: str) -> str:
"""Upload a file for bulk verification. Returns job ID."""
with open(file_path, "rb") as f:
resp = requests.post(
"https://email-validaton.com/api/v1/bulk",
files={"file": f},
headers={"Authorization": f"Bearer {api_key}"},
)
return resp.json()["job_id"]
def wait_for_results(job_id: str, api_key: str, poll_sec: int = 5):
"""Poll until the bulk job completes, then download results."""
while True:
resp = requests.get(
f"https://email-validaton.com/api/v1/bulk/status/{job_id}",
headers={"Authorization": f"Bearer {api_key}"},
)
data = resp.json()
if data["status"] in ("completed", "failed", "cancelled"):
return data
time.sleep(poll_sec)
# Usage
job_id = bulk_verify("my_list.csv", API_KEY)
result = wait_for_results(job_id, API_KEY)
# Download the result file
download = requests.get(result["result_url"])
Error Handling & Best Practices
Timeout Handling
The verification API typically responds in 200-800ms. Set a timeout of 10-15 seconds to handle network issues gracefully.
Rate Limiting
Free accounts are limited to 100/day. Paid plans range from 5,000 to 500,000/month. The API returns 429 Too Many Requests when you exceed your limit.
Retry Logic
For transient failures (network errors, 503), retry up to 3 times with exponential backoff:
import time
from requests.exceptions import RequestException
def verify_with_retry(email, api_key, max_retries=3):
for attempt in range(max_retries):
try:
return verify_email(email, api_key)
except RequestException:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt)
Do Not Block Signups on API Failure
If the verification API is temporarily unavailable, do not block user registration. Instead:
1. Allow signup, flag the account as "pending verification"
2. Run verification asynchronously when the API recovers
3. Notify the user if their email is invalid after the fact
Blocking signups on API downtime loses real users.
Conclusion
Email verification is not optional for production applications. Invalid emails cause bounces, disposable emails enable abuse, and unverified lists damage your sender reputation.
The integration patterns in this guide cover the most common use cases. Copy the code, adapt it to your stack, and start verifying every email that enters your system.
Ready to integrate? Get your free API key — 100 verifications included.