VerifyMail
DOCS · v2026-04

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.

Positioning: Email verification tells you the address exists. VerifyMail tells you whether the person behind it is real.

Quickstart

Install the SDK for your language and make your first check in under 60 seconds.

1. Install

terminal
# Node
npm install verifymail

# Python
pip install verifymail

2. Make your first check

first_check.py
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.0

3. Switch on the recommendation

signup.js
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.

shell
export VERIFYMAIL_KEY="vm_live_..."

curl -H "X-API-Key: $VERIFYMAIL_KEY" \
  https://api.verifymailapi.com/v1/check?email=test@example.com

Rotate 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

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

200 OK · application/json
{
  "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.

request
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:

BlockPurpose
metaRequest fingerprint: request_id, email, timing, API version, cache hit flag.
verdictThe single actionable field: recommendation + human-readable summary + high-level booleans (disposable, catch_all).
scoreQuantitative output: 0–100 risk score, confidence 0–1, applied thresholds, catch-all probability detail.
signalsEvery signal that fired, its direction (risk/trust) and weight. The "why" behind the verdict.
checksWhich 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.

ValueMeaningSuggested action
blockHigh confidence this is abuse or a dead address.Refuse signup. Show a generic error.
verify_manuallySuspicious but ambiguous — could be legitimate.Send a confirmation email; hold pending click-through.
allow_with_flagMostly trusted but some risk signals present.Allow signup, tag for post-hoc review or rate-limited onboarding.
allowClean. 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.

Profileblock_atflag_atBest for
strict5535Payment or financial services — false positives acceptable.
balanced default7050SaaS signup, marketing tools, most B2B.
permissive8565Consumer 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:

  1. SMTP probe result — response to a random-UUID recipient.
  2. Infrastructure signals — MX configuration, WHOIS age, ASN reputation.
  3. Behavioral history — delivery/bounce patterns from past traffic.
  4. 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:

summary
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:

summary
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.

SignalCategoryDir.WeightDescription
malformed_local_partstructuralrisk20Local part contains disallowed characters or invalid encoding.
typo_of_popular_domainstructuralrisk15Likely typo of a major provider (e.g. gmial.com).
role_addressstructuralrisk12Generic role address (admin@, info@, noreply@).
known_disposable_domainblocklistrisk100Domain on confirmed disposable blocklist.
domain_age_under_30_daysdomainrisk42Recently-registered domain. Fraud domains are almost always fresh.
no_mx_recordsdomainrisk70Domain has no MX records — cannot receive mail.
domain_parkingdomainrisk50Domain resolves to a parking / for-sale page.
free_domain_tlddomainrisk18Uses a free or high-abuse TLD (.tk, .ml, .ga, etc).
mx_shared_with_fraudinfrarisk35MX infrastructure shared with confirmed fraud domains.
asn_high_abuseinfrarisk22MX host on an ASN with high historical abuse rate.
catch_all_confirmedsmtprisk55SMTP returned 250 for a random UUID recipient.
smtp_greeting_suspicioussmtprisk12SMTP banner matches known fraud-tool signatures.
mailbox_does_not_existsmtprisk80SMTP 550 — mailbox explicitly rejected.
abuse_history_networkbehavrisk40Cross-customer signal: this address or domain has abused other accounts.
high_bounce_historybehavrisk28Domain has elevated historical bounce rate.
signup_velocity_spikebehavrisk18Domain involved in recent signup velocity anomaly.
major_provider_gmailtrusttrust-30Gmail / Workspace address with valid MX.
major_provider_outlooktrusttrust-28Outlook / Microsoft 365 address with valid MX.
domain_age_over_2_yearstrusttrust-18Domain registered over 2 years ago.
dmarc_alignedtrusttrust-14Domain publishes aligned SPF / DMARC policy.
healthy_delivery_historytrusttrust-22Past deliveries to domain have high engagement and low bounce.
corporate_domaintrusttrust-12Domain 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 response
{
  "error": {
    "code": "invalid_email",
    "message": "Email failed syntax validation.",
    "request_id": "req_..."
  }
}
HTTPCodeMeaning
400invalid_emailEmail failed syntax validation.
401invalid_api_keyAPI key missing or unknown.
403key_scope_deniedKey does not have permission for this environment.
429rate_limitedRate limit exceeded. See Retry-After.
402quota_exceededNo credits remaining. Buy a bundle to keep going.
500internal_errorTransient server error. Safe to retry with backoff.
503deep_check_unavailableDeep check service temporarily unavailable — fast-path result returned.

Versioning

API versions use YYYY-MM format. Pin a version with the API-Version header:

header
API-Version: 2026-04

Breaking 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.