Search "validate email Python" and you'll drown in regexes. Regex has its place, but it only answers "does this look like an email?", not "can this address actually receive mail?" Here are the layers, and how to add the ones regex can't.
Layer 1: syntax (regex)
A simple pattern rejects obvious nonsense. Don't chase the giant RFC 5322 monster regex; it's huge and still tells you nothing about whether the domain exists.
import re
EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
def looks_valid(email: str) -> bool:
return bool(EMAIL_RE.match(email))
Layer 2: domain and MX records
A real address needs a domain that actually accepts mail. Check its MX records with dnspython:
import dns.resolver # pip install dnspython
def domain_accepts_mail(domain: str) -> bool:
try:
return len(dns.resolver.resolve(domain, "MX")) > 0
except Exception:
return False
This removes dead domains and a lot of fakes. It's server-side only, though, and it won't tell you whether an address is disposable, a role account, or a likely typo.
Layer 3: disposable, role, and typo detection
These need data you don't really want to maintain yourself (a current list of throwaway domains, a list of common providers for typo suggestions, and so on). In practice most people reach for an email verification API here, which returns the lot in one call. The response shape is similar across providers:
import requests
def verify(email: str) -> dict:
res = requests.post(
"https://your-verification-api.example/v1/verify",
headers={"x-api-key": "YOUR_KEY"},
json={"email": email},
timeout=10,
)
res.raise_for_status()
return res.json()
# Typical response:
# {
# "status": "risky", "score": 75,
# "checks": {"syntax": True, "mx_found": True, "disposable": False, "role": False},
# "did_you_mean": "gmail.com"
# }
Putting it together at signup
In a Flask or Django view, verify on the server before creating the account:
@app.post("/signup")
def signup():
email = request.form["email"]
result = verify(email)
if result["status"] == "undeliverable":
return {"error": "That email can't receive mail."}, 400
# "risky" -> warn but allow; "deliverable" -> proceed
create_account(email)
return {"ok": True}
Show the did_you_mean hint in your form to recover typos, reject undeliverable on submit, and treat risky as a soft warning rather than a hard block.
Summary
- Regex = "looks like an email." Keep it simple.
- MX lookup = "the domain can receive mail." Worth doing.
- Disposable, role, and typo detection plus a single score = the part that actually cleans your signups, and the part not worth maintaining yourself.
Disclosure: I build an email verification API, so this is a topic I work on daily.
The approach above is vendor-neutral; any verification API follows the same shape.
Top comments (1)
Hi, how are you? how can I contact you?