Live on Cloudflare Workers · 50ms p50

Email verification API. 50ms p50. No SDK.

One endpoint, JSON in, JSON out. Disposable detection across 272,228+ domains, Spamhaus DBL probes, per-customer bounce history, typo correction — every signal returned in a single call. Drop into any signup form in under a minute.

The endpoint

POST /v1/check

One endpoint covers verification, disposable detection, spam-trap probes, typo suggestion, and bounce history. The verdict shape doesn't change between cache hits and cold paths — your handler logic stays simple.

request.sh
bash
curl https://vrfymail.com/v1/check \
  -H "Authorization: Bearer vk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "email": "ada@example.com", "strict": true }'
response.json
json
{
  "result": "deliverable",
  "reason": "valid_mailbox",
  "reason_message": null,
  "disposable": false,
  "spam_trap": false,
  "mx_found": true,
  "score": 0.98,
  "did_you_mean": null,
  "trap": { "listed": false, "lists": [], "code": null },
  "account_history": { "matched": false }
}
Verdict semantics

Four results, machine-readable reasons

Every verdict carries a result, a machine code, and a pre-mapped end-user copy string. Pass reason_message straight through to your form.

result When Recommended handling Billed?
deliverable Valid mailbox confirmed Accept Yes
unknown Couldn't reach a verdict (DNS / MX / strict-mode) Accept (never block on a network hiccup) No — refunded
risky Role accounts, weak signals, low score Soft warning, allow submit Yes
undeliverable Disposable, spam-trap, syntax, no MX, previously bounced Block, show reason_message verbatim Yes
Drop in anywhere fetch runs

No SDK. Plain HTTP, every language.

signup.ts
typescript
// Node, Bun, Deno, Workers — anywhere fetch runs.
const r = await fetch("https://vrfymail.com/v1/check", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.MAIL_VERIFIER_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ email: "ada@example.com", strict: true }),
});
const verdict = await r.json();
if (verdict.result === "deliverable") {
  // accept
}
signup.py
python
import os, requests

r = requests.post(
    "https://vrfymail.com/v1/check",
    headers={"Authorization": f"Bearer {os.environ['MAIL_VERIFIER_KEY']}"},
    json={"email": "ada@example.com", "strict": True},
)
verdict = r.json()
if verdict["result"] == "deliverable":
    # accept
    pass
signup.go
go
// main.go
body := strings.NewReader(`{"email":"ada@example.com","strict":true}`)
req, _ := http.NewRequest("POST", "https://vrfymail.com/v1/check", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("MAIL_VERIFIER_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
vs the field

Where the math lands

Numbers as of 2026-05. See the compare hub for head-to-heads with full reasoning.

vrfymail ZeroBounce NeverBounce Hunter
Free tier5,000/mo100 lifetime1,000 lifetime50/mo
Cheapest paid$9/mo · 10k$16 / 2k (PAYG)$80 / 10k$49/mo · 1k
p50 latency~50ms (cache)200–500msNot published~800ms (SMTP)
Edge / cold startsCloudflare, noneRegionalRegionalRegional
Unknown billed?No (refunded)YesYes (free retry)Yes
SDK requiredNo (fetch)OptionalOptionalOptional
Where it ships

Ten AI coding tools, one fetch contract

Every AI editor we cover scaffolds against the same vrfymail contract via its native config artifact — .cursor/rules/*.mdc, CLAUDE.md, .windsurfrules, .github/copilot-instructions.md, Custom Instructions.

Frequently asked

Email verification API, answered

What does the API check on every call?
Syntax (RFC-leaning), MX records, role accounts (info@, support@), disposable domains against a 272K+ self-growing database, Spamhaus DBL probe, per-customer bounce history, and optional strict-mode flags (plus-aliases, gmail dot tricks, sequential keyboard mashes). One request, one verdict, every signal returned.
How is the verdict actually shaped?
Four results: deliverable, undeliverable, risky, unknown. Each comes with a machine code (e.g. role_account, plus_addressing_rejected, previously_bounced) and an end-user copy string already mapped per reason. Pass reason_message straight through to your form — no client-side mapping needed. did_you_mean carries a typo correction or null.
What about unknown verdicts — do they cost me a credit?
No. When the pipeline can't reach a verdict (DNS lookup failed, MX timed out, strict-mode flag fired with no resolution), refundUsage() releases the slot server-side and the call isn't billed. Treat unknown as deliverable on the client — never block real users on a DNS hiccup.
Is there an SDK?
No, and that's deliberate. POST /v1/check takes JSON, returns JSON. Every framework an AI editor or human developer will scaffold — Next.js, Hono, Express, Fastify, FastAPI, Laravel, Rails — speaks fetch (or its language equivalent). TypeScript SDK is on the roadmap; until then plain fetch is the contract.
How does pricing compare to ZeroBounce, NeverBounce, Hunter?
Free tier: 5,000 verifies/month, no card (ZeroBounce: 100 lifetime credits, NeverBounce: 1,000 lifetime, Hunter: 50/month). Cheapest paid: $9/mo for 10,000 (ZeroBounce: $16 PAYG for 2k, NeverBounce: $80 for 10k, Hunter Starter: $49 for 1k). Effective $/1k at 50k volume: $0.58 on Pro plan. See the full matrix on the compare page.
Does the API support batch verification?
POST /v1/check/batch (up to 100 addresses per call) is on the roadmap. Today's pattern: parallelize with Promise.all in JS or asyncio in Python — fan out 50-100 concurrent calls, vrfymail's edge handles the bursts. Per-customer caching means duplicate addresses don't double-bill.

Spin up a key in 30 seconds.

5,000 verifies/month free, no card. Paid plans start at $9/mo for 10,000 — see pricing.

Get my API key