Machine-readable spec available at /openapi.json — compatible with any OpenAI-style tool use.

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
email 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
email 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
email 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
email 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.
{ "detail": "Email not found in cache" }

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

NameTypeRequiredDescription
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

NameTypeRequiredDescription
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

NameTypeRequiredDescription
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

NameTypeRequiredDescription
job_id string Yes Job ID returned by /validate/bulk/async

Job Status Values

StatusMeaning
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

NameTypeRequiredDescription
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

NameTypeRequiredDescription
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

NameTypeRequiredDescription
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

NameTypeRequiredDescription
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

NameTypeRequiredDescription
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 invalid or invalid_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"
    }
  ]
}