Back to Blog
Guide2026-04-076 min read

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

Email Validation in Python — requests + Flask Guide

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.
Python Email Validation — From Basic to Production
User Input
Regex / Library
API Validation
Valid
Risky
Invalid
Layer syntax checks with API verification for production-grade validation

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:

✦ Quick Integration

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 (re module) — 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

Keep Reading

More guides and insights on email validation.