emailsyntax
Syntax-only email address validation and normalization, zero dependencies, no DNS or network
Install
pip install emailsyntax
Usage
from emailsyntax import is_valid, validate, validate_or_raise, normalize, split
is_valid("User@Example.com") # True
is_valid("user@localhost") # False (dotless domain)
is_valid("us..er@example.com") # False (consecutive dots)
normalize(" User.Name@Example.COM ") # "User.Name@example.com"
split("user@sub.example.co.uk") # ("user", "sub.example.co.uk")
A reason on failure
validate returns a typed ValidationResult that is truthy when valid and tells you why
when it is not:
result = validate("user@localhost")
result.valid # False
result.code # "domain_not_dotted"
result.reason # "domain must have at least two labels"
ok = validate("a.b@sub.example.com")
ok.local, ok.domain, ok.normalized # "a.b", "sub.example.com", "a.b@sub.example.com"
Validate or raise
validate_or_raise (alias parse) returns a parsed EmailAddress or raises
EmailSyntaxError, which subclasses ValueError and carries .code, .reason and
.address:
from emailsyntax import validate_or_raise, EmailSyntaxError
addr = validate_or_raise("User@Example.com")
addr.local, addr.domain, addr.normalized # "User", "Example.com", "User@example.com"
try:
validate_or_raise("nope")
except EmailSyntaxError as error:
error.code # "no_at"
Opt-in shapes: quoted, IP literals, unicode
Defaults are strict ASCII. The richer RFC 5321/5322 shapes are opt-in keyword flags, so turning them on never surprises existing callers:
is_valid('"john doe"@example.com', allow_quoted=True) # True
is_valid("user@[192.168.0.1]", allow_ip_literal=True) # True
is_valid("user@[IPv6:2001:db8::1]", allow_ip_literal=True) # True
is_valid("josé@example.com", allow_unicode=True) # True (EAI local part)
is_valid("user@münchen.de", allow_unicode=True) # True (IDN domain)
Every function accepts allow_unicode, allow_quoted and allow_ip_literal.
Command line
A console script ships with the package:
emailsyntax user@example.com # exit 0 and a line when valid
emailsyntax user@localhost # exit 1 and the reason when invalid
emailsyntax --allow-ip-literal user@[192.168.0.1]
printf 'a@b.co\nbad\n' | emailsyntax --stdin # one address per line
emailsyntax --json user@example.com # machine-readable output
emailsyntax --version
Valid input exits 0, invalid input exits 1, and usage problems print to stderr. Run
emailsyntax --help for every flag with runnable examples.
What it checks
This is a pure syntax check. It never looks up DNS or MX records, never contacts a mail server and never touches the network. A valid result means the address is well formed, not that a mailbox exists or can receive mail. With the default options it follows the practical strict shape (no quoted strings, no domain literals, ASCII only), referencing RFC 5321/5322 behaviour rather than implementing the full obsolete grammar. The rules enforced:
- Exactly one
@separating a non-empty local part and domain (the last@outside a quoted string is the separator). - Local part up to 64 characters, made of dot-separated atoms using
A-Z a-z 0-9 ! # $ % & ' * + / = ? ^ _ `` { | } ~ -. No leading, trailing or consecutive dots. - Domain up to 255 characters with at least two labels: a dotless domain such as
localhostis rejected. Each label is 1 to 63 characters of letters, digits and hyphens, with no leading or trailing hyphen and no empty label. - Whole address up to 254 characters.
- ASCII only by default. With
allow_unicode, internationalized domains (IDN U-labels) and unicode local parts (RFC 6531 EAI) are accepted; without it, an internationalized domain must be IDNA encoded to punycode (for examplexn--mnchen-3ya.de).
normalize lowercases the domain and trims surrounding whitespace, keeping the local-part case
(local parts are case sensitive per the spec, even though most providers ignore it). It also
canonicalises IP-literal domains.
Why
Most email helpers do more than check syntax. email-validator pulls in dnspython and checks
deliverability, and pyisemail aims at the full RFC grammar. When all you want is a fast,
offline "does this look like an address" gate plus a canonical form, those are heavy. This is the
small, typed, zero-dependency primitive for that: no network, no surprises, one job.
See GUIDE.md for task-oriented recipes.
License
MIT. See LICENSE.
Links
- Website: https://lianes8studio.com
- GitHub: https://github.com/JaviLianes8
- LinkedIn: https://www.linkedin.com/in/jlianes/
Metadata
Release files for emailsyntax 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| emailsyntax-0.2.0.tar.gz | 17.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| emailsyntax-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 30.8 kB
Release files / emailsyntax-0.2.0.tar.gz
| Download URL | emailsyntax-0.2.0.tar.gz |
|---|---|
| Size | 17.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4d42cd6c69f124491f1fc11b019918db492c3513feaed1f311b8c1617706cee3
|
|
BLAKE2b-256 checksum How to use checksums |
f87d1b34499d467a14c9fa381ef6a67f586897d726e131c1877929afa8714465
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.2
|
Release files / emailsyntax-0.2.0-py3-none-any.whl
| Download URL | emailsyntax-0.2.0-py3-none-any.whl |
|---|---|
| Size | 13.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8b37d73880b96f9987353066d61a593dfb3b87cb973c8ea586c7ba1c683c67c7
|
|
BLAKE2b-256 checksum How to use checksums |
830a576e8e109b1a66375805153ac7b79bd3102b1b50405ae388387e9cb58e06
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.2
|
Security for emailsyntax 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.