1. Syntax check with email-validator
pip install email-validator
from email_validator import validate_email, EmailNotValidError
try:
info = validate_email("Anna@Example.com", check_deliverability=False)
email = info.normalized # "Anna@example.com"
except EmailNotValidError as e:
print(str(e))
check_deliverability=True additionally checks that the domain has DNS records for email.
2. MX lookup with dnspython
import dns.resolver # pip install dnspython
def has_mx(domain: str) -> bool:
try:
return len(dns.resolver.resolve(domain, "MX")) > 0
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers):
return False
3. Check that the mailbox exists
import os
import requests
def validate(email: str) -> dict:
res = requests.get(
"https://api.emailvalidation.io/v1/info",
params={"email": email},
headers={"apikey": os.environ["EMAILVALIDATION_API_KEY"]},
timeout=30,
)
res.raise_for_status()
return res.json()
result = validate("anna@company.com")
print(result["state"], result["reason"], result["score"])
4. Validate a CSV file
import csv
with open("contacts.csv") as src, open("contacts.validated.csv", "w", newline="") as dst:
reader = csv.DictReader(src)
writer = csv.DictWriter(dst, fieldnames=reader.fieldnames + ["state", "reason", "disposable"])
writer.writeheader()
for row in reader:
r = validate(row["email"])
writer.writerow({**row, "state": r["state"], "reason": r["reason"], "disposable": r["disposable"]})
For large files, run requests in parallel with a thread pool (5–10 workers) and respect your plan’s rate limit. For a quick one-off list of up to 50 addresses, use the bulk email validator.