Email Validation in Python — requests + Flask Guide
Regex checks format. It does not check whether a mailbox exists, the domain is disposable, or the user meant 'gmail.com' instead of 'gmial.com'. Here is how to validate emails in Python — from a quick regex to a production Flask integration — with code you can ship today.
MailSentry·Email Validation API

TL;DR
- •Python's re module and libraries like email-validator handle syntax, but they cannot detect disposable domains, catch typos, or verify that a mailbox exists.
- •Use the requests library to call a validation API from any Python script, cron job, or backend service — a single POST that returns 8 validation layers in one response.
- •In Flask, build a decorator or middleware that validates emails on signup and checkout routes, with caching and fail-open error handling for production resilience.
Email validation in Python is a problem most developers think they have solved — until bounce rates climb, disposable signups pile up, and their sender reputation takes a hit. A regex or the email-validator library catches formatting errors. It will not catch user@gmial.com, test@guerrillamail.com, or a mailbox that was deleted three months ago. This guide covers every practical approach to email validation in Python, from a quick regex to a production-ready Flask integration with the MailSentry API.
Approach 1: Regex-Based Email Validation in Python
Python's re module handles basic format checks. This costs nothing, runs in microseconds, and prevents obviously broken input from reaching your server:
import re
EMAIL_PATTERN = re.compile(
r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+"
r"@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?"
r"(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
)
def is_valid_syntax(email: str) -> bool:
return bool(EMAIL_PATTERN.match(email))
print(is_valid_syntax("dev@example.com")) # True
print(is_valid_syntax("not-an-email")) # False
print(is_valid_syntax("user@gmial.com")) # True — and that is the problem
Regex validates structure, not reality. user@gmial.com passes because the format is technically correct. The domain is not. For a quick client-side or form-level filter, regex is fine. For anything that touches your database, you need more.
Approach 2: The email-validator Library
The email-validator package (install with pip install email-validator) goes beyond regex. It validates format, checks the domain for MX or A records via DNS, and rejects addresses with invalid TLDs:
from email_validator import validate_email, EmailNotValidError
def check_email(email: str) -> dict:
try:
result = validate_email(email, check_deliverability=True)
return {"valid": True, "normalized": result.normalized}
except EmailNotValidError as e:
return {"valid": False, "error": str(e)}
print(check_email("dev@example.com"))
# {'valid': True, 'normalized': 'dev@example.com'}
print(check_email("user@nonexistent-domain-xyz.com"))
# {'valid': False, 'error': 'The domain name ... does not exist.'}
This is a solid step up from regex — it catches domains that cannot receive email. But it still has blind spots. It cannot verify whether the specific mailbox exists on a valid domain. It has no awareness of disposable email providers like Guerrilla Mail or Temp Mail. And it will not notice that gmial.com is a typo for gmail.com.
Approach 3: API-Based Email Validation in Python with requests
For production applications, the most reliable approach is calling a validation API that checks syntax, DNS, SMTP mailbox existence, disposable status, typo patterns, and risk signals in a single request. Here is how to call the MailSentry API using Python's requests library:
import requests
import os
MAILSENTRY_API_KEY = os.environ["MAILSENTRY_API_KEY"]
def validate_email(email: str) -> dict:
response = requests.post(
"https://api.mailsentry.net/v1/validate",
headers={
"Authorization": f"Bearer {MAILSENTRY_API_KEY}",
"Content-Type": "application/json",
},
json={"email": email},
timeout=10,
)
response.raise_for_status()
return response.json()
result = validate_email("john@gmial.com")
print(result)
The response includes everything you need to make an informed decision:
{
"email": "john@gmial.com",
"is_valid": false,
"checks": {
"syntax": true,
"mx_records": false,
"smtp_exists": false,
"is_disposable": false,
"is_role_based": false
},
"risk_score": 85,
"suggestion": "john@gmail.com",
"reason": "Domain does not have valid MX records"
}
One API call, 8 validation layers: syntax, MX records, SMTP verification, disposable detection, role-based flagging, free provider detection, typo correction, and risk scoring. The typo suggestion alone — showing "Did you mean john@gmail.com?" instead of "Invalid email" — is the difference between a recovered user and an abandoned signup.
Building a Reusable Validation Module
Wrap the API call in a module with caching and fail-open error handling so you can reuse it across your application:
Solve this with one API call
8 validation layers, real-time results, sub-50ms response. Drop it into your signup flow in under 5 minutes.
Try MailSentry Free →# email_check.py
import requests
import os
from functools import lru_cache
from typing import Optional
MAILSENTRY_API_KEY = os.environ["MAILSENTRY_API_KEY"]
API_URL = "https://api.mailsentry.net/v1/validate"
@lru_cache(maxsize=1024)
def validate(email: str) -> dict:
"""Validate an email via MailSentry. Fails open on network errors."""
try:
resp = requests.post(
API_URL,
headers={
"Authorization": f"Bearer {MAILSENTRY_API_KEY}",
"Content-Type": "application/json",
},
json={"email": email},
timeout=10,
)
resp.raise_for_status()
return resp.json()
except requests.RequestException:
# Fail open — accept the email and verify later
return {"is_valid": True, "risk_score": None, "fail_open": True}
def is_acceptable(email: str, max_risk: int = 70) -> bool:
"""Returns True if the email is valid and below the risk threshold."""
result = validate(email)
if not result.get("is_valid"):
return False
if result.get("is_disposable"):
return False
risk = result.get("risk_score")
if risk is not None and risk > max_risk:
return False
return True
Key design decisions here: lru_cache avoids redundant API calls when the same email is validated multiple times in a session. The except block fails open — if MailSentry is unreachable, accept the email and queue it for later verification. Never let a third-party outage block your users from signing up.
Integrating Email Validation in Flask
For Flask applications, create a decorator that validates emails on any route that accepts them:
from flask import Flask, request, jsonify
from email_check import validate, is_acceptable
app = Flask(__name__)
def require_valid_email(f):
"""Decorator that validates the email field in the request body."""
from functools import wraps
@wraps(f)
def decorated(*args, **kwargs):
data = request.get_json(silent=True) or {}
email = data.get("email", "").strip().lower()
if not email:
return jsonify({"error": "Email is required"}), 422
result = validate(email)
if not result.get("is_valid"):
suggestion = result.get("suggestion")
error = "Please provide a valid email address."
if suggestion:
error = f"Did you mean {suggestion}?"
return jsonify({"error": error}), 422
if result.get("is_disposable"):
return jsonify({
"error": "Disposable email addresses are not allowed."
}), 422
risk = result.get("risk_score")
if risk is not None and risk > 75:
return jsonify({
"error": "This email address could not be verified."
}), 422
# Attach validation result for the route to use
request.email_validation = result
return f(*args, **kwargs)
return decorated
@app.route("/signup", methods=["POST"])
@require_valid_email
def signup():
data = request.get_json()
email = data["email"].strip().lower()
# Email is validated — create the account
# ...
return jsonify({"message": "Account created", "email": email}), 201
@app.route("/checkout", methods=["POST"])
@require_valid_email
def checkout():
data = request.get_json()
# Email is validated — process the order
# ...
return jsonify({"message": "Order confirmed"}), 200
The @require_valid_email decorator keeps your route handlers clean. Every endpoint that accepts an email gets the same validation logic — disposable detection, risk scoring, typo suggestions — without duplicating code. Add it to signup, checkout, invite, and contact form routes.
Batch Validation with a Python Script
If you have an existing list of emails to clean — a CSV export from your database or CRM — you can validate them in bulk with a simple script:
import csv
import time
from email_check import validate
def validate_csv(input_path: str, output_path: str):
with open(input_path) as infile, open(output_path, "w", newline="") as outfile:
reader = csv.DictReader(infile)
fieldnames = reader.fieldnames + ["is_valid", "risk_score", "suggestion"]
writer = csv.DictWriter(outfile, fieldnames=fieldnames)
writer.writeheader()
for row in reader:
result = validate(row["email"])
row["is_valid"] = result.get("is_valid", "")
row["risk_score"] = result.get("risk_score", "")
row["suggestion"] = result.get("suggestion", "")
writer.writerow(row)
time.sleep(0.05) # respect rate limits
validate_csv("users.csv", "users_validated.csv")
This reads each email, validates it, and writes the result alongside the original data. The time.sleep(0.05) keeps you within rate limits. For larger lists, MailSentry also offers a dedicated bulk endpoint that processes thousands of addresses in a single request — see the API docs for details.
What Regex and Libraries Miss
Here is a concrete summary of what each approach catches — and what it misses:
- Regex (
remodule) — catches missing @ signs, spaces, and obviously malformed strings. Misses everything else. - email-validator library — adds DNS/MX checks on top of syntax. Catches domains that cannot receive email. Misses disposable providers, specific mailbox existence, typos, and risk signals.
- API validation (MailSentry) — catches all of the above plus disposable domains, role-based addresses (info@, admin@), typo corrections, catch-all detection, and assigns a risk score you can use to make tiered decisions.
For production Python applications, the practical pattern is: use regex or email-validator as a fast client-side or form-level filter, then validate server-side with an API call before the email enters your database. The two layers complement each other — the first is instant and free, the second is comprehensive and authoritative.
Key Takeaways
Email validation in Python ranges from a one-line regex to a full API-backed pipeline. Regex catches formatting errors. The email-validator library adds DNS awareness. But for production applications — where bounce rates, disposable abuse, and sender reputation matter — you need API validation that checks the mailbox, detects disposable providers, suggests typo corrections, and scores risk. Build a reusable module with caching and fail-open handling, wire it into your Flask routes with a decorator, and every email that enters your system will be validated consistently.
MailSentry validates emails across 8 layers — syntax, MX records, SMTP verification, disposable detection, typo correction, role-based flagging, catch-all detection, and risk scoring — in a single API call. The free plan includes 1,000 checks per month with no credit card required. Start validating for free or explore the API docs to integrate in minutes.
Stop losing leads to bad emails
Validate emails in real-time with 8 layers of checks. Free plan includes 1,000 validations/month.
Start Free — No Credit Card →Setup takes 30 seconds