Email Validation API Integration: A Developer's Guide
On this page
Why Use a Validation API
Email validation APIs let you programmatically check whether an email address is valid before accepting it into your system or sending to it. Instead of manually uploading CSV files to a verification service, you integrate the check directly into your application.
Common integration points:
- Signup forms (validate before creating an account).
- Contact import flows (validate during bulk import).
- CRM data entry (validate when a sales rep adds a contact).
- Data pipelines (validate as part of an automated processing workflow).
- Checkout pages (validate before processing an order).
Choosing an API
Key evaluation criteria
Accuracy. The percentage of addresses correctly classified as valid, invalid or risky. Test each provider against a known list of valid and invalid addresses.
Speed. Response time per check. For real-time form validation, you need under 2 seconds. For batch processing, throughput (checks per second) matters more.
Checks performed. What does the API actually verify?
| Check | What it does | Speed |
|---|---|---|
| Syntax | Validates email format | Instant |
| DNS/MX | Verifies domain has mail servers | Fast (ms) |
| Disposable detection | Flags temporary email services | Instant |
| Role-based detection | Flags shared inboxes (info@, admin@) | Instant |
| SMTP verification | Connects to server, checks if mailbox exists | 1-5 seconds |
| Catch-all detection | Identifies domains that accept all addresses | 1-5 seconds |
| Spam trap detection | Checks against known spam trap databases | Varies |
| Free provider detection | Flags Gmail, Yahoo, Outlook, etc. | Instant |
Rate limits. How many requests per second or minute does the API allow? This determines whether it can handle your form traffic or batch processing needs.
Pricing. Most providers charge per verification. Compare cost at your expected volume.
Uptime and reliability. For real-time form validation, the API is in the critical path of user signups. Check the provider's SLA and status page history.
Providers with APIs
| Provider | Single check | Bulk API | Webhook | Starting price |
|---|---|---|---|---|
| ZeroBounce | Yes | Yes | Yes | ~$0.008/check |
| NeverBounce | Yes | Yes | Yes | ~$0.008/check |
| Emailable | Yes | Yes | Yes | ~$0.007/check |
| BriteVerify | Yes | Yes | No | ~$0.01/check |
| Bouncer | Yes | Yes | Yes | ~$0.008/check |
| MillionVerifier | Yes | Yes | No | ~$0.003/check |
| Kickbox | Yes | Yes | Yes | ~$0.01/check |
Pricing varies by volume. Most providers offer lower per-check rates at higher volumes.
Single-Check Integration
Request format
Most APIs accept a simple GET or POST request:
GET https://api.verificationservice.example.com/v1/verify?email=user@example.com
Authorization: Bearer YOUR_API_KEY
Or:
POST https://api.verificationservice.example.com/v1/verify
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY
{
"email": "user@example.com"
}
Response format
A typical response:
{
"email": "user@example.com",
"result": "valid",
"reason": "mailbox_exists",
"disposable": false,
"role": false,
"free": false,
"catch_all": false,
"mx_found": true,
"smtp_check": true,
"score": 95,
"did_you_mean": null
}
Result categories
| Result | Meaning | Action |
|---|---|---|
| valid | Mailbox exists and accepts mail | Accept |
| invalid | Mailbox does not exist or domain has no MX | Reject |
| catch_all | Domain accepts all addresses (cannot confirm individual mailbox) | Accept with caution |
| disposable | Temporary email service | Reject (for signups) or accept (for content downloads) |
| role | Shared inbox (info@, admin@) | Accept but flag |
| unknown | Could not determine (timeout, greylisting) | Accept and recheck later |
Python implementation
import requests
import time
API_KEY = "your_api_key"
API_URL = "https://api.verificationservice.example.com/v1/verify"
def verify_email(email, timeout=5, retries=2):
"""Verify a single email address."""
for attempt in range(retries):
try:
response = requests.get(
API_URL,
params={"email": email},
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=timeout
)
response.raise_for_status()
data = response.json()
return {
"email": email,
"valid": data["result"] == "valid",
"result": data["result"],
"disposable": data.get("disposable", False),
"role": data.get("role", False),
"catch_all": data.get("catch_all", False),
}
except requests.exceptions.Timeout:
if attempt < retries - 1:
time.sleep(1)
continue
# Fail open: accept the address and verify later
return {
"email": email,
"valid": True,
"result": "timeout",
"disposable": False,
"role": False,
"catch_all": False,
}
except requests.exceptions.RequestException as e:
if attempt < retries - 1:
time.sleep(1)
continue
return {
"email": email,
"valid": True, # Fail open
"result": "error",
"error": str(e),
}
JavaScript implementation (Node.js)
const axios = require('axios');
const API_KEY = 'your_api_key';
const API_URL = 'https://api.verificationservice.example.com/v1/verify';
async function verifyEmail(email, timeout = 5000) {
try {
const response = await axios.get(API_URL, {
params: { email },
headers: { Authorization: `Bearer ${API_KEY}` },
timeout,
});
const data = response.data;
return {
email,
valid: data.result === 'valid',
result: data.result,
disposable: data.disposable || false,
role: data.role || false,
catchAll: data.catch_all || false,
};
} catch (error) {
// Fail open
return {
email,
valid: true,
result: 'error',
error: error.message,
};
}
}
Bulk/Batch Integration
When to use batch
- Verifying existing lists (CRM cleanup, pre-campaign verification).
- Processing extracted email lists (after extraction with Email Extractor).
- Nightly or scheduled verification of new contacts.
Workflow
- Submit a list of addresses to the batch endpoint.
- Receive a batch ID.
- Poll for completion or receive a webhook when done.
- Download results.
Batch submission
def submit_batch(emails):
"""Submit a list of emails for batch verification."""
response = requests.post(
f"{API_URL}/batch",
json={"emails": emails},
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=30
)
response.raise_for_status()
return response.json()["batch_id"]
Polling for results
def check_batch(batch_id, max_attempts=60, interval=10):
"""Poll for batch completion."""
for _ in range(max_attempts):
response = requests.get(
f"{API_URL}/batch/{batch_id}",
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=10
)
data = response.json()
if data["status"] == "completed":
return data["results"]
elif data["status"] == "failed":
raise Exception(f"Batch failed: {data.get('error')}")
time.sleep(interval)
raise Exception("Batch verification timed out")
Webhook processing
Instead of polling, configure a webhook URL that the API calls when the batch is done:
from flask import Flask, request
app = Flask(__name__)
@app.route('/webhook/verification', methods=['POST'])
def verification_webhook():
data = request.json
batch_id = data['batch_id']
results = data['results']
# Process results
valid = [r for r in results if r['result'] == 'valid']
invalid = [r for r in results if r['result'] == 'invalid']
# Update your database
# ...
return {'status': 'received'}, 200
Best Practices
Fail open for real-time checks
If the validation API is down or slow, accept the email address and verify it later in batch. Blocking form submissions because a third-party API is unavailable loses you real leads.
Cache results
Cache verification results for a reasonable period (24-72 hours). If the same address is submitted again, use the cached result instead of making another API call.
from functools import lru_cache
from datetime import datetime, timedelta
# Simple in-memory cache
verification_cache = {}
def verify_with_cache(email, cache_hours=24):
email_lower = email.lower()
cached = verification_cache.get(email_lower)
if cached and cached['expires'] > datetime.now():
return cached['result']
result = verify_email(email)
verification_cache[email_lower] = {
'result': result,
'expires': datetime.now() + timedelta(hours=cache_hours)
}
return result
Rate limiting
Respect the API's rate limits. Implement exponential backoff on 429 (Too Many Requests) responses:
def verify_with_backoff(email, max_retries=3):
for attempt in range(max_retries):
result = verify_email(email)
if result.get('result') != 'rate_limited':
return result
wait = 2 ** attempt # 1, 2, 4 seconds
time.sleep(wait)
return {'email': email, 'valid': True, 'result': 'rate_limited'}
Handle all result types
Do not just check for "valid" and "invalid." Handle every result type the API can return:
def should_accept(result):
"""Decide whether to accept an email based on verification result."""
status = result['result']
if status == 'valid':
return True
elif status == 'invalid':
return False
elif status == 'catch_all':
return True # Accept but flag for monitoring
elif status == 'disposable':
return False # Or True, depending on use case
elif status == 'role':
return True # Accept but exclude from cold outreach
elif status == 'unknown':
return True # Accept and recheck later
elif status in ('timeout', 'error'):
return True # Fail open
else:
return True # Unknown status, fail open
Log everything
Log every verification request and result. This data helps you:
- Track API costs.
- Identify patterns in invalid addresses (bad data sources).
- Debug issues when addresses that were verified as valid still bounce.
Separate validation from business logic
Keep your validation function separate from the decision about what to do with the result. One application might reject disposable addresses (signup form), while another accepts them (content download).
Integration with Email Extractor
When processing extracted email lists:
- Extract and deduplicate emails from your source files using Email Extractor.
- Download the CSV results.
- Feed the CSV into your batch verification pipeline.
- Process the verification results.
- Import only valid addresses into your CRM or email platform.
This creates an automated pipeline from raw data to a verified, ready-to-use email list.