Introduction
Overview
EmailVerify Pro is a high-accuracy email verification API that processes 45+ signals per email
to determine deliverability. Built for B2B sales teams, data enrichment pipelines, and marketing operations —
used for cleaning outbound sales lists, reducing bounce rates, and enriching contact databases.
Achieves 99.4% accuracy on Fortune 1000 domains. Available as a hosted API at
emailverifypro.com or self-hosted on your own infrastructure.
- 45+ signals per email — syntax, DNS, SMTP, RBL, breach data, and ML scoring
- Bulk verification up to 5,000 emails synchronously
- Email finder by name + domain (~70%+ hit rate on Fortune 1000)
- Suppression list management — maintain opt-out and bounce lists
- Async jobs for large lists that exceed synchronous limits
- Bounce feedback loop for continuous ML model training
Authentication
All API requests require an API key. EmailVerify Pro supports two authentication methods. The header method is preferred because query string parameters may appear in server logs.
Method 1: Request Header RECOMMENDED
Pass your API key in the X-API-Key request header.
Method 2: Query String
Append api_key as a URL query parameter.
# Header method (preferred)
curl -H "X-API-Key: evp_live_abc123" \
"http://52.87.60.126:8000/validate/checks?email=test@example.com"
# Query string method
curl "http://52.87.60.126:8000/validate/checks?email=test@example.com&api_key=evp_live_abc123"
Get your API key at /app → API Keys tab. All keys start with the prefix
evp_live_. Do not share your API key or commit it to source control.
If your key is missing, invalid, or revoked, the API returns HTTP 401:
// HTTP 401 Unauthorized
{
"detail": "Invalid or revoked API key"
}
Base URL & Limits
Base URLs
| Environment | Base URL |
|---|---|
| Primary | http://52.87.60.126:8000 |
| Alias | https://emailverifypro.com |
Rate Limits
| Plan | Requests per Minute | Monthly | Notes |
|---|---|---|---|
| Free | 200 | 10,000 | Credit card not required |
| Pro | 2,000 | 500,000 | Priority queue |
| Enterprise | Unlimited | Custom | Dedicated infrastructure |
Performance
| Metric | Value |
|---|---|
| p50 response time | 450ms |
| p95 response time | 2.1s |
| p99 response time | 4.5s |
Response times are primarily bounded by the destination mail server's SMTP response latency, which varies by provider and cannot be optimized away.
Cache TTL
Repeated requests for the same email address return a cached result. Cache hit is indicated
by "cached": true in the response and does not count against your rate limit.
| Result | TTL |
|---|---|
valid / invalid |
7 days |
accept_all (catch-all) |
6 months |
unknown |
24 hours |
Quick Start
30-Second Example
Verify your first email in 30 seconds. The
/validate/checks endpoint runs all 45+ signals and returns a normalized result
optimized for direct database insertion.
curl -X GET \
"http://52.87.60.126:8000/validate/checks?email=john@example.com" \
-H "X-API-Key: evp_live_your_key_here"
import requests
response = requests.get(
"http://52.87.60.126:8000/validate/checks",
params={"email": "john@example.com"},
headers={"X-API-Key": "evp_live_your_key_here"}
)
data = response.json()
print(data["checks"]["deliverable"]) # True or False
print(data["checks"]["score"]) # 0–100
print(data["checks"]["grade"]) # A, B, C, D, or F
const response = await fetch(
'http://52.87.60.126:8000/validate/checks?email=john@example.com',
{ headers: { 'X-API-Key': 'evp_live_your_key_here' } }
);
const data = await response.json();
console.log(data.checks.deliverable); // true or false
console.log(data.checks.score); // 0–100
console.log(data.checks.grade); // 'A', 'B', 'C', 'D', or 'F'
Response
A successful request returns HTTP 200 with a JSON body. All fields are always present — there are no optional fields to guard against.
{
"checks": {
"email": "john@example.com", // normalized email address
"deliverable": true, // primary send/no-send signal
"can_send": true, // alias for deliverable
"status": "valid", // valid | invalid | accept_all | unknown
"score": 87, // ML confidence 0–100
"grade": "B", // letter grade: A B C D F
"is_catch_all": false, // domain accepts all addresses
"is_role_account": false, // e.g. info@, support@, noreply@
"is_disposable": false, // temporary / throwaway provider
"is_free_provider": false, // gmail, yahoo, outlook, etc.
"mx_valid": true, // domain has valid MX records
"smtp_verified": true, // mailbox confirmed via SMTP RCPT TO
"cached": false // true = served from cache, not billed
}
}
Use deliverable (or its alias can_send) as your primary gate.
Use score and grade for tiered segmentation — e.g. send immediately
to grade A/B, suppress grade D/F, and review grade C manually.
Single Email Verification
GET /validate/checks RECOMMENDED
The recommended endpoint for most use cases. Returns a normalized
checks object optimized for database storage and downstream
processing. Runs syntax, DNS, SMTP, RBL checks, and ML scoring. Results
are cached for 7 days (valid/invalid) or 6 months (catch-all).
| Name | Type | Required | Description |
|---|---|---|---|
string |
Yes | Email address to verify | |
| smtp | boolean |
No (default: true) |
Run SMTP verification. Set to false for DNS-only (faster, less accurate) |
| rbl | boolean |
No (default: true) |
Check against Real-time Block Lists |
| breach | boolean |
No (default: false) |
Check if email appears in known data breaches (costs extra credit) |
curl -X GET \
"http://52.87.60.126:8000/validate/checks?email=jane@salesforce.com&smtp=true&rbl=true" \
-H "X-API-Key: evp_live_your_key_here"
import requests
r = requests.get(
"http://52.87.60.126:8000/validate/checks",
params={
"email": "jane@salesforce.com",
"smtp": True,
"rbl": True,
},
headers={"X-API-Key": "evp_live_your_key_here"}
)
result = r.json()
checks = result["checks"]
# Use can_send for sending decision
if checks["can_send"]:
print(f"Safe to send — score {checks['score']} ({checks['grade']})")
else:
print(f"Do not send — status: {checks['status']}")
const params = new URLSearchParams({
email: 'jane@salesforce.com',
smtp: 'true',
rbl: 'true'
});
const res = await fetch(
`http://52.87.60.126:8000/validate/checks?${params}`,
{ headers: { 'X-API-Key': 'evp_live_your_key_here' } }
);
const { checks } = await res.json();
if (checks.can_send) {
console.log(`Safe to send — score ${checks.score} (${checks.grade})`);
} else {
console.log(`Do not send — status: ${checks.status}`);
}
Response
{
"checks": {
"email": "jane@salesforce.com", // Normalized input address
"deliverable": true, // SMTP accepted the address
"can_send": true, // Safe to send: deliverable and not blocklisted
"status": "valid", // valid | invalid | catch_all | unknown | risky
"score": 94, // 0–100 deliverability confidence score
"grade": "A", // A / B / C / D / F letter grade
"score_breakdown": {
"base": 50, // Starting baseline
"smtp_bonus": 30, // Added when SMTP accepted the address
"mx_bonus": 10, // Added when valid MX records found
"rbl_penalty": 0, // Deducted for each RBL hit
"disposable_penalty": 0, // Deducted for disposable/temp domain
"role_penalty": 0, // Deducted for role-based address (info@, admin@)
"ml_adjustment": 4 // ML model fine-tuning adjustment (-20 to +20)
},
"is_catch_all": false, // Domain accepts all addresses — individual not confirmed
"is_role_account": false, // Role-based address (info@, support@, noreply@)
"is_disposable": false, // Temporary/throwaway email provider
"is_free_provider": false, // Free consumer provider (Gmail, Yahoo, Outlook)
"is_subaddress": false, // Plus-addressed (user+tag@domain.com)
"is_corporate": true, // Custom business domain (not a free provider)
"mx_valid": true, // At least one MX record resolves
"mx_records": ["mta1.salesforce.com", "mta2.salesforce.com"], // All MX records found
"spf_valid": true, // Domain has a valid SPF record
"dkim_present": true, // DKIM selector found in DNS
"smtp_verified": true, // SMTP RCPT TO accepted
"smtp_status": "accepted", // accepted | rejected | timeout | error | skipped
"smtp_response_code": 250, // Raw SMTP response code from mail server
"smtp_banner": "220 mta1.salesforce.com ESMTP", // Server greeting banner
"rbl_listed": false, // Domain/IP appears on a blocklist
"rbl_lists_checked": 12, // Number of RBL feeds checked
"breach_found": null, // null if breach check not requested; true/false otherwise
"domain": "salesforce.com", // Extracted domain
"provider": "Salesforce", // Detected provider/brand name if known
"tld": "com", // Top-level domain
"cached": false, // true if result served from cache
"verified_at": "2026-06-18T14:23:11Z" // ISO 8601 timestamp of verification
}
}
GET /validate
Returns the raw, unprocessed verification result with full verbose output.
Includes all intermediate SMTP handshake data, DNS resolution details, and
ML feature vectors. Use this when you need the complete diagnostic picture.
For production data pipelines, prefer /validate/checks.
| Name | Type | Required | Description |
|---|---|---|---|
string |
Yes | Email address to verify | |
| smtp | boolean |
No (default: true) |
Run SMTP verification. Set to false for DNS-only (faster, less accurate) |
| rbl | boolean |
No (default: true) |
Check against Real-time Block Lists |
| breach | boolean |
No (default: false) |
Check if email appears in known data breaches (costs extra credit) |
curl -X GET \
"http://52.87.60.126:8000/validate?email=jane@salesforce.com" \
-H "X-API-Key: evp_live_your_key_here"
import requests
r = requests.get(
"http://52.87.60.126:8000/validate",
params={"email": "jane@salesforce.com"},
headers={"X-API-Key": "evp_live_your_key_here"}
)
data = r.json()
const res = await fetch(
'http://52.87.60.126:8000/validate?email=jane@salesforce.com',
{ headers: { 'X-API-Key': 'evp_live_your_key_here' } }
);
const data = await res.json();
This endpoint returns the complete verbose response including intermediate SMTP handshake steps, raw DNS resolution data, and ML feature vectors. For the full field reference, see the Response Reference › Full Response Format section.
GET /validate/quick
Syntax and DNS validation only — no SMTP connection. Completes in under 100ms. Ideal for real-time form validation where you need instant feedback without the latency of a full SMTP check.
When to use
- Form validation on sign-up pages
- Real-time feedback as user types
- Pre-filtering before bulk SMTP verification
| Name | Type | Required | Description |
|---|---|---|---|
string |
Yes | Email to validate |
curl "http://52.87.60.126:8000/validate/quick?email=test@example.com" \
-H "X-API-Key: evp_live_your_key_here"
import requests
r = requests.get(
"http://52.87.60.126:8000/validate/quick",
params={"email": "test@example.com"},
headers={"X-API-Key": "evp_live_your_key_here"}
)
const res = await fetch(
'http://52.87.60.126:8000/validate/quick?email=test@example.com',
{ headers: { 'X-API-Key': 'evp_live_your_key_here' } }
);
Response
{
"email": "test@example.com",
"syntax_valid": true,
"mx_valid": true,
"mx_records": ["mail.example.com"],
"status": "unknown",
"note": "DNS-only check. Use /validate/checks for SMTP verification."
}
GET /validate/fast
Cache lookup only. Returns the cached result if this email was previously
verified, otherwise returns HTTP 404. Sub-50ms response time. Use this as a
first-pass before calling /validate/checks to avoid redundant
SMTP connections on emails you've already verified.
Use /validate/fast before /validate/checks in
high-throughput pipelines to avoid redundant SMTP connections on repeat
emails. Cache TTL: 7 days for valid/invalid results, 6 months for
catch-all results.
| Name | Type | Required | Description |
|---|---|---|---|
string |
Yes | Email to look up in cache |
curl "http://52.87.60.126:8000/validate/fast?email=jane@salesforce.com" \
-H "X-API-Key: evp_live_your_key_here"
import requests
r = requests.get(
"http://52.87.60.126:8000/validate/fast",
params={"email": "jane@salesforce.com"},
headers={"X-API-Key": "evp_live_your_key_here"}
)
if r.status_code == 404:
# Not cached — run full verification
r = requests.get(
"http://52.87.60.126:8000/validate/checks",
params={"email": "jane@salesforce.com"},
headers={"X-API-Key": "evp_live_your_key_here"}
)
data = r.json()
let res = await fetch(
'http://52.87.60.126:8000/validate/fast?email=jane@salesforce.com',
{ headers: { 'X-API-Key': 'evp_live_your_key_here' } }
);
if (res.status === 404) {
// Not in cache — run full check
res = await fetch(
'http://52.87.60.126:8000/validate/checks?email=jane@salesforce.com',
{ headers: { 'X-API-Key': 'evp_live_your_key_here' } }
);
}
const data = await res.json();
Responses
| Status | Description |
|---|---|
200 |
Returns a checks object identical to
/validate/checks, with "cached": true
|
404 |
Email not found in cache — run /validate/checks for a
full verification.
|
Bulk Verification
POST /validate/bulk/turbo RECOMMENDED
The recommended bulk endpoint. Verifies up to 5,000 emails synchronously with a cache-first strategy — cached results are returned in microseconds, making repeat verifications up to 50x faster. Optimal for list cleaning workflows.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
emails |
array of strings | Yes | List of email addresses (max 5,000) |
smtp |
boolean | No (default: true) |
Run SMTP for uncached emails |
deduplicate |
boolean | No (default: true) |
Remove duplicates before processing |
Code Examples
# POST /validate/bulk/turbo — synchronous, cache-first
curl -X POST "http://52.87.60.126:8000/validate/bulk/turbo" \
-H "X-API-Key: evp_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"emails": ["alice@example.com", "bob@salesforce.com", "invalid@@bad.com"],
"smtp": true,
"deduplicate": true
}'
import requests
r = requests.post(
"http://52.87.60.126:8000/validate/bulk/turbo",
headers={
"X-API-Key": "evp_live_your_key_here",
"Content-Type": "application/json"
},
json={
"emails": ["alice@example.com", "bob@salesforce.com", "invalid@@bad.com"],
"smtp": True,
"deduplicate": True
}
)
data = r.json()
for result in data["results"]:
checks = result["checks"]
print(f"{checks['email']}: {checks['status']} (score {checks['score']})")
const res = await fetch('http://52.87.60.126:8000/validate/bulk/turbo', {
method: 'POST',
headers: {
'X-API-Key': 'evp_live_your_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
emails: ['alice@example.com', 'bob@salesforce.com', 'invalid@@bad.com'],
smtp: true,
deduplicate: true
})
});
const data = await res.json();
data.results.forEach(r => {
const c = r.checks;
console.log(`${c.email}: ${c.status} (score ${c.score})`);
});
Response
{
"results": [
{
"checks": {
"email": "alice@example.com",
"deliverable": true,
"can_send": true,
"status": "valid",
"score": 88,
"grade": "B",
"cached": true
}
},
{
"checks": {
"email": "bob@salesforce.com",
"deliverable": true,
"can_send": true,
"status": "valid",
"score": 96,
"grade": "A",
"cached": false
}
},
{
"checks": {
"email": "invalid@@bad.com",
"deliverable": false,
"can_send": false,
"status": "invalid_syntax",
"score": 0,
"grade": "F",
"cached": false
}
}
],
"stats": {
"total": 3,
"deduplicated": 0,
"cache_hits": 1,
"fresh_verified": 2,
"elapsed_seconds": 1.24,
"emails_per_second": 2.42
}
}
POST /validate/bulk/async
For lists larger than 5,000 emails. Submits a job and returns immediately with a job_id. Poll /jobs/{job_id} to check progress and retrieve results when complete. Optionally provide a webhook_url to receive a POST notification when the job finishes.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
emails |
array of strings | Yes | Email addresses to verify (no hard limit) |
smtp |
boolean | No (default: true) |
Enable SMTP verification |
deduplicate |
boolean | No (default: true) |
Remove duplicates |
webhook_url |
string | No | URL to POST results to when job completes |
Code Examples
curl -X POST "http://52.87.60.126:8000/validate/bulk/async" \
-H "X-API-Key: evp_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"emails": ["user1@domain.com", "user2@domain.com"],
"smtp": true,
"deduplicate": true,
"webhook_url": "https://yourapp.com/webhooks/evp"
}'
r = requests.post(
"http://52.87.60.126:8000/validate/bulk/async",
headers={"X-API-Key": "evp_live_your_key_here"},
json={
"emails": ["user1@domain.com", "user2@domain.com"],
"smtp": True,
"deduplicate": True,
"webhook_url": "https://yourapp.com/webhooks/evp"
}
)
job = r.json()
print(f"Job submitted: {job['job_id']}")
const res = await fetch('http://52.87.60.126:8000/validate/bulk/async', {
method: 'POST',
headers: {
'X-API-Key': 'evp_live_your_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
emails: ['user1@domain.com', 'user2@domain.com'],
smtp: true,
deduplicate: true,
webhook_url: 'https://yourapp.com/webhooks/evp'
})
});
const { job_id } = await res.json();
console.log('Job submitted:', job_id);
Response
{
"job_id": "job_8f3a2b1c9d4e5f6a",
"status": "queued",
"total": 2,
"submitted_at": "2026-06-18T14:30:00Z",
"estimated_seconds": 4
}
POST /validate/bulk
Basic synchronous bulk endpoint. Processes up to 100 emails. No cache optimization, no deduplication. Use /validate/bulk/turbo for all new integrations — this endpoint exists for backward compatibility.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
emails |
array | Yes | Max 100 emails |
smtp |
boolean | No (default: true) |
Enable SMTP |
This endpoint is maintained for backward compatibility only. New integrations should use /validate/bulk/turbo, which adds cache optimization, deduplication, and supports up to 5,000 emails per request.
Code Examples
curl -X POST "http://52.87.60.126:8000/validate/bulk" \
-H "X-API-Key: evp_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"emails": ["a@example.com", "b@example.com"], "smtp": true}'
r = requests.post(
"http://52.87.60.126:8000/validate/bulk",
headers={"X-API-Key": "evp_live_your_key_here"},
json={"emails": ["a@example.com", "b@example.com"], "smtp": True}
)
results = r.json()["results"]
const res = await fetch('http://52.87.60.126:8000/validate/bulk', {
method: 'POST',
headers: { 'X-API-Key': 'evp_live_your_key_here', 'Content-Type': 'application/json' },
body: JSON.stringify({ emails: ['a@example.com', 'b@example.com'], smtp: true })
});
const { results } = await res.json();
Async Jobs
GET /jobs/{job_id}
Check the status of an async bulk job. Poll every 5–10 seconds for long-running jobs. When status is "complete", the full results array is included in the response.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
job_id |
string | Yes | Job ID returned by /validate/bulk/async |
Job Status Values
| Status | Meaning |
|---|---|
queued |
Job is waiting to be processed |
running |
Job is actively being processed |
complete |
All emails verified; results available |
failed |
Job failed — retry with /jobs/{job_id}/retry-dead-letter |
Code Examples
curl "http://52.87.60.126:8000/jobs/job_8f3a2b1c9d4e5f6a" \
-H "X-API-Key: evp_live_your_key_here"
import time
job_id = "job_8f3a2b1c9d4e5f6a"
while True:
r = requests.get(
f"http://52.87.60.126:8000/jobs/{job_id}",
headers={"X-API-Key": "evp_live_your_key_here"}
)
job = r.json()
print(f"Status: {job['status']} — {job['pct_complete']:.0f}% complete")
if job["status"] in ("complete", "failed"):
break
time.sleep(5)
if job["status"] == "complete":
results = job["results"]
const pollJob = async (jobId) => {
while (true) {
const res = await fetch(`http://52.87.60.126:8000/jobs/${jobId}`, {
headers: { 'X-API-Key': 'evp_live_your_key_here' }
});
const job = await res.json();
console.log(`Status: ${job.status} — ${job.pct_complete.toFixed(0)}% complete`);
if (job.status === 'complete' || job.status === 'failed') return job;
await new Promise(r => setTimeout(r, 5000));
}
};
const job = await pollJob('job_8f3a2b1c9d4e5f6a');
Response — In Progress
{
"job_id": "job_8f3a2b1c9d4e5f6a",
"status": "running",
"total": 1000,
"progress": 420,
"pct_complete": 42.0,
"started_at": "2026-06-18T14:30:02Z",
"results": null
}
Response — Complete
{
"job_id": "job_8f3a2b1c9d4e5f6a",
"status": "complete",
"total": 1000,
"progress": 1000,
"pct_complete": 100.0,
"started_at": "2026-06-18T14:30:02Z",
"completed_at": "2026-06-18T14:42:18Z",
"results": [ "...array of checks objects..." ]
}
POST /jobs/{job_id}/retry-dead-letter
Re-queues any emails from the job that failed verification (timed out, server errors, greylisted). Returns a new job_id for the retry job. Useful for catching emails that failed due to transient server issues.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
job_id |
string | Yes | Original job ID |
Code Examples
curl -X POST \
"http://52.87.60.126:8000/jobs/job_8f3a2b1c9d4e5f6a/retry-dead-letter" \
-H "X-API-Key: evp_live_your_key_here"
r = requests.post(
f"http://52.87.60.126:8000/jobs/{job_id}/retry-dead-letter",
headers={"X-API-Key": "evp_live_your_key_here"}
)
retry_job = r.json()
print(f"Retry job: {retry_job['job_id']} ({retry_job['total']} emails re-queued)")
const res = await fetch(
`http://52.87.60.126:8000/jobs/${jobId}/retry-dead-letter`,
{ method: 'POST', headers: { 'X-API-Key': 'evp_live_your_key_here' } }
);
const retry = await res.json();
console.log(`Retry job: ${retry.job_id} (${retry.total} emails re-queued)`);
Response
{
"job_id": "job_9a1b2c3d4e5f6a7b",
"status": "queued",
"total": 47,
"original_job_id": "job_8f3a2b1c9d4e5f6a"
}
Email Finder
POST /find-email/v2
Find a corporate email address by first name, last name, and company domain. Uses pattern inference, MX analysis, and SMTP verification to identify the most likely valid email format. ~70%+ hit rate on Fortune 1000 companies. Costs 5 credits per successful find.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
first_name |
string | Yes | Contact's first name |
last_name |
string | Yes | Contact's last name |
domain |
string | Yes | Company domain (e.g. salesforce.com) |
company_name |
string | No | Company name for additional context |
do_smtp |
boolean | No (default: true) |
SMTP-verify discovered candidates |
Code Examples
curl -X POST \
"http://52.87.60.126:8000/find-email/v2?first_name=Jane&last_name=Smith&domain=salesforce.com&do_smtp=true" \
-H "X-API-Key: evp_live_your_key_here"
r = requests.post(
"http://52.87.60.126:8000/find-email/v2",
params={
"first_name": "Jane",
"last_name": "Smith",
"domain": "salesforce.com",
"do_smtp": True
},
headers={"X-API-Key": "evp_live_your_key_here"}
)
result = r.json()
print(f"Best email: {result['best_email']} ({result['best_confidence']:.0%} confidence)")
const params = new URLSearchParams({
first_name: 'Jane', last_name: 'Smith',
domain: 'salesforce.com', do_smtp: 'true'
});
const res = await fetch(
`http://52.87.60.126:8000/find-email/v2?${params}`,
{ method: 'POST', headers: { 'X-API-Key': 'evp_live_your_key_here' } }
);
const result = await res.json();
console.log(`Best: ${result.best_email} (${(result.best_confidence * 100).toFixed(0)}%)`);
Response
{
"best_email": "jane.smith@salesforce.com",
"best_confidence": 0.94,
"domain_format": "{first}.{last}",
"domain_format_confidence": 0.91,
"candidates": [
{
"email": "jane.smith@salesforce.com",
"confidence": 0.94,
"format_type": "first.last",
"verified": true,
"smtp_status": "accepted"
},
{
"email": "jsmith@salesforce.com",
"confidence": 0.61,
"format_type": "flast",
"verified": false,
"smtp_status": "rejected"
},
{
"email": "j.smith@salesforce.com",
"confidence": 0.48,
"format_type": "f.last",
"verified": false,
"smtp_status": "unknown"
}
],
"find_time_ms": 1842,
"credits_used": 5
}
Suppression List
GET /suppression/check
Check whether an email is on your suppression list before sending. Always check this before dispatching outbound email. Suppressed emails include hard bounces, spam complaints, and manually added addresses.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Email to check |
Code Examples
curl "http://52.87.60.126:8000/suppression/check?email=unsubscribed@example.com" \
-H "X-API-Key: evp_live_your_key_here"
r = requests.get(
"http://52.87.60.126:8000/suppression/check",
params={"email": "unsubscribed@example.com"},
headers={"X-API-Key": "evp_live_your_key_here"}
)
data = r.json()
if data["should_never_send"]:
print("BLOCKED — do not send to this address")
const res = await fetch(
'http://52.87.60.126:8000/suppression/check?email=unsubscribed@example.com',
{ headers: { 'X-API-Key': 'evp_live_your_key_here' } }
);
const data = await res.json();
if (data.should_never_send) console.log('BLOCKED');
Response
{
"email": "unsubscribed@example.com",
"is_suppressed": true,
"reason": "hard_bounce",
"should_never_send": true,
"suppressed_at": "2026-05-10T09:14:22Z",
"expires_at": null
}
POST /suppression/add
Add an email address to your suppression list. Accepts a reason and optional source system identifier. Suppressions can optionally auto-expire after a set number of days; omit expires_in_days for a permanent entry.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Email to suppress |
reason |
string | Yes | One of: hard_bounce, soft_bounce, spam_complaint, unsubscribe, manual |
source |
string | No | Source system (e.g. "sendgrid_webhook", "manual") |
expires_in_days |
integer | No | Auto-remove after N days. Omit for permanent suppression |
Code Examples
curl -X POST "http://52.87.60.126:8000/suppression/add" \
-H "X-API-Key: evp_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"email": "bounced@example.com",
"reason": "hard_bounce",
"source": "sendgrid_webhook"
}'
r = requests.post(
"http://52.87.60.126:8000/suppression/add",
headers={"X-API-Key": "evp_live_your_key_here"},
json={
"email": "bounced@example.com",
"reason": "hard_bounce",
"source": "sendgrid_webhook"
}
)
const res = await fetch('http://52.87.60.126:8000/suppression/add', {
method: 'POST',
headers: { 'X-API-Key': 'evp_live_your_key_here', 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'bounced@example.com',
reason: 'hard_bounce',
source: 'sendgrid_webhook'
})
});
Response
{ "status": "ok", "message": "Email added to suppression list" }
DELETE /suppression/{email}
Remove an email from the suppression list. URL-encode the @ symbol as %40 in the path segment.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Email to remove — URL-encode @ as %40 |
Always percent-encode the @ character in the path: bounced%40example.com. Most HTTP clients (Python's urllib.parse.quote, JavaScript's encodeURIComponent) handle this automatically when building the URL programmatically.
Code Examples
curl -X DELETE \
"http://52.87.60.126:8000/suppression/bounced%40example.com" \
-H "X-API-Key: evp_live_your_key_here"
import urllib.parse
email = urllib.parse.quote("bounced@example.com", safe="")
r = requests.delete(
f"http://52.87.60.126:8000/suppression/{email}",
headers={"X-API-Key": "evp_live_your_key_here"}
)
const email = encodeURIComponent('bounced@example.com');
const res = await fetch(`http://52.87.60.126:8000/suppression/${email}`, {
method: 'DELETE',
headers: { 'X-API-Key': 'evp_live_your_key_here' }
});
Response
{ "status": "ok", "message": "Email removed from suppression list" }
Response Reference
Full Response Format
The /validate endpoint returns a comprehensive response object. Fields are grouped into logical sections covering identity, deliverability scoring, DNS checks, SMTP results, provider classification, ML signals, breach data, and reputation.
// Full /validate response — every field annotated
{
// ─── Identity ───────────────────────────────────────────────
"email": "jane@salesforce.com", // Normalized email address
"domain": "salesforce.com", // Domain portion
"local_part": "jane", // Local portion (before @)
"tld": "com", // Top-level domain
// ─── Deliverability Score ────────────────────────────────────
"status": "valid", // valid|invalid|accept_all|unknown|invalid_syntax
"deliverable": true, // True if SMTP accepted
"can_send": true, // Recommended send decision (considers all signals)
"score": 94, // 0–100 composite score
"grade": "A", // A/B/C/D/F
"score_breakdown": {
"base": 50, // Starting score
"smtp_bonus": 30, // +30 for SMTP accept
"mx_bonus": 10, // +10 for valid MX
"rbl_penalty": 0, // Penalty if on block lists
"disposable_penalty": 0, // Penalty if disposable provider
"role_penalty": 0, // Penalty for role accounts (info@, admin@)
"ml_adjustment": 4 // ML model adjustment
},
// ─── DNS ─────────────────────────────────────────────────────
"syntax_valid": true, // Passes RFC 5322 syntax check
"mx_valid": true, // Domain has valid MX records
"mx_records": ["mta1.salesforce.com"], // Resolved MX hosts
"spf_valid": true, // Domain has SPF record
"dkim_present": true, // DKIM selector found
// ─── SMTP ─────────────────────────────────────────────────────
"smtp_verified": true, // SMTP RCPT TO accepted
"smtp_status": "accepted", // accepted|rejected|timeout|error
"smtp_response_code": 250, // Raw SMTP response code
"smtp_banner": "220 mta1.salesforce.com ESMTP", // Server greeting
"smtp_connect_ms": 312, // Time to connect to MX (ms)
"smtp_total_ms": 441, // Full SMTP round-trip time (ms)
// ─── Provider Directories ─────────────────────────────────────
"provider": "Salesforce", // Identified email provider
"is_free_provider": false, // Gmail, Yahoo, Hotmail, etc.
"is_disposable": false, // Mailinator, Guerrilla, etc.
"is_role_account": false, // info@, admin@, support@, etc.
"is_subaddress": false, // jane+tag@example.com
// ─── Email Type ───────────────────────────────────────────────
"is_catch_all": false, // Domain accepts all addresses
"is_corporate": true, // Non-free, non-disposable domain
// ─── Pattern / ML ─────────────────────────────────────────────
"ml_score": 0.94, // ML model confidence (0–1)
"pattern_match": "first.last", // Detected naming pattern
"domain_age_days": 9124, // Domain registration age
// ─── Breach / Identity ────────────────────────────────────────
"breach_found": false, // Email in known breach databases
"breach_count": 0, // Number of breaches found
// ─── Reputation ───────────────────────────────────────────────
"rbl_listed": false, // On any real-time block list
"rbl_lists_checked": 12, // Number of RBL lists checked
"rbl_lists_matched": [], // Which lists matched (if any)
// ─── Meta ─────────────────────────────────────────────────────
"cached": false, // True if result served from cache
"verified_at": "2026-06-18T14:23:11Z" // ISO timestamp of verification
}
Checks Format (/validate/checks)
The /validate/checks endpoint returns a flattened checks object — identical data to the full response but normalized for direct database insertion. Every field below is always present (never null unless noted).
{
"checks": {
"email": "jane@salesforce.com",
"deliverable": true,
"can_send": true,
"status": "valid",
"score": 94,
"grade": "A",
"score_breakdown": {
"base": 50,
"smtp_bonus": 30,
"mx_bonus": 10,
"rbl_penalty": 0,
"disposable_penalty": 0,
"role_penalty": 0,
"ml_adjustment": 4
},
"is_catch_all": false,
"is_role_account": false,
"is_disposable": false,
"is_free_provider": false,
"is_subaddress": false,
"is_corporate": true,
"mx_valid": true,
"mx_records": ["mta1.salesforce.com"],
"spf_valid": true,
"dkim_present": true,
"smtp_verified": true,
"smtp_status": "accepted",
"smtp_response_code": 250,
"smtp_banner": "220 mta1.salesforce.com ESMTP",
"rbl_listed": false,
"rbl_lists_checked": 12,
"breach_found": false,
"domain": "salesforce.com",
"provider": "Salesforce",
"tld": "com",
"cached": false,
"verified_at": "2026-06-18T14:23:11Z"
}
}
Status Values
The status field is the primary deliverability signal. Each status maps to a specific server or validation result.
| Status | Badge | Meaning | Action |
|---|---|---|---|
valid |
valid | SMTP accepted; high confidence this is a real mailbox | Safe to send |
invalid |
invalid | SMTP rejected or API confirmed nonexistent | Do not send — suppress from list |
accept_all |
accept_all | Domain accepts all RCPT TO commands (catch-all); individual mailbox existence unverifiable | Send with caution — low confidence |
unknown |
unknown | Could not verify — timeout, greylisting, or TLS error prevented SMTP handshake | Retry later or skip |
invalid_syntax |
invalid_syntax | Email does not pass RFC 5322 syntax check — malformed address | Do not send — fix input data |
Score & Grade
The score field is a composite 0–100 integer reflecting overall deliverability confidence. It combines SMTP results, DNS health, reputation data, and ML model output. The grade field maps the numeric score to a letter for quick human review.
| Score Range | Grade | Recommendation |
|---|---|---|
| 90–100 | A | Highly deliverable — send with confidence |
| 75–89 | B | Likely deliverable — safe to send |
| 60–74 | C | Uncertain — consider additional validation before sending |
| 40–59 | D | Low confidence — avoid for cold outreach |
| 0–39 | F | Do not send — invalid, disposable, or blocked |
The can_send field
can_send: true means all three conditions are met:
- Score is 60 or higher
- Status is not
invalidorinvalid_syntax - Email is not on the suppression list
can_send: false means at least one of those conditions failed. Use this field as your primary send/no-send gate rather than building your own score threshold logic.
score_breakdown fields
| Field | Range | Meaning |
|---|---|---|
base |
50 | Starting point for all email evaluations |
smtp_bonus |
0–30 | +30 if SMTP accepted; +0 if rejected or unreachable |
mx_bonus |
0–10 | +10 if valid MX records found for the domain |
rbl_penalty |
−30–0 | Deducted if domain or IP appears on RBL block lists |
disposable_penalty |
−40–0 | Deducted for known disposable / temporary email providers |
role_penalty |
−10–0 | Deducted for role accounts (info@, admin@, support@, etc.) |
ml_adjustment |
−20–+20 | Fine-tuning from the ML model based on learned patterns |
Feedback & ML
POST /bounce
Report a bounce back to EmailVerify Pro. Hard bounces are automatically added to the suppression list and used to train the ML model, improving accuracy for similar domains. Call this endpoint from your email sending platform's bounce webhook.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Email address that bounced |
bounce_type |
string | Yes | hard_bounce, soft_bounce, or spam_complaint |
bounce_code |
string | No | SMTP bounce code (e.g. "550 5.1.1") |
bounce_msg |
string | No | Full bounce message from the MTA |
curl -X POST "http://52.87.60.126:8000/bounce" \
-H "X-API-Key: evp_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"email": "bounced@example.com",
"bounce_type": "hard_bounce",
"bounce_code": "550 5.1.1",
"bounce_msg": "The email account that you tried to reach does not exist."
}'
r = requests.post(
"http://52.87.60.126:8000/bounce",
headers={"X-API-Key": "evp_live_your_key_here"},
json={
"email": "bounced@example.com",
"bounce_type": "hard_bounce",
"bounce_code": "550 5.1.1",
"bounce_msg": "The email account that you tried to reach does not exist."
}
)
await fetch('http://52.87.60.126:8000/bounce', {
method: 'POST',
headers: {
'X-API-Key': 'evp_live_your_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'bounced@example.com',
bounce_type: 'hard_bounce',
bounce_code: '550 5.1.1',
bounce_msg: 'The email account that you tried to reach does not exist.'
})
});
Response
{
"status": "ok",
"suppressed": true,
"message": "Hard bounce recorded and email suppressed"
}
Suppression & training: Hard bounces are automatically added to your suppression list and excluded from future sends. Soft bounces and spam complaints are tracked but not suppressed. All feedback events — regardless of type — are used to retrain the ML model, improving accuracy for similar domains and address patterns over time.
POST /received/batch
Mark emails as confirmed-received senders. When you call this endpoint, EmailVerify Pro applies a +25 score boost on future verifications for those addresses. This is high-value signal — emails that successfully delivered to your inbox are almost certainly valid. Integrate this into your email client or CRM to continuously feed inbox senders back into the verification engine.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
senders |
array | Yes | Array of {email, received_at} objects |
source |
string | No | Source identifier (e.g. "gmail_sync", "outlook_webhook") |
senders object
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Sender email address |
received_at |
string | No | ISO 8601 timestamp of when the email was received |
curl -X POST "http://52.87.60.126:8000/received/batch" \
-H "X-API-Key: evp_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"senders": [
{"email": "alice@company.com", "received_at": "2026-06-18T10:00:00Z"},
{"email": "bob@partner.org", "received_at": "2026-06-18T11:30:00Z"}
],
"source": "gmail_sync"
}'
r = requests.post(
"http://52.87.60.126:8000/received/batch",
headers={"X-API-Key": "evp_live_your_key_here"},
json={
"senders": [
{"email": "alice@company.com", "received_at": "2026-06-18T10:00:00Z"},
{"email": "bob@partner.org", "received_at": "2026-06-18T11:30:00Z"}
],
"source": "gmail_sync"
}
)
data = r.json()
print(f"Processed {data['processed']} senders")
const res = await fetch('http://52.87.60.126:8000/received/batch', {
method: 'POST',
headers: {
'X-API-Key': 'evp_live_your_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
senders: [
{ email: 'alice@company.com', received_at: '2026-06-18T10:00:00Z' },
{ email: 'bob@partner.org', received_at: '2026-06-18T11:30:00Z' }
],
source: 'gmail_sync'
})
});
const data = await res.json();
console.log(`Processed ${data.processed} senders`);
Response
{
"status": "ok",
"processed": 2,
"score_boosts_applied": 2,
"message": "Inbox senders recorded. +25 score boost will apply on next verification."
}
Error Codes
All errors return JSON. Standard form: {"detail": "error message"}. Validation errors use FastAPI's extended form: {"detail": [{"msg": "...", "loc": [...], "type": "..."}]}.
| HTTP Code | Status | Meaning | Common Cause | Resolution |
|---|---|---|---|---|
400 |
Bad Request | Invalid request parameters | Bad email format, missing required parameter | Fix request parameters and retry |
401 |
Unauthorized | Invalid or missing API key | Wrong key, revoked key, missing X-API-Key header |
Check key at /app → API Keys |
402 |
Payment Required | Insufficient credits | Credit balance exhausted | Add credits at /app → Billing |
422 |
Unprocessable Entity | Request body validation failed | Malformed JSON, wrong field types, missing required fields | Check request body against the schema |
429 |
Too Many Requests | Rate limit exceeded | Requests-per-minute cap hit | Back off and check Retry-After header |
500 |
Internal Server Error | Unexpected server error | Bug or transient failure | Retry with exponential backoff |
503 |
Service Unavailable | Server temporarily overloaded | High traffic period | Retry with exponential backoff |
Rate limit handling: When you receive a 429, read the Retry-After response header — it contains the exact number of seconds to wait before retrying. Use exponential backoff: start with the Retry-After value, then double on each subsequent 429. Never retry in a tight loop without a delay.
Example Error Responses
// 401 Unauthorized
{"detail": "Invalid or revoked API key"}
// 429 Too Many Requests (Retry-After: 12 in response headers)
{"detail": "Rate limit exceeded. Retry after 12 seconds."}
// 402 Payment Required
{"detail": "Insufficient credits. Add credits at /app → Billing."}
// 422 Validation Error
{
"detail": [
{
"loc": ["body", "emails"],
"msg": "field required",
"type": "value_error.missing"
}
]
}