Skip to main content

Official Python SDK for the VerifyMail API — disposable, throwaway, and abusive email detection.

Project description

verifymailapi

PyPI version python license

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"

Install

pip install verifymailapi
# or
uv add verifymailapi

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


Production-ready example

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"}

Async

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.


API

VerifyMail(api_key, *, base_url=None, retries=2, timeout=30.0, risk_profile=None)

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.

Methods (same on VerifyMail and AsyncVerifyMail)

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.


Verdicts — what to do with each

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

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


Errors

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


Idempotency

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


Bulk processing

Small batches (≤ 100 emails)

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")

Large batches (5k–100k addresses)

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.


Async deep checks (webhooks)

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.

Verifying the webhook signature (FastAPI)

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.


Framework recipes

Django view

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})

FastAPI (async)

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"}

Rate limits

The API enforces 600 requests / minute per key by default (configurable for paying customers). The SDK automatically:

  • Reads Retry-After on 429 responses
  • Backs off and retries up to retries times
  • Raises RateLimitError if all retries fail

Configuration via environment

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

Links

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

verifymailapi-0.1.0.tar.gz (13.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

verifymailapi-0.1.0-py3-none-any.whl (16.0 kB view details)

Uploaded Python 3

File details

Details for the file verifymailapi-0.1.0.tar.gz.

File metadata

  • Download URL: verifymailapi-0.1.0.tar.gz
  • Upload date:
  • Size: 13.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for verifymailapi-0.1.0.tar.gz
Algorithm Hash digest
SHA256 541a279a0c6f0341b38d880517c6e05a032cdc4c5b30837a7b9aea3db461fbf8
MD5 7f469cc089e3d90d512486eb6e4eb9ff
BLAKE2b-256 991e584ce6821ebab4b627e5bc931362e8ebea8eafa8fed0dc77f52a601a5d37

See more details on using hashes here.

Provenance

The following attestation bundles were made for verifymailapi-0.1.0.tar.gz:

Publisher: publish.yml on jt1402/verifymail-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file verifymailapi-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: verifymailapi-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 16.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for verifymailapi-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b7141a2b2d9be71a855cde0bbae86735f218eb6173600541dfd82d849fab0f36
MD5 aac1bffe62a19a54c826f89361faddc9
BLAKE2b-256 3302f3f306bc7f6841dc8313ca74c6469e4be393d82bd97e8f7c0cc1a53af5ec

See more details on using hashes here.

Provenance

The following attestation bundles were made for verifymailapi-0.1.0-py3-none-any.whl:

Publisher: publish.yml on jt1402/verifymail-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page