VerifyMail API
A single endpoint that tells you whether the person behind an email is real. Confidence-weighted risk scores, explainable signals, and catch-all detection built in.
Introduction
VerifyMail identifies throwaway, disposable, catch-all, and abusive email addresses at signup. Instead of a boolean “valid/invalid” flag, every call to /v1/check returns a 5-block response — meta, verdict, score, signals, and checks — so you can make nuanced policy decisions from a single request.
The entire API is intentionally small. One endpoint handles 99% of traffic. Supporting endpoints let you report false positives, monitor usage, and subscribe to async webhooks.
Quickstart
Install the SDK for your language and make your first check in under 60 seconds.
1. Install
# Node
npm install verifymail
# Python
pip install verifymail2. Make your first check
from verifymail import VerifyMail
client = VerifyMail(api_key="vm_live_...")
result = client.check("user@mailinator.com")
print(result.verdict.recommendation) # -> "block"
print(result.score.value) # -> 100
print(result.score.confidence) # -> 1.03. Switch on the recommendation
switch (result.verdict.recommendation) {
case 'block': throw new Error(result.verdict.summary);
case 'verify_manually': queueForReview(email, result); break;
case 'allow_with_flag': logSuspicious(email, result.signals.fired); break;
case 'allow': break;
}Authentication
Every request requires an API key, sent as the X-API-Key header. Keys are environment-scoped: vm_live_* for production, vm_test_* for staging and tests.
export VERIFYMAIL_KEY="vm_live_..."
curl -H "X-API-Key: $VERIFYMAIL_KEY" \
https://api.verifymailapi.com/v1/check?email=test@example.comRotate keys freely from the dashboard — previous keys remain valid for 24 hours to give you a grace window.
Rate limits
Default limit is 100 requests / second per API key, bursting to 500 with token-bucket smoothing. Requests over the limit return 429 with a Retry-After header. For higher volume, contact support.
POST /v1/check
The single core endpoint. Accepts an email, returns a full 5-block scored response.
Request
POST https://api.verifymailapi.com/v1/check
X-API-Key: vm_live_...
X-Risk-Profile: balanced # optional — strict | balanced | permissive
Content-Type: application/json
{ "email": "user@myagency-solutions.com" }Response — catch-all fraud example
{
"meta": {
"request_id": "req_8d4b3e2f1a6c",
"email": "user@myagency-solutions.com",
"domain": "myagency-solutions.com",
"checked_at": "2026-04-21T10:24:12.391Z",
"latency_ms": 94,
"api_version": "2026-04",
"path_taken": "standard",
"cached": false
},
"verdict": {
"recommendation": "block",
"risk_level": "critical",
"disposable": false,
"catch_all": true,
"safe_to_send": false,
"summary": "Domain is 14 days old, configured as catch-all, and matches known fraud infrastructure."
},
"score": {
"value": 94,
"confidence": 0.91,
"confidence_level": "high",
"thresholds": {
"block_at": 70,
"flag_at": 45,
"your_profile": "balanced"
},
"catch_all_detail": {
"detected": true,
"probability": 0.97,
"confidence": 0.94,
"type": "confirmed"
}
},
"signals": {
"fired": [
{ "name": "catch_all_confirmed", "direction": "risk", "weight": 55 },
{ "name": "domain_age_14_days", "direction": "risk", "weight": 42 },
{ "name": "mx_shared_with_9_domains", "direction": "risk", "weight": 35 }
]
}
}POST /v1/report
Report a false positive or false negative to tune VerifyMail against your workload. Reports feed the network-effect model and are shared (de-identified) across customers.
POST /v1/report
{ "request_id": "req_8d4b3e2f1a6c",
"correct_verdict": "allow",
"notes": "Customer converted and paid." }GET /v1/usage
Returns your current-period usage, credit balance, and next billing window. Useful for dashboards and budget guards.
The 5-block structure
Every response has the same top-level shape. Each block has a single purpose:
| Block | Purpose |
|---|---|
meta | Request fingerprint: request_id, email, timing, API version, cache hit flag. |
verdict | The single actionable field: recommendation + human-readable summary + high-level booleans (disposable, catch_all). |
score | Quantitative output: 0–100 risk score, confidence 0–1, applied thresholds, catch-all probability detail. |
signals | Every signal that fired, its direction (risk/trust) and weight. The "why" behind the verdict. |
checks | Which physical probes ran (DNS, SMTP, blocklist) and how long each took. Useful for debugging latency. |
The 4 recommendation values
The verdict.recommendation field is always one of exactly four strings — switch on it directly without parsing thresholds.
| Value | Meaning | Suggested action |
|---|---|---|
| block | High confidence this is abuse or a dead address. | Refuse signup. Show a generic error. |
| verify_manually | Suspicious but ambiguous — could be legitimate. | Send a confirmation email; hold pending click-through. |
| allow_with_flag | Mostly trusted but some risk signals present. | Allow signup, tag for post-hoc review or rate-limited onboarding. |
| allow | Clean. No material risk signals. | Proceed normally. |
Risk profiles
Three built-in profiles control the block/flag thresholds applied to the raw 0–100 score. Send X-Risk-Profile: strict | balanced | permissive per request, or set an account default.
| Profile | block_at | flag_at | Best for |
|---|---|---|---|
| strict | 55 | 35 | Payment or financial services — false positives acceptable. |
| balanced default | 70 | 50 | SaaS signup, marketing tools, most B2B. |
| permissive | 85 | 65 | Consumer apps with high-funnel priority — minimize friction. |
Catch-all detection
Catch-all domains accept mail for any address, which defeats naive SMTP probes. VerifyMail combines four evidence sources into a single Bayesian probability:
- SMTP probe result — response to a random-UUID recipient.
- Infrastructure signals — MX configuration, WHOIS age, ASN reputation.
- Behavioral history — delivery/bounce patterns from past traffic.
- Cross-customer network data — correlation with confirmed fraud on shared infrastructure.
Worked example A — fraud catch-all
Domain registered 14 days ago, MX shared with 9 other domains (7 confirmed disposable), SMTP accepts UUID addresses:
catch_all.probability = 0.97
catch_all.confidence = 0.94
catch_all.type = "confirmed"
verdict.recommendation = "block"Worked example B — legitimate catch-all
Domain registered 6 years ago, SPF/DMARC aligned, healthy delivery history, SMTP accepts UUID addresses:
catch_all.probability = 0.88
catch_all.legitimate_use_likely = true
catch_all.type = "legitimate"
verdict.recommendation = "allow_with_flag"Signals reference
A representative slice of what VerifyMail evaluates, with balanced-profile weights. Risk signals add to the score, trust signals subtract.
| Signal | Category | Dir. | Weight | Description |
|---|---|---|---|---|
malformed_local_part | structural | risk | 20 | Local part contains disallowed characters or invalid encoding. |
typo_of_popular_domain | structural | risk | 15 | Likely typo of a major provider (e.g. gmial.com). |
role_address | structural | risk | 12 | Generic role address (admin@, info@, noreply@). |
known_disposable_domain | blocklist | risk | 100 | Domain on confirmed disposable blocklist. |
domain_age_under_30_days | domain | risk | 42 | Recently-registered domain. Fraud domains are almost always fresh. |
no_mx_records | domain | risk | 70 | Domain has no MX records — cannot receive mail. |
domain_parking | domain | risk | 50 | Domain resolves to a parking / for-sale page. |
free_domain_tld | domain | risk | 18 | Uses a free or high-abuse TLD (.tk, .ml, .ga, etc). |
mx_shared_with_fraud | infra | risk | 35 | MX infrastructure shared with confirmed fraud domains. |
asn_high_abuse | infra | risk | 22 | MX host on an ASN with high historical abuse rate. |
catch_all_confirmed | smtp | risk | 55 | SMTP returned 250 for a random UUID recipient. |
smtp_greeting_suspicious | smtp | risk | 12 | SMTP banner matches known fraud-tool signatures. |
mailbox_does_not_exist | smtp | risk | 80 | SMTP 550 — mailbox explicitly rejected. |
abuse_history_network | behav | risk | 40 | Cross-customer signal: this address or domain has abused other accounts. |
high_bounce_history | behav | risk | 28 | Domain has elevated historical bounce rate. |
signup_velocity_spike | behav | risk | 18 | Domain involved in recent signup velocity anomaly. |
major_provider_gmail | trust | trust | -30 | Gmail / Workspace address with valid MX. |
major_provider_outlook | trust | trust | -28 | Outlook / Microsoft 365 address with valid MX. |
domain_age_over_2_years | trust | trust | -18 | Domain registered over 2 years ago. |
dmarc_aligned | trust | trust | -14 | Domain publishes aligned SPF / DMARC policy. |
healthy_delivery_history | trust | trust | -22 | Past deliveries to domain have high engagement and low bounce. |
corporate_domain | trust | trust | -12 | Domain has WHOIS registered to a verifiable organization. |
Above: 22 representative signals. The full reference includes 80+ signals across all categories.
SDKs
Official clients for four languages, with full types for every response field.
Python
Install via pip install verifymail. The client is sync by default; import AsyncVerifyMail for asyncio support.
Node.js
Install via npm install verifymail. Ships with full TypeScript types for every field in the response.
PHP
Install via composer require verifymail/verifymail. PHP 8.1+; uses readonly properties for the response object.
Go
Install via go get github.com/verifymail/verifymail-go. Context-aware, zero external dependencies beyond the stdlib.
Webhooks
Deep checks run async when latency budget would exceed 300ms. Subscribe to check.completed events to receive the full response when the background job finishes. Preliminary response is returned synchronously.
Errors
All errors return a consistent shape:
{
"error": {
"code": "invalid_email",
"message": "Email failed syntax validation.",
"request_id": "req_..."
}
}| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_email | Email failed syntax validation. |
| 401 | invalid_api_key | API key missing or unknown. |
| 403 | key_scope_denied | Key does not have permission for this environment. |
| 429 | rate_limited | Rate limit exceeded. See Retry-After. |
| 402 | quota_exceeded | No credits remaining. Buy a bundle to keep going. |
| 500 | internal_error | Transient server error. Safe to retry with backoff. |
| 503 | deep_check_unavailable | Deep check service temporarily unavailable — fast-path result returned. |
Versioning
API versions use YYYY-MM format. Pin a version with the API-Version header:
API-Version: 2026-04Breaking changes release on a new dated version. We support 6 months of deprecation for every version before removal, with migration guides published 3 months in advance. Unversioned requests use the oldest supported version to guarantee stability.