Python’s email.utils module provides utilities for parsing and formatting RFC 2822 email addresses and dates. from email import utils. Address functions: utils.parseaddr("Alice Smith <[email protected]>") → ("Alice Smith", "[email protected]"); utils.formataddr(("Alice", "[email protected]")) → "Alice <[email protected]>" (quotes display name if needed); utils.getaddresses(header_values) → [("name", "addr"), ...] (handles multiple comma-separated addresses). Date functions: utils.parsedate("Mon, 01 Feb 2029 12:00:00 +0000") → 9-tuple; utils.parsedate_tz(s) → 10-tuple (last element = UTC offset in seconds); utils.parsedate_to_datetime(s) → aware datetime; utils.formatdate(timetuple=None, localtime=False, usegmt=False) → RFC 2822 str; utils.format_datetime(dt, usegmt=False) → RFC 2822 str. Message ID: utils.make_msgid(idstring=None, domain=None) → "<random@domain>". RFC 2231: utils.encode_rfc2231(s, charset=None, language=None) / utils.decode_rfc2231(s) / utils.collapse_rfc2231_value(value). Claude Code generates address parsers, date formatters, message-id generators, header extractors, and RFC-compliant email construction utilities.
CLAUDE.md for email.utils
## email.utils Stack
- Stdlib: from email import utils
- Addr: utils.parseaddr("Alice <[email protected]>") # ("Alice", "alice@...")
- utils.formataddr(("Alice", "[email protected]"))
- utils.getaddresses(["Alice <[email protected]>, Bob <[email protected]>"])
- # [("Alice", "[email protected]"), ("Bob", "[email protected]")]
- Date: utils.parsedate("Mon, 01 Feb 2029 12:00:00 +0000")
- utils.parsedate_tz(s) # 10-tuple with UTC offset
- utils.parsedate_to_datetime(s) # → aware datetime
- utils.formatdate(localtime=True)
- utils.format_datetime(dt)
- MsgID: utils.make_msgid(domain="example.com")
email.utils RFC 2822 Utilities Pipeline
# app/emailutilsutil.py — parse, format, date, msgid, rfc2231, validate
from __future__ import annotations
import re
import time
from datetime import datetime, timezone
from email import utils as _utils
from dataclasses import dataclass, field
from typing import Any
# ─────────────────────────────────────────────────────────────────────────────
# 1. Address parsing and formatting
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class EmailAddress:
display_name: str
addr_spec: str
@property
def domain(self) -> str:
return self.addr_spec.split("@", 1)[-1] if "@" in self.addr_spec else ""
@property
def local_part(self) -> str:
return self.addr_spec.split("@", 1)[0] if "@" in self.addr_spec else self.addr_spec
def __str__(self) -> str:
return _utils.formataddr((self.display_name, self.addr_spec))
def parse_address(text: str) -> EmailAddress | None:
"""
Parse a single RFC 2822 address string.
Returns None if the result has no local@domain form.
Example:
a = parse_address("Alice Smith <[email protected]>")
print(a.display_name, a.domain)
"""
name, addr = _utils.parseaddr(text)
if not addr:
return None
return EmailAddress(display_name=name, addr_spec=addr)
def parse_address_list(header_values: "list[str]") -> list[EmailAddress]:
"""
Parse one or more address-header values (may include comma-separated addresses).
Example:
addrs = parse_address_list(["Alice <[email protected]>, Bob <[email protected]>", "[email protected]"])
for a in addrs:
print(a.addr_spec)
"""
pairs = _utils.getaddresses(header_values)
return [EmailAddress(display_name=n, addr_spec=a)
for n, a in pairs if a]
def format_address(display_name: str, addr_spec: str) -> str:
"""
Format a display name + addr_spec as an RFC 2822 address string.
Example:
fmt = format_address("O'Brien, Pat", "[email protected]")
# '"O\'Brien, Pat" <[email protected]>'
"""
return _utils.formataddr((display_name, addr_spec))
def extract_domains(header_values: "list[str]") -> list[str]:
"""
Return unique domains from a list of address-header values.
Example:
doms = extract_domains(["To: [email protected], [email protected]"])
print(doms) # ["example.com", "acme.com"]
"""
addrs = parse_address_list(header_values)
seen: dict[str, bool] = {}
result: list[str] = []
for a in addrs:
d = a.domain.lower()
if d and d not in seen:
seen[d] = True
result.append(d)
return result
# ─────────────────────────────────────────────────────────────────────────────
# 2. Date parsing and formatting
# ─────────────────────────────────────────────────────────────────────────────
def parse_date(date_str: str) -> datetime | None:
"""
Parse an RFC 2822 date string to an aware datetime.
Returns None on failure.
Example:
dt = parse_date("Mon, 10 Feb 2029 12:00:00 +0000")
print(dt.isoformat())
"""
try:
return _utils.parsedate_to_datetime(date_str)
except Exception:
return None
def format_date(dt: "datetime | None" = None,
*,
localtime: bool = False,
usegmt: bool = False) -> str:
"""
Format a datetime (or now) as an RFC 2822 date string.
Example:
now_str = format_date() # current UTC time
local_str = format_date(localtime=True)
dt_str = format_date(datetime.now(timezone.utc))
"""
if dt is None:
return _utils.formatdate(localtime=localtime, usegmt=usegmt)
return _utils.format_datetime(dt, usegmt=usegmt)
def date_age_seconds(date_str: str) -> float | None:
"""
Return how many seconds ago the date was, or None on failure.
Example:
age = date_age_seconds(msg["Date"])
if age and age > 86400:
print("message is older than 24 hours")
"""
dt = parse_date(date_str)
if dt is None:
return None
now = datetime.now(timezone.utc)
return (now - dt).total_seconds()
# ─────────────────────────────────────────────────────────────────────────────
# 3. Message-ID generation and parsing
# ─────────────────────────────────────────────────────────────────────────────
def new_message_id(label: str = "",
domain: str = "example.com") -> str:
"""
Generate a globally unique Message-ID.
Example:
mid = new_message_id(domain="myapp.com")
# "<[email protected]>"
"""
return _utils.make_msgid(idstring=label or None, domain=domain)
def parse_message_id(mid: str) -> "tuple[str, str] | None":
"""
Parse a Message-ID into (unique_part, domain).
Example:
uid, dom = parse_message_id("<[email protected]>")
"""
m = re.match(r"<([^@>]+)@([^>]+)>", mid.strip())
if not m:
return None
return m.group(1), m.group(2)
# ─────────────────────────────────────────────────────────────────────────────
# 4. RFC 2231 parameter encoding
# ─────────────────────────────────────────────────────────────────────────────
def encode_param(value: str,
charset: str = "utf-8",
language: str = "") -> str:
"""
Encode a parameter value as RFC 2231 for use in Content-Disposition / Content-Type.
Example:
enc = encode_param("resume_été.pdf")
# "utf-8''resume_%C3%A9t%C3%A9.pdf"
"""
return _utils.encode_rfc2231(value, charset, language)
def decode_param(encoded: str) -> str:
"""
Decode an RFC 2231 encoded parameter value to Unicode.
Example:
text = decode_param("utf-8''resume_%C3%A9t%C3%A9.pdf")
# "resume_été.pdf"
"""
parts = _utils.decode_rfc2231(encoded)
return _utils.collapse_rfc2231_value(parts)
# ─────────────────────────────────────────────────────────────────────────────
# 5. Address validation helpers
# ─────────────────────────────────────────────────────────────────────────────
_ADDR_RE = re.compile(
r"^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$"
)
def is_valid_email(addr: str) -> bool:
"""
Quick RFC 5321-ish email address validation (structural, not DNS).
Example:
is_valid_email("[email protected]") # True
is_valid_email("not-an-email") # False
"""
return bool(_ADDR_RE.match(addr.strip()))
def deduplicate_addresses(header_values: "list[str]") -> list[EmailAddress]:
"""
Parse address-header values and return unique addresses (case-insensitive on addr_spec).
Example:
uniq = deduplicate_addresses([
"Alice <[email protected]>, Bob <[email protected]>",
"[email protected]",
])
print([a.addr_spec for a in uniq])
"""
seen: dict[str, bool] = {}
result: list[EmailAddress] = []
for addr in parse_address_list(header_values):
key = addr.addr_spec.lower()
if key not in seen:
seen[key] = True
result.append(addr)
return result
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== email.utils demo ===")
# ── parse_address ──────────────────────────────────────────────────────
print("\n--- parse_address ---")
for text in [
"Alice Smith <[email protected]>",
"[email protected]",
'"O\'Brien, Pat" <[email protected]>',
"invalid-address",
]:
a = parse_address(text)
if a:
print(f" {text!r}")
print(f" display={a.display_name!r} addr={a.addr_spec!r} domain={a.domain!r}")
else:
print(f" {text!r} → None")
# ── parse_address_list ────────────────────────────────────────────────
print("\n--- parse_address_list ---")
raw = ["Alice <[email protected]>, Bob <[email protected]>", "[email protected]"]
for addr in parse_address_list(raw):
print(f" {str(addr)!r}")
# ── extract_domains ───────────────────────────────────────────────────
print("\n--- extract_domains ---")
print(f" {extract_domains(raw)}")
# ── parse_date / format_date ──────────────────────────────────────────
print("\n--- dates ---")
date_str = "Mon, 10 Feb 2029 12:30:45 +0000"
dt = parse_date(date_str)
print(f" parsed : {dt}")
print(f" formatted: {format_date(dt)!r}")
print(f" age_sec : {date_age_seconds(date_str):.0f}s")
print(f" now : {format_date()!r}")
# ── new_message_id ───────────────────────────────────────────────────
print("\n--- message_id ---")
mid = new_message_id(domain="example.com")
print(f" generated: {mid!r}")
parsed = parse_message_id(mid)
print(f" parsed : uid={parsed[0]!r} domain={parsed[1]!r}")
# ── RFC 2231 param ────────────────────────────────────────────────────
print("\n--- RFC 2231 ---")
filename = "résumé été 2029.pdf"
encoded = encode_param(filename)
decoded = decode_param(encoded)
print(f" original : {filename!r}")
print(f" encoded : {encoded!r}")
print(f" decoded : {decoded!r}")
print(f" round-trip: {decoded == filename}")
# ── is_valid_email ────────────────────────────────────────────────────
print("\n--- is_valid_email ---")
for addr in ["[email protected]", "[email protected]", "notanemail", "@nolocalpart.com"]:
print(f" {addr!r:35s} → {is_valid_email(addr)}")
print("\n=== done ===")
For the email.headerregistry.Address companion — email.headerregistry.Address(addr_spec="[email protected]") provides structured address objects with .display_name, .username, .domain attributes used by email.policy.default-parsed messages — use Address for modern EmailMessage-based code requiring typed header objects; use email.utils.parseaddr() for simple name/addr splitting of raw strings in scripts and legacy email.message.Message code. For the email-validator (PyPI) alternative — email_validator.validate_email("[email protected]").email performs full RFC 5321/5322 validation including DNS MX-record checks, international domain names, and deliverability scoring — use email-validator for user registration forms and subscription systems where deliverability matters; use email.utils for internal address parsing, formatting, and date handling where structural correctness is sufficient. The Claude Skills 360 bundle includes email.utils skill sets covering EmailAddress/parse_address()/parse_address_list()/format_address()/extract_domains() address tools, parse_date()/format_date()/date_age_seconds() date helpers, new_message_id()/parse_message_id() Message-ID utilities, encode_param()/decode_param() RFC 2231 helpers, and is_valid_email()/deduplicate_addresses() validators. Start with the free tier to try RFC 2822 utility patterns and email.utils pipeline code generation.