The email-validator Python package validates that a string is a genuinely well-formed, deliverable email address rather than merely matching a regular expression, and using it correctly means treating validation as one input-hardening layer, not the whole defense. If you have reached for a hand-rolled regex to check email input, the email-validator Python library exists precisely because that regex is almost certainly wrong — the real grammar for an email address is far more complicated than any pattern you would write by hand.
The package, maintained by Joshua Tauberer, targets Python 3.8+ and has been downloaded hundreds of millions of times. It is the de facto standard when you want more than a superficial format check.
Why regex email validation is a trap
The instinct to write ^[^@]+@[^@]+\.[^@]+$ is understandable and almost always a mistake. The formal grammar for email addresses spans multiple RFCs and includes quoted local parts, comments, internationalized (Unicode) domains, and internationalized local parts. A regex that accepts every valid address is unreadable; a readable regex rejects legitimate addresses and frustrates real users.
Worse, a naive regex gives false confidence. It passes user@example.com and rejects obvious garbage, so it looks like it works — until a user with a perfectly valid internationalized address, or a plus-addressed alias, or an address with a subdomain gets silently turned away at signup. The python email validator ecosystem consolidated around a dedicated library exactly because everyone eventually hits these edge cases.
The email validator python developers reach for solves two separate problems that regex conflates: is this syntactically a valid address, and can this domain actually receive mail. Those are different questions with different failure modes, and treating them separately is the core of using the tool well.
Installing and using email-validator
Installation is a single command:
pip install email-validator
The primary entry point is the validate_email function. A minimal, correct usage looks like this:
from email_validator import validate_email, EmailNotValidError
def check(email: str):
try:
emailinfo = validate_email(email, check_deliverability=True)
# Use the normalized form, not the raw input
return emailinfo.normalized
except EmailNotValidError as e:
# e carries a friendly, human-readable message
return None
Two details matter here. First, note the import — the package is installed as email-validator but imported as email_validator with an underscore. That mismatch trips up nearly everyone the first time, and searching for both python email-validator and python email_validator will surface the same library. Second, always use the returned normalized value rather than the raw string the user typed. Normalization lowercases the domain, applies Unicode normalization, and produces a canonical form you should store and compare against — critical for preventing duplicate accounts that differ only by case or Unicode representation.
The check_deliverability=True flag makes the library perform a DNS lookup to confirm the domain has MX (or fallback A/AAAA) records, meaning it could plausibly receive mail. This catches typos like gmial.com that are syntactically perfect but point nowhere.
Deliverability checks and their trade-offs
Deliverability checking is powerful but has real operational implications you must plan for.
Because it performs a live DNS query, it introduces network latency and a dependency on DNS resolution being available and fast at the moment a user submits a form. In a high-throughput signup endpoint, a slow or timing-out resolver can become a bottleneck or a source of spurious rejections. The common pattern is to run syntax validation synchronously (fast, no network) and defer deliverability to an asynchronous step, or to disable it entirely in latency-sensitive paths.
Set check_deliverability=False when you validate the same address repeatedly, when you are inside a tight request budget, or when you validate in an environment without reliable outbound DNS such as some CI runners. The syntax check alone still eliminates the overwhelming majority of malformed input.
Also understand what deliverability does not prove. A domain having MX records does not mean the specific mailbox exists or that the address is not disposable. The library deliberately does not attempt SMTP callbacks to verify individual mailboxes, because that technique is unreliable, often blocked, and can get your IP flagged as abusive. If you need to confirm a real, reachable inbox, the only reliable method is sending a confirmation link — which you should be doing regardless.
Where validation fits in your security model
Input validation is a defensive layer, and email validation specifically closes a few concrete gaps, but it is not a substitute for the controls downstream of it.
Validating and normalizing email input reduces the surface for a class of injection issues where an unvalidated address flows into a downstream system — an SMTP header, a SQL query, a log line, or an LDAP filter. An attacker who can smuggle a newline into an email field may attempt header injection against your mail-sending code. Rejecting anything that is not a well-formed single address, and using the normalized form, removes that vector at the boundary. That said, the real fix for header injection lives in how your mail library constructs messages, not only in validation; validation is defense in depth.
Keep the dependency current. The email-validator package has no known direct CVEs in the common advisory databases at the time of writing, and it releases regularly, but any input-parsing library is exactly the kind of component worth watching for advisories, since parsers are a classic source of denial-of-service and edge-case bugs. Pin it in your lockfile and let your dependency scanner track it. An SCA tool such as Safeguard will flag the package the moment a new advisory lands, including when it sits as a transitive dependency of something else. For teams doing this at scale, integrating software composition analysis means you are not manually watching a changelog.
The broader principle: validate at the boundary, normalize before you store, and never treat a passing validation as proof that the value is safe everywhere it later travels. Encode and parameterize at each downstream sink regardless.
FAQ
Why does import email-validator fail in Python?
Because the import name uses an underscore. Install with pip install email-validator but import as from email_validator import validate_email. The hyphenated name is only the PyPI package name; Python module names cannot contain hyphens.
Should I always enable deliverability checking?
No. Enable it in interactive signup flows where a live DNS lookup is affordable and catches domain typos. Disable it (check_deliverability=False) in latency-sensitive endpoints, in bulk validation, or in environments without reliable DNS. Syntax validation alone still rejects the vast majority of bad input.
Does email-validator confirm the mailbox actually exists?
No. It confirms the domain could receive mail (via MX/A records) but deliberately avoids per-mailbox SMTP callbacks, which are unreliable and can get your IP blocked. The only dependable way to confirm a real inbox is sending a confirmation email.
Is the email-validator Python package safe to use?
It has no known direct vulnerabilities in the common advisory databases and is actively maintained. As with any input-parsing dependency, pin the version, keep it updated, and let a dependency scanner watch for future advisories rather than assuming it will always stay clean.