Email verification in Python
Python reaches the ZapBounce API over plain HTTP, so requests is all you need and there is no SDK to keep current. The examples below run against the live API with a key in the environment.
One habit worth keeping from the start: use a Session. It pools the TCP connection, which turns a loop of ten thousand single verifications from a connection-per-call into something that finishes this afternoon.
requests.Session for pooling, urllib3.Retry mounted on the adapter so 429 and 5xx back off without a hand-rolled loop.
Install
pip install requestsVerify one address
You branch on four results: valid, invalid, catch_all and unknown. Role and disposable are boolean flags beside the result, so test them separately. Unknown is the branch most code forgets, and it is never billed.
import os
import requests
session = requests.Session()
session.headers.update({"Authorization": f"Bearer {os.environ['ZAPBOUNCE_KEY']}"})
def verify(email: str) -> dict:
r = session.post(
"https://api.zapbounce.com/v1/verify",
json={"email": email},
timeout=(3.05, 10), # connect, read — the read side is the slow mail server
)
r.raise_for_status()
return r.json()
result = verify("ada@example.com")
if result["result"] == "valid":
send_to(result["email"])
elif result["result"] == "catch_all":
queue_for_separate_send(result["email"]) # the domain accepts everything
elif result["result"] == "unknown":
keep_and_retry_later(result["email"]) # not billed, not a verdict
else:
suppress(result["email"])Verify a list
Submit a batch rather than looping the single endpoint. We pace probes per receiving mail host, which protects the sending reputation your results depend on.
from typing import Iterator
def submit(emails: list[str], name: str) -> str:
r = session.post(
"https://api.zapbounce.com/v1/batches",
json={"name": name, "emails": emails},
timeout=(3.05, 30),
)
r.raise_for_status()
return r.json()["batch_id"]
def results(batch_id: str) -> Iterator[dict]:
"""Yield every row, following the cursor. Never build the full list in memory."""
cursor = None
while True:
params = {"limit": 1000}
if cursor:
params["cursor"] = cursor
page = session.get(
f"https://api.zapbounce.com/v1/batches/{batch_id}/results",
params=params, timeout=(3.05, 30),
).json()
yield from page["data"]
if not page["has_more"]:
return
cursor = page["next_cursor"]
batch_id = submit(load_emails("contacts.csv"), name="q4-webinar")
# ... wait for the webhook, then:
billed = sum(1 for row in results(batch_id) if row["billed"])
print(f"{billed} credits spent; the rest came back unknown and cost nothing")Errors and retries
A 429 and a 5xx are worth retrying. A 402 for credits and a 400 for a malformed address will fail the same way on every attempt, so stop.
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
# Retry-After on a 429 is honored by urllib3 itself, so no hand-rolled sleep loop.
retry = Retry(
total=5,
backoff_factor=0.5,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "POST"],
respect_retry_after_header=True,
)
session.mount("https://", HTTPAdapter(max_retries=retry, pool_maxsize=20))
try:
result = verify(email)
except requests.HTTPError as exc:
body = exc.response.json().get("error", {})
if body.get("code") == "insufficient_credits":
raise OutOfCredits(body["message"]) from exc # retrying will not help
if exc.response.status_code == 402:
raise
logger.warning("verify failed request_id=%s", body.get("request_id"))
raise
except requests.Timeout:
# A verifier timing out is not a reason to block a signup. Fail open.
result = {"email": email, "result": "unknown", "billed": False}Python: common questions
Is there an official Python SDK?
No, and one would add a dependency to wrap four HTTP calls. requests plus the retry adapter above is the whole integration.
How do I verify at signup without blocking the form?
Give the call a two-second read timeout and treat a timeout as a pass. A verifier having a slow second should not cost you a registration.
Does async help?
For bulk, no: submit a batch and let us pace the probes. For a page that checks several addresses at once, httpx.AsyncClient with the same headers works the same way.
Run this Python code today
100 free checks a month, no card, credits that never expire, and unknown results that cost nothing.