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.
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.
curl https://vrfymail.com/v1/check \
-H "Authorization: Bearer vk_live_..." \
-H "Content-Type: application/json" \
-d '{ "email": "ada@example.com", "strict": true }' {
"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 }
} 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 |
No SDK. Plain HTTP, every language.
// 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
} 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 // 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() 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 tier | 5,000/mo | 100 lifetime | 1,000 lifetime | 50/mo |
| Cheapest paid | $9/mo · 10k | $16 / 2k (PAYG) | $80 / 10k | $49/mo · 1k |
| p50 latency | ~50ms (cache) | 200–500ms | Not published | ~800ms (SMTP) |
| Edge / cold starts | Cloudflare, none | Regional | Regional | Regional |
| Unknown billed? | No (refunded) | Yes | Yes (free retry) | Yes |
| SDK required | No (fetch) | Optional | Optional | Optional |
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.
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