Python’s email.policy module controls how the email package parses and generates messages — header encoding, line folding, line endings, and strictness. from email import policy. Built-in policy objects: policy.default — the modern EmailPolicy; headers are str, max line 78, \n linesep; suitable for in-memory work. policy.SMTP — EmailPolicy with linesep="\r\n" and max_line_length=998; for SMTP wire format. policy.SMTPUTF8 — like SMTP but utf8=True; allows non-ASCII headers without RFC 2047 encoding (requires SMTPUTF8-capable server). policy.HTTP — linesep="\n", max_line_length=None; for HTTP payloads. policy.compat32 — the legacy policy matching Python ≤3.2 behaviour; used by default in email.message.Message. Key attributes: pol.max_line_length (int, default 78); pol.linesep (str); pol.utf8 (bool); pol.raise_on_defect (bool, default False); pol.header_factory (callable). Clone and customise: my_pol = policy.SMTP.clone(max_line_length=200, raise_on_defect=True). Parse with policy: email.parser.BytesParser(policy=policy.default).parsebytes(raw). Generate with policy: msg.as_string(policy=policy.SMTP) or email.generator.BytesGenerator(fp, policy=policy.SMTP). Claude Code generates policy-aware message parsers, standards-compliant email generators, header validators, and RFC 5322 serializers.
CLAUDE.md for email.policy
## email.policy Stack
- Stdlib: from email import policy
- from email.parser import BytesParser, Parser
- from email.generator import BytesGenerator, Generator
- Policies:
- policy.default — modern str-header, \n linesep, max_line=78
- policy.SMTP — same + linesep="\r\n", max_line=998
- policy.SMTPUTF8 — SMTP + utf8=True (no RFC 2047 encoding)
- policy.HTTP — linesep="\n", max_line=None
- policy.compat32 — legacy (default for old Message class)
- Clone: p = policy.SMTP.clone(raise_on_defect=True)
- Parse: msg = BytesParser(policy=policy.default).parsebytes(raw)
- Gen: msg.as_string(policy=policy.SMTP)
- BytesGenerator(fp, policy=policy.SMTP).flatten(msg)
email.policy Message Policy Pipeline
# app/emailpolicyutil.py — parse, generate, validate, compare, header inspect
from __future__ import annotations
import io
import textwrap
from dataclasses import dataclass, field
from email import policy as _policy
from email.generator import BytesGenerator, Generator
from email.headerregistry import Address
from email.message import EmailMessage, Message
from email.parser import BytesParser, Parser
from typing import Any
# ─────────────────────────────────────────────────────────────────────────────
# 1. Policy-aware parser helpers
# ─────────────────────────────────────────────────────────────────────────────
def parse_bytes(raw: bytes,
pol: Any = _policy.default) -> EmailMessage:
"""
Parse raw RFC 5322 bytes into an EmailMessage using the given policy.
Example:
msg = parse_bytes(b"From: [email protected]\r\nSubject: Hi\r\n\r\nBody")
print(msg["Subject"])
"""
return BytesParser(policy=pol).parsebytes(raw) # type: ignore[return-value]
def parse_str(text: str,
pol: Any = _policy.default) -> EmailMessage:
"""
Parse RFC 5322 text into an EmailMessage.
Example:
raw = "From: [email protected]\nSubject: Hi\n\nBody"
msg = parse_str(raw)
"""
return Parser(policy=pol).parsestr(text) # type: ignore[return-value]
def safe_parse(raw: "bytes | str",
pol: Any = _policy.default) -> "tuple[EmailMessage | None, list[str]]":
"""
Parse with defect collection. Returns (msg, defects).
defects is a list of human-readable strings; empty if mail is clean.
Example:
msg, defects = safe_parse(raw_bytes)
if defects:
print("Parse warnings:", defects)
"""
strict_pol = pol.clone(raise_on_defect=False)
try:
if isinstance(raw, bytes):
msg = BytesParser(policy=strict_pol).parsebytes(raw)
else:
msg = Parser(policy=strict_pol).parsestr(raw)
except Exception as e:
return None, [f"fatal: {e}"]
defects = [str(d) for d in msg.defects]
for part in msg.walk():
defects.extend(str(d) for d in part.defects)
return msg, defects # type: ignore[return-value]
# ─────────────────────────────────────────────────────────────────────────────
# 2. Policy-aware generator helpers
# ─────────────────────────────────────────────────────────────────────────────
def to_smtp_bytes(msg: "Message | EmailMessage") -> bytes:
"""
Serialise a message to SMTP wire-format bytes (CRLF, max_line=998).
Example:
raw = to_smtp_bytes(msg)
smtp.sendmail(from_addr, to_addrs, raw)
"""
buf = io.BytesIO()
BytesGenerator(buf, policy=_policy.SMTP).flatten(msg)
return buf.getvalue()
def to_smtp_utf8_bytes(msg: "Message | EmailMessage") -> bytes:
"""
Serialise with SMTPUTF8 policy (allows UTF-8 headers, no RFC 2047 encoding).
Requires an SMTPUTF8-capable SMTP server.
Example:
raw = to_smtp_utf8_bytes(msg)
"""
buf = io.BytesIO()
BytesGenerator(buf, policy=_policy.SMTPUTF8).flatten(msg)
return buf.getvalue()
def to_display_str(msg: "Message | EmailMessage",
max_line: int = 78) -> str:
"""
Serialise to a human-readable string with configurable line length.
Example:
print(to_display_str(msg))
"""
pol = _policy.default.clone(max_line_length=max_line)
buf = io.StringIO()
Generator(buf, policy=pol).flatten(msg)
return buf.getvalue()
# ─────────────────────────────────────────────────────────────────────────────
# 3. Header inspection (EmailPolicy typed headers)
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class HeaderReport:
name: str
raw_value: str
folded_value: str
addresses: list[str] = field(default_factory=list)
def inspect_headers(msg: EmailMessage,
names: "list[str] | None" = None) -> list[HeaderReport]:
"""
Return a HeaderReport for each requested header (or all headers if names=None).
For address headers, parses out display-name + email pairs.
Example:
msg = parse_bytes(raw, policy.default)
for h in inspect_headers(msg, ["From", "To", "Subject"]):
print(h.name, h.addresses or h.raw_value)
"""
address_headers = {"from", "to", "cc", "bcc", "reply-to", "sender"}
target = names if names else [k for k, _ in msg.items()]
reports: list[HeaderReport] = []
for name in target:
raw = msg.get(name, "")
folded = msg.get(name, "")
addrs: list[str] = []
if name.lower() in address_headers:
try:
header_obj = msg[name]
if hasattr(header_obj, "addresses"):
addrs = [str(a) for a in header_obj.addresses]
except Exception:
pass
reports.append(HeaderReport(name=name, raw_value=raw,
folded_value=str(folded), addresses=addrs))
return reports
# ─────────────────────────────────────────────────────────────────────────────
# 4. Policy comparison utility
# ─────────────────────────────────────────────────────────────────────────────
_POLICIES = {
"default": _policy.default,
"SMTP": _policy.SMTP,
"SMTPUTF8": _policy.SMTPUTF8,
"HTTP": _policy.HTTP,
"compat32": _policy.compat32,
}
def policy_info(name: str = "SMTP") -> dict[str, Any]:
"""
Return a dict of key attributes for a named policy.
Example:
for pol_name in ["default", "SMTP", "SMTPUTF8", "HTTP", "compat32"]:
print(pol_name, policy_info(pol_name))
"""
pol = _POLICIES.get(name)
if pol is None:
return {"error": f"unknown policy {name!r}"}
attrs: dict[str, Any] = {
"name": name,
"max_line_length": getattr(pol, "max_line_length", None),
"linesep": repr(getattr(pol, "linesep", None)),
"utf8": getattr(pol, "utf8", None),
"raise_on_defect": getattr(pol, "raise_on_defect", None),
"cte_type": getattr(pol, "cte_type", None),
"class": type(pol).__name__,
}
return attrs
# ─────────────────────────────────────────────────────────────────────────────
# 5. Build an EmailMessage with modern policy
# ─────────────────────────────────────────────────────────────────────────────
def build_email_message(
subject: str,
body: str,
from_addr: str,
to_addrs: "list[str]",
*,
html_body: str | None = None,
pol: Any = _policy.default,
) -> EmailMessage:
"""
Build an EmailMessage using the modern email API (Python 3.6+).
Uses EmailMessage.set_content() and make_alternative() for clean MIME structure.
Example:
msg = build_email_message(
"Hello",
"Plain text body.",
"[email protected]",
["[email protected]"],
html_body="<b>Hello!</b>",
)
raw = to_smtp_bytes(msg)
"""
msg = EmailMessage(policy=pol)
msg["Subject"] = subject
msg["From"] = from_addr
msg["To"] = ", ".join(to_addrs)
msg.set_content(body)
if html_body:
msg.make_alternative()
msg.get_payload()[0].set_content( # type: ignore[union-attr]
html_body, subtype="html")
return msg
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== email.policy demo ===")
# ── policy comparison table ───────────────────────────────────────────
print("\n--- policy_info comparison ---")
for name in ["default", "SMTP", "SMTPUTF8", "HTTP", "compat32"]:
info = policy_info(name)
print(f" {info['name']:12s} max_line={str(info['max_line_length']):5s} "
f"linesep={info['linesep']:8s} utf8={info['utf8']} "
f"class={info['class']}")
# ── parse and detect defects ──────────────────────────────────────────
print("\n--- safe_parse with defects ---")
good_raw = b"From: [email protected]\r\nTo: [email protected]\r\nSubject: Test\r\n\r\nHello"
bad_raw = b"From: alice\r\nTo:\r\n\r\nBody" # missing domain, empty To
msg_good, defects_good = safe_parse(good_raw)
msg_bad, defects_bad = safe_parse(bad_raw)
print(f" good defects: {defects_good}")
print(f" bad defects: {defects_bad}")
# ── generate with different policies ─────────────────────────────────
print("\n--- generate with SMTP vs default policy ---")
if msg_good:
smtp_bytes = to_smtp_bytes(msg_good)
display_str = to_display_str(msg_good)
print(f" SMTP bytes linesep: {smtp_bytes[:80]!r}")
print(f" display str lines : {len(display_str.splitlines())}")
# ── inspect headers ───────────────────────────────────────────────────
print("\n--- inspect_headers ---")
sample = b"From: Alice Smith <[email protected]>\r\nTo: Bob <[email protected]>, [email protected]\r\nSubject: Policy demo\r\n\r\n"
parsed, _ = safe_parse(sample)
if parsed:
for h in inspect_headers(parsed, ["From", "To", "Subject"]):
print(f" {h.name:10s}: {h.raw_value!r}")
if h.addresses:
print(f" {'':10s} addresses: {h.addresses}")
# ── clone and customise ───────────────────────────────────────────────
print("\n--- clone policy ---")
strict_smtp = _policy.SMTP.clone(
raise_on_defect=True,
max_line_length=200,
)
print(f" cloned max_line_length: {strict_smtp.max_line_length}")
print(f" cloned raise_on_defect: {strict_smtp.raise_on_defect}")
print(f" cloned linesep : {strict_smtp.linesep!r}")
print("\n=== done ===")
For the email.message.EmailMessage stdlib companion — EmailMessage (Python 3.6+) is the modern message class that pairs naturally with email.policy.default; its set_content(), add_attachment(), make_alternative(), and make_related() methods build correct MIME structure automatically, replacing the older MIMEMultipart/MIMEText assembly — use EmailMessage + policy.SMTP for all new code; reserve email.mime.* classes for compatibility with code using legacy email.message.Message. For the flanker (PyPI) alternative — flanker.mime.create.text() / flanker.mime.create.multipart() provide high-level MIME construction with built-in address parsing and RFC compliance validation — use flanker in production email platforms (ESPs, inbox providers) that process high volumes of potentially malformed mail; use stdlib email.policy for general-purpose email tools that need zero dependencies. The Claude Skills 360 bundle includes email.policy skill sets covering parse_bytes()/parse_str()/safe_parse() policy-aware parsers, to_smtp_bytes()/to_smtp_utf8_bytes()/to_display_str() generators, HeaderReport/inspect_headers() typed header inspector, policy_info() attribute comparator, and build_email_message() modern EmailMessage builder. Start with the free tier to try email policy patterns and RFC 5322 pipeline code generation.