How to validate an email address in Python
Create a free accountPlans and credits
Validating an email address in Python takes two steps. A regular expression catches addresses that are badly written. Only a check against the receiving mail server can tell you whether the mailbox exists. This guide does both, with tested code: a small regex, then the Easy Email Verification API through requests. You can try every example with the free sandbox key eev_sandbox_key, without an account.
1. Check the format with a regular expression
Keep the pattern simple. It should reject obvious mistakes (no @, spaces, no dot in the domain) and leave everything else to the mailbox check:
import re
EMAIL_FORMAT = re.compile(r"^[^\s@]+@[^\s@]+\.[^\s@]+$")
def looks_like_email(address: str) -> bool:
"""Cheap first filter: something@something.something, no spaces."""
return bool(EMAIL_FORMAT.match(address))
looks_like_email("jane@example.com") # True
looks_like_email("jane.doe@example") # False: no dot in the domain
looks_like_email("jane doe@example.com") # False: space
Avoid the long "RFC 5322" patterns found online. They are hard to read, they still accept addresses that cannot receive mail, and some of them reject valid addresses such as jane+news@example.com.
Why a regex is not enough
A regex only proves that the text has the shape of an address. All of these pass the format check and still bounce:
| Address | Passes the regex | Problem |
|---|---|---|
jane@gmial.com | yes | The domain is a typo and does not exist |
former.employee@example.com | yes | The mailbox was closed |
jane@example-site.com | yes | The domain has no MX record, so it receives no mail |
x7k2@temp-inbox.example | yes | Disposable inbox that disappears in a few minutes |
To catch them you need DNS (does the domain exist and have an MX record?) and an SMTP conversation with the mail server (does it accept this mailbox?). Many networks block outgoing connections on port 25, and mail servers treat repeated checks from unknown hosts as abuse, so doing it from your own server is unreliable. An email verification API does those checks for you, without sending any email.
2. Verify the mailbox with the API
Install requests (pip install requests) and call GET /v1/verify. Keep the API key on the server, in the EEV_API_KEY environment variable; without it, the code below uses the sandbox key.
import os
import requests
API_URL = "https://api.easyemailverification.com/v1/verify"
API_KEY = os.environ.get("EEV_API_KEY", "eev_sandbox_key")
def verify_email(address: str) -> dict:
response = requests.get(
API_URL,
params={"email": address}, # requests URL-encodes it (+ becomes %2B)
headers={"X-API-Key": API_KEY},
timeout=30,
)
data = response.json()
if response.status_code != 200:
raise RuntimeError(f"EEV error {response.status_code}: {data.get('message')}")
return data
print(verify_email("valid@sandbox.easyemailverification.com"))
The answer for that sandbox address:
{
"email": "valid@sandbox.easyemailverification.com",
"result": "valid",
"reason": "accepted_email",
"disposable": false,
"accept_all": false,
"role": false,
"free": false,
"user": "valid",
"domain": "sandbox.easyemailverification.com",
"mx_record": "mx.sandbox.easyemailverification.com",
"mx_domain": "easyemailverification.com",
"safe_to_send": true,
"did_you_mean": "",
"success": true,
"message": "Sandbox response: no credits used",
"http_code": "200"
}
Errors come back with an HTTP status and a message: 401 for an unknown key, 402 when the account has no credits left. success: true only means the request was processed; it does not mean the address is deliverable.
3. Decide what to do with the result
result is valid, invalid or unknown. Turn it into a decision your application understands:
def decide(result: dict) -> str:
if result["did_you_mean"]:
return "suggest" # ask the user: did you mean ...?
if result["result"] == "valid" and result["safe_to_send"]:
return "accept"
if result["result"] == "invalid":
return "reject"
return "review" # unknown, catch-all or disposable: your policy decides
- accept: the mail server accepted the mailbox and no risk signal advises against it.
- reject: the address cannot receive mail. See the reason for details (
rejected_email,invalid_domain,no_mx_record…). - suggest:
did_you_meanhas a corrected address, for exampletypo@gmail.comfortypo@gmial.com. Show it to the user instead of rejecting. - review: an unknown result (the server timed out or greylisted the check), a catch-all domain or a disposable address. Do not treat unknown as invalid: accept the signup and confirm the address by email, or verify it again later.
4. Verify up to 50 addresses in one request
POST /v1/verify takes a list of up to 50 addresses and returns one result per address, in the same format:
def verify_emails(addresses: list[str]) -> list[dict]:
response = requests.post(
API_URL,
json={"emails": addresses}, # up to 50 per request
headers={"X-API-Key": API_KEY},
timeout=120,
)
data = response.json()
if response.status_code != 200:
raise RuntimeError(f"EEV error {response.status_code}: {data.get('message')}")
return data
for r in verify_emails(["valid@sandbox.easyemailverification.com", "typo@gmial.com"]):
print(r["email"], decide(r))
For whole files (thousands of addresses), use the bulk endpoints or upload the list in the dashboard instead of looping over this call: see bulk email verification and the API reference.
Test addresses
With the key eev_sandbox_key, these addresses return fixed answers and use no credits:
| Address | Answer |
|---|---|
valid@sandbox.easyemailverification.com | valid, accepted_email |
invalid@sandbox.easyemailverification.com | invalid, rejected_email |
unknown@sandbox.easyemailverification.com | unknown, timeout |
catchall@sandbox.easyemailverification.com | valid, accept_all: true, safe_to_send: false |
disposable@sandbox.easyemailverification.com | valid, disposable: true, safe_to_send: false |
typo@gmial.com | invalid, did_you_mean: typo@gmail.com |
quota@sandbox.easyemailverification.com | HTTP 402 (no credits) |
When it works, create a free account, generate a key under API settings and set it in EEV_API_KEY. Each verified address uses one credit; the free plan includes 50 verifications a day.
Next steps
- Prefer a library? The official package wraps everything above (single, batch, bulk, credits, sandbox):
pip install easyemailverification(PyPI). - Email Verification API: plans, limits and the other endpoints.
- Result codes: every
result,reasonand risk signal. - Examples in other languages on GitHub.
More ways to verify emails
- Email validation in Node.js: from regex to mailbox checks
- Email validation in Java with Spring Boot
- Validate email addresses in PHP without sending a message
- What every result code means
- API reference
- Check a single address for free
- All integrations
Using an AI assistant? It can follow this guide with you: give it validate-email-python.md.