Run a local syntax check first
A verification API call costs a network round trip and, on paid plans, a credit. Before you spend either, reject input that cannot possibly be an address. A simple check for one @ sign with text on both sides and a dot in the domain is enough; a stricter regex mostly rejects real users with unusual addresses.
Keep this check in its own function so the rest of your code can call it independently and your tests can cover it without mocking anything. A pattern like one or more non-space characters, an @ sign, more non-space characters, a dot, and a top-level domain is as strict as you want to be before the network call.
A reusable verify function with requests
The VeriDock endpoint is a single GET request with the address as a query parameter and the key in an Authorization header. Read the key from the VERIDOCK_API_KEY environment variable, always pass a timeout, and let requests encode the email for you by using params instead of building the URL by hand.
The response has a status of deliverable, undeliverable, risky, or unknown plus a reason such as accepted_email, no_mx_record, or disposable, so return the parsed dict and let callers decide what to do. The score, checks, and suggestion fields come back in the same dict, which is why the function returns the whole payload rather than a boolean.
import os
import requests
API_URL = "https://api.veridock.io/v1/verify"
def verify_email(email: str, timeout: float = 10.0) -> dict:
resp = requests.get(
API_URL,
params={"email": email},
headers={"Authorization": f"Bearer {os.environ['VERIDOCK_API_KEY']}"},
timeout=timeout,
)
resp.raise_for_status()
return resp.json()
result = verify_email("jane.doe@acme.com")
print(result["status"], result["reason"], result["score"])Handle failed requests separately from unknown results
Two different things can go wrong, and they need different handling. A transport problem such as a timeout, an HTTP 429 rate limit, or a 401 from a bad key means you did not get an answer. An HTTP 200 with status unknown means the API answered and the mail server was inconclusive, often with a reason of timeout or greylisted. Unknown results are never billed, so retrying them later costs nothing.
Wrap the call in a try block that catches requests.RequestException, log the exception type and the email domain, and decide whether to fail open or closed based on context. At signup you usually fail open; during a list import you queue the address for a retry.
- requests.Timeout: retry once with backoff, then treat as unknown.
- HTTP 429: sleep for the Retry-After value before retrying.
- HTTP 401: stop and alert, the key is wrong or revoked.
- HTTP 200 with status unknown: not an error, keep the address and re-verify later.
A 200 response with status unknown is a valid answer, not a failure; only transport and HTTP errors belong in your retry path.
Testing with mocked responses
Unit tests should never reach the network. Patch requests.get with a MagicMock whose json method returns a fixture, and assert both on the returned dict and on how the function called the client. That catches regressions in parameter handling, such as dropping the timeout, without a key or a credit.
For an occasional end-to-end check, VeriDock provides deterministic free test API keys, so a small integration test can run in CI without billing or real customer data. Keep that suite separate from the unit tests and skip it automatically when the key is not present in the environment.
from unittest.mock import MagicMock, patch
from verifier import verify_email
def test_deliverable_result():
fake = MagicMock(status_code=200)
fake.json.return_value = {"email": "jane@acme.com", "status": "deliverable", "reason": "accepted_email"}
with patch("verifier.requests.get", return_value=fake) as get:
result = verify_email("jane@acme.com")
assert result["status"] == "deliverable"
assert get.call_args.kwargs["params"] == {"email": "jane@acme.com"}
assert get.call_args.kwargs["timeout"] == 10.0Key takeaways
0/4Try it on a real address
Run syntax, DNS, MX, disposable, and role checks on any address. No signup needed.
Enter an address and press Check.
Runs real syntax, DNS and mail-server checks.
This demo checks syntax, domain, mail server, disposable and role-address rules. Sign up free for live mailbox checks.
Frequently asked questions
Verify addresses with one API call.
250 free checks every month, recurring. Unknown results are never billed.