Official Python SDK for the VerifyMail API — detect disposable, throwaway, and abusive emails before they reach your database.
from verifymailapi import VerifyMail
vm = VerifyMail(api_key="dc_...")
r = vm.check("user@example.com")
print(r.verdict.recommendation) # "allow" | "allow_with_flag" | "block"pip install verifymailapi
# or
uv add verifymailapiRequires Python 3.10+. One dependency: httpx.
Server-side only. The API key gives full read/write access to your account. Keep it in your backend /
.env/ secrets store, never in a client app.
import os
from verifymailapi import (
VerifyMail,
QuotaExceededError,
RateLimitError,
VerifyMailError,
)
# Reuse one instance across requests.
vm = VerifyMail(api_key=os.environ["VERIFYMAIL_KEY"], risk_profile="balanced")
def handle_signup(email: str, request_id: str):
try:
r = vm.check(email, idempotency_key=f"signup:{request_id}")
except QuotaExceededError:
# Out of credits — don't bounce real customers. Allow with a flag.
return {"ok": True, "action": "allow_with_flag", "reason": "verifymail_unavailable"}
except RateLimitError as e:
return {"ok": False, "error": "Please retry in a moment.", "retry_after": e.retry_after}
except VerifyMailError as e:
# Log the error and fail open — losing one signup hurts more than
# briefly skipping fraud detection.
print(f"VerifyMail error: {e.code} {e.message} (req {e.request_id})")
return {"ok": True, "action": "allow", "reason": "verifymail_error"}
rec = r.verdict.recommendation
if rec == "block":
return {"ok": False, "error": r.verdict.summary}
if rec == "allow_with_flag":
# Route through your verification step (email confirmation, captcha,
# extra onboarding step — whatever your app already has).
return {"ok": True, "action": "allow_with_flag"}
return {"ok": True, "action": "allow"}import asyncio
from verifymailapi import AsyncVerifyMail
async def main():
async with AsyncVerifyMail(api_key="dc_...") as vm:
r = await vm.check("user@example.com")
print(r.verdict.recommendation)
asyncio.run(main())Same method names, same return types — just await them. Use this from FastAPI, aiohttp, or any asyncio codebase.
| Argument | Default | Notes |
|---|---|---|
api_key |
required | Your dc_… key from the dashboard. |
base_url |
"https://api.verifymailapi.com" |
Override for staging. |
retries |
2 |
Retries on 429 / 5xx. Set 0 to disable. |
timeout |
30.0 |
Per-request timeout in seconds. |
risk_profile |
None (server default) |
"strict" / "balanced" / "permissive". Per-call override available. |
| Method | Returns | What it does |
|---|---|---|
check(email, *, risk_profile=, idempotency_key=) |
CheckResponse |
Check a single email. 1 credit. |
check_domain(domain, *, ...) |
CheckResponse |
Domain-only check. 1 credit. |
check_bulk(emails, *, ...) |
BulkCheckResponse |
1–100 emails. Charges N up front. |
check_bulk_stream(emails, *, risk_profile=) |
iterator | NDJSON stream — yields rows as each finishes. |
check_async(email, webhook_url, *, webhook_secret=, ...) |
AsyncCheckResponse |
Returns 202 + preliminary verdict. Final result POSTed to your webhook. |
report(domain, outcome, *, notes=) |
ReportResponse |
File a domain-outcome report. |
usage() |
UsageMeResponse |
Current-period totals + credit balance. |
status() |
StatusResponse |
Component health (Redis / Postgres / DNS). |
Every method that costs credits accepts idempotency_key=True (auto-generated UUID) or a fixed string.
if r.verdict.recommendation == "block":
# High confidence: abuse, dead address, or disposable provider.
reject()
elif r.verdict.recommendation == "allow_with_flag":
# Suspicious. Route through your verification step.
user.requires_email_verification = True
elif r.verdict.recommendation == "allow":
# Clean. Proceed.
passThe most important rule: map allow_with_flag to user.requires_email_verification = True (or whatever your friction step is called). Most B2B apps already have email verification — that one line costs zero new code and catches the vast majority of bot signups.
from verifymailapi import (
VerifyMailError,
InvalidApiKeyError,
QuotaExceededError,
RateLimitError,
IdempotencyConflictError,
ValidationError,
ServiceDegradedError,
)
try:
vm.check(email)
except QuotaExceededError as e:
show_billing(e.upgrade_url)
except RateLimitError as e:
retry_after(e.retry_after) # seconds
except InvalidApiKeyError:
alert_ops("VerifyMail key rotated?")
except VerifyMailError as e:
log(e.code, e.status, e.request_id, e.message)Every error carries code, status, request_id, docs_url, and the raw body payload when available. Subclasses add specific fields (RateLimitError.retry_after, QuotaExceededError.upgrade_url, etc.).
POST endpoints that charge credits all accept idempotency_key. Replay the same key within 24 hours and you get the cached response — no duplicate work, no duplicate charge.
# Auto-generate a UUID
vm.check(email, idempotency_key=True)
# Or pass your own (correlate with your request)
vm.check(email, idempotency_key=f"signup:{request_id}")Reusing the same key with a different request body raises IdempotencyConflictError (HTTP 409).
result = vm.check_bulk(["a@x.com", "b@x.com", "c@x.com"])
for r in result.items:
print(r.meta.domain, "→", r.verdict.recommendation)
print(f"charged {result.summary.credits_charged} credits "
f"in {result.summary.elapsed_ms}ms")Stream results as each check completes:
from verifymailapi import BulkStreamSummary
for event in vm.check_bulk_stream(big_list_of_emails):
if isinstance(event, BulkStreamSummary):
print("done — credits remaining:", event.credits_remaining)
else:
process_row(event.index, event.result)Async version:
async for event in vm.check_bulk_stream(big_list_of_emails):
...Results arrive in finish order, not input order — correlate via event.index.
r = vm.check_async(
email="user@example.com",
webhook_url="https://your-app.example/webhooks/verifymail",
webhook_secret=os.environ["VERIFYMAIL_WEBHOOK_SECRET"],
)
print(r.preliminary.verdict.recommendation) # act on this now
# Final verdict is POSTed to webhook_url after the deep SMTP probe.from fastapi import FastAPI, Request, HTTPException
from verifymailapi import verify_webhook
import os, json
app = FastAPI()
@app.post("/webhooks/verifymail")
async def webhook(request: Request):
raw = await request.body() # must be raw bytes
sig = request.headers.get("X-VerifyMail-Signature", "")
if not verify_webhook(raw, sig, os.environ["VERIFYMAIL_WEBHOOK_SECRET"]):
raise HTTPException(status_code=401, detail="bad signature")
event = json.loads(raw)
# event["result"] is the final CheckResponse JSON.
return {"ok": True}Same idea in Flask / Django — just keep the body raw until after verify_webhook returns True.
from django.http import JsonResponse
from verifymailapi import VerifyMail
vm = VerifyMail(api_key=settings.VERIFYMAIL_KEY)
def signup(request):
email = request.POST["email"]
r = vm.check(email)
if r.verdict.recommendation == "block":
return JsonResponse({"error": r.verdict.summary}, status=422)
User.objects.create(
email=email,
requires_verification=(r.verdict.recommendation == "allow_with_flag"),
)
return JsonResponse({"ok": True})from fastapi import FastAPI
from verifymailapi import AsyncVerifyMail
import os
vm = AsyncVerifyMail(api_key=os.environ["VERIFYMAIL_KEY"])
app = FastAPI()
@app.post("/signup")
async def signup(email: str):
r = await vm.check(email)
if r.verdict.recommendation == "block":
return {"error": r.verdict.summary}
return {"ok": True, "flagged": r.verdict.recommendation == "allow_with_flag"}The API enforces 600 requests / minute per key by default (configurable for paying customers). The SDK automatically:
- Reads
Retry-Afteron429responses - Backs off and retries up to
retriestimes - Raises
RateLimitErrorif all retries fail
The SDK doesn't read env vars itself — you pass them. Recommended names:
| Var | Purpose |
|---|---|
VERIFYMAIL_KEY |
Your dc_… API key |
VERIFYMAIL_WEBHOOK_SECRET |
Optional shared secret for vm.check_async(...) |
VERIFYMAIL_API_URL |
Optional override of base_url for staging |
- Full API docs: https://verifymailapi.com/docs
- Dashboard / API keys: https://verifymailapi.com/dashboard/keys
- Issues / discussions: https://github.com/jt1402/verifymail-python
- Pricing: https://verifymailapi.com/pricing
MIT