Skip to main content

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 localhost is 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 example xn--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.

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)

Source distribution for emailsyntax 0.2.0
File Size Uploaded Provenance
emailsyntax-0.2.0.tar.gz 17.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for emailsyntax 0.2.0
File Interpreter ABI Platform
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.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page