Skip to content

Email

This content is for v0.3.1. Switch to the latest version for up-to-date documentation.

Canonicalizes one email address per call — standard, obfuscated, or localhost — to a lowercased addr-spec.

In plain language: give it something that looks like an email address and it hands back the single correct lowercase address if a real specification says it is valid, plus a citation of that specification. If it does not look like an email, or no spec accepts it, it tells you so without raising an exception.


What it recognizes — and what it does not

Section titled “What it recognizes — and what it does not”
RecognizesDoes not recognize
Standard user@example.com (case-insensitive)Display-name forms like Bob <bob@example.com> — pass the addr-spec part
Obfuscated user at example dot com (only when include_obfuscated=True)Arbitrary prose that happens to mention a domain without address structure
admin@localhost (only when include_localhost=True, the default)

Grammars are syntax-only; whether the match is valid is decided by the rules (see Pipeline).


Default output_format is "email" — a single format, always a lowercased addr-spec.

from paxman.capabilities import Email
import paxman
paxman.register_all_shipped()
contract = Email.create_contract()
paxman.canonicalize(
"USER@Example.COM", contract
).canonicalized_value # "user@example.com"

There are no offered alternatives for Email — None, "default", and "email" all resolve to the same rendering; any other value raises ContractError.


contract = Email.create_contract(
include_obfuscated=False, # bool, default False — recognize "user at example dot com"
include_localhost=True, # bool, default True — recognize admin@localhost
# plus every common field: excluded_rules / pinned_rules / year / output_format / extra_grammars
)
  • Toggling include_obfuscated or include_localhost changes which grammars run. When the relevant grammar is off, the corresponding input is MISSING (never seen), not INVALID.
  • Use excluded_rules / pinned_rules / year to control which rules validate — a recognized input that no rule accepts becomes INVALID.
  • output_format is ignored beyond validation; it only shapes the rendered string.

See Contracts and the API Reference for the full policy.


InputContract tweakStatusValue / why
user@example.comdefaultsSUCCESS"user@example.com"
USER@Example.COMdefaultsSUCCESSlowercased to "user@example.com"
user at example dot comdefaults (include_obfuscated=False)MISSINGgrammar not active — nothing seen
user at example dot cominclude_obfuscated=TrueSUCCESS"user@example.com"
admin@localhostdefaultsSUCCESSlocalhost path via RFC 6761
admin@localhostinclude_localhost=FalseMISSINGgrammar not active
admin@localhostexcluded_rules=["Section 6.3-localhost"]INVALIDrecognized but no rule accepts it
@@anyMISSINGno email pattern at all
alice@example.com and bob@example.organyraises MultipleMentionsErrortwo distinct mentions — split first (see Segmentation)

import paxman
from paxman.capabilities import Email
from paxman.core.domain import Resolution
paxman.register_all_shipped()
contract = Email.create_contract(include_obfuscated=True, include_localhost=True)
rows = [
"user@Example.COM",
"user at example dot com",
"admin@localhost",
"not an email",
"alice@example.com and bob@example.org",
]
for text in rows:
try:
r = paxman.canonicalize(text, contract)
except Exception as e:
print(f"{text!r:35} → exception {type(e).__name__}: {e}")
continue
tag = r.status.value
val = r.canonicalized_value if r.status == Resolution.SUCCESS else "—"
print(f"{text!r:35} → {tag:10} {val!r}")

Validated values carry provenance from:

  • IETF RFC 5322 — Internet Message Format, Section 3.4.1 addr-spec
  • IETF RFC 6761 — Special-Use Domain Names, Section 6.3 (localhost)

Inspect candidate.provenance and candidate.validation_rule for the exact citation:

for c in result.candidates:
p = c.provenance[0]
print(
c.value,
"via",
c.validation_rule,
f"({p.authority}: {p.specification_name}, {p.publication_year})",
)

On SUCCESS, result.span is the exact [start, end) of the email in the original input — useful for highlighting in UIs or logging. On MISSING / INVALID it is None; on AMBIGUOUS (rare for Email) use each candidate.span.

See also: Execution Result, Provenance, Segmentation.