Claude Code for email.generator: Python Email Message Serializers — Claude Skills 360 Blog
Blog / AI / Claude Code for email.generator: Python Email Message Serializers
AI

Claude Code for email.generator: Python Email Message Serializers

Published: February 2, 2029
Read time: 5 min read
By: Claude Skills 360

Python’s email.generator module serialises email.message.Message and EmailMessage objects to text or bytes. from email.generator import BytesGenerator, Generator, DecodedGenerator. Three classes: Generator(outfp, mangle_from_=False, maxheaderlen=78, *, policy=None) — writes text (str) to outfp; mangle_from_=True prepends > to lines starting with From (mbox safety); maxheaderlen controls folding. BytesGenerator(outfp, mangle_from_=False, maxheaderlen=78, *, policy=None) — writes bytes to a binary stream; essential for SMTP sendmail() calls. DecodedGenerator(outfp, mangle_from_=False, maxheaderlen=78, fmt=None, *, policy=None) — writes decoded (human-readable) payloads, stripping transfer encoding; fmt is a format string "%(type)s %(maintype)s %(subtype)s %(filename)s %(desc)s %(encoding)s" for non-text parts. Flatten a message: gen.flatten(msg). Or use msg.as_string(unixfrom=False, maxheaderlen=78, policy=None) / msg.as_bytes() which delegate to Generator/BytesGenerator internally. Policy pairing: BytesGenerator(buf, policy=email.policy.SMTP) produces CRLF-terminated output; Generator(buf, policy=email.policy.default) uses \n. Claude Code generates SMTP wire-format serializers, mbox file writers, policy-aware message renderers, multipart flatteners, and email archive pipelines.

CLAUDE.md for email.generator

## email.generator Stack
- Stdlib: from email.generator import Generator, BytesGenerator, DecodedGenerator
-         from email import policy
- Text:   buf = io.StringIO()
-         Generator(buf, mangle_from_=True).flatten(msg)
-         text = buf.getvalue()
- Bytes:  buf = io.BytesIO()
-         BytesGenerator(buf, policy=policy.SMTP).flatten(msg)
-         raw = buf.getvalue()             # CRLF wire-format bytes
- Decoded: buf = io.StringIO()
-          DecodedGenerator(buf).flatten(msg)   # no base64/QP encoding
- Short:  msg.as_string()    # → str via Generator
-         msg.as_bytes()     # → bytes via BytesGenerator
- Policy: BytesGenerator(buf, policy=policy.SMTPUTF8) for UTF-8 SMTP

email.generator Serialization Pipeline

# app/emailgeneratorutil.py — text, bytes, decoded, mbox, smtp, diff
from __future__ import annotations

import io
import mailbox
import os
from email import policy as _policy
from email.generator import BytesGenerator, DecodedGenerator, Generator
from email.message import EmailMessage, Message
from email.parser import BytesParser
from typing import Any


# ─────────────────────────────────────────────────────────────────────────────
# 1. Core serialisation helpers
# ─────────────────────────────────────────────────────────────────────────────

def to_string(msg: "Message | EmailMessage",
              *,
              mangle_from_: bool = False,
              maxheaderlen: int = 78,
              pol: Any = None) -> str:
    """
    Serialise a message to a str using Generator.

    Example:
        text = to_string(msg)
        print(text[:200])
    """
    buf = io.StringIO()
    gen = Generator(buf, mangle_from_=mangle_from_,
                    maxheaderlen=maxheaderlen, policy=pol)
    gen.flatten(msg)
    return buf.getvalue()


def to_bytes(msg: "Message | EmailMessage",
             *,
             mangle_from_: bool = False,
             maxheaderlen: int = 78,
             pol: Any = _policy.SMTP) -> bytes:
    """
    Serialise a message to bytes using BytesGenerator with SMTP policy by default.
    Produces CRLF line endings suitable for SMTP sendmail().

    Example:
        raw = to_bytes(msg)
        smtp.sendmail(from_addr, to_addrs, raw)
    """
    buf = io.BytesIO()
    gen = BytesGenerator(buf, mangle_from_=mangle_from_,
                         maxheaderlen=maxheaderlen, policy=pol)
    gen.flatten(msg)
    return buf.getvalue()


def to_decoded_text(msg: "Message | EmailMessage",
                    fmt: str | None = None) -> str:
    """
    Serialise a message to text with transfer-encoded payloads decoded.
    Non-text parts are replaced by a summary line controlled by fmt.

    Example:
        text = to_decoded_text(msg)
        print(text)   # base64/QP payloads appear as plain text
    """
    buf = io.StringIO()
    gen = DecodedGenerator(buf, fmt=fmt)
    gen.flatten(msg)
    return buf.getvalue()


# ─────────────────────────────────────────────────────────────────────────────
# 2. File output helpers
# ─────────────────────────────────────────────────────────────────────────────

def save_to_eml(msg: "Message | EmailMessage", path: str) -> None:
    """
    Save a message to an .eml file (bytes, SMTP policy).

    Example:
        save_to_eml(msg, "/tmp/outgoing.eml")
    """
    with open(path, "wb") as fp:
        BytesGenerator(fp, policy=_policy.SMTP).flatten(msg)


def load_from_eml(path: str,
                  pol: Any = _policy.default) -> EmailMessage:
    """
    Load a message from an .eml file.

    Example:
        msg = load_from_eml("/tmp/outgoing.eml")
        print(msg["Subject"])
    """
    with open(path, "rb") as fp:
        return BytesParser(policy=pol).parse(fp)   # type: ignore[return-value]


def append_to_mbox(msg: "Message | EmailMessage", mbox_path: str) -> None:
    """
    Append a message to an mbox file using mangle_from_=True for mbox safety.

    Example:
        append_to_mbox(msg, "/var/mail/archive.mbox")
    """
    with open(mbox_path, "ab") as fp:
        BytesGenerator(fp, mangle_from_=True,
                       policy=_policy.compat32).flatten(msg)
        fp.write(b"\n")


# ─────────────────────────────────────────────────────────────────────────────
# 3. Policy-aware rendering
# ─────────────────────────────────────────────────────────────────────────────

def render_for_smtp(msg: "Message | EmailMessage") -> bytes:
    """
    Render to bytes with policy.SMTP (CRLF, max_line=998).

    Example:
        raw = render_for_smtp(msg)
        with smtplib.SMTP("localhost", 25) as smtp:
            smtp.sendmail(from_addr, to_addrs, raw)
    """
    return to_bytes(msg, pol=_policy.SMTP)


def render_for_smtputf8(msg: "Message | EmailMessage") -> bytes:
    """
    Render to bytes with policy.SMTPUTF8 (UTF-8 headers, CRLF).
    Requires an SMTPUTF8-capable server.

    Example:
        raw = render_for_smtputf8(msg)
    """
    return to_bytes(msg, pol=_policy.SMTPUTF8)


def render_for_display(msg: "Message | EmailMessage",
                        max_line: int = 78) -> str:
    """
    Render to human-readable str with configurable line folding.

    Example:
        print(render_for_display(msg))
    """
    pol = _policy.default.clone(max_line_length=max_line)
    return to_string(msg, pol=pol)


# ─────────────────────────────────────────────────────────────────────────────
# 4. Multipart inspection via generation
# ─────────────────────────────────────────────────────────────────────────────

def part_sizes(msg: "Message | EmailMessage") -> list[dict[str, Any]]:
    """
    Return the serialised byte size of each MIME part in the message tree.

    Example:
        for info in part_sizes(msg):
            print(info["content_type"], info["size_bytes"])
    """
    result: list[dict[str, Any]] = []
    for part in msg.walk():
        buf = io.BytesIO()
        BytesGenerator(buf, policy=_policy.SMTP).flatten(part)
        result.append({
            "content_type": part.get_content_type(),
            "size_bytes":   len(buf.getvalue()),
            "encoding":     part.get("Content-Transfer-Encoding", "none"),
            "filename":     part.get_filename(""),
        })
    return result


# ─────────────────────────────────────────────────────────────────────────────
# 5. Round-trip verifier
# ─────────────────────────────────────────────────────────────────────────────

def round_trip_check(raw: bytes,
                     pol: Any = _policy.default) -> dict[str, Any]:
    """
    Parse raw bytes, re-serialise, re-parse, and compare Subject/From/To.
    Returns a dict with 'match' (bool) and any 'diffs' found.

    Example:
        report = round_trip_check(raw_email_bytes)
        print(report["match"], report["diffs"])
    """
    msg1: EmailMessage = BytesParser(policy=pol).parsebytes(raw)  # type: ignore
    re_serialised = to_bytes(msg1, pol=pol)
    msg2: EmailMessage = BytesParser(policy=pol).parsebytes(re_serialised)  # type: ignore

    diffs: list[str] = []
    for header in ("Subject", "From", "To", "Message-ID"):
        v1 = msg1.get(header, "")
        v2 = msg2.get(header, "")
        if str(v1) != str(v2):
            diffs.append(f"{header}: {v1!r}{v2!r}")

    return {
        "match":           len(diffs) == 0,
        "diffs":           diffs,
        "original_bytes":  len(raw),
        "serialised_bytes": len(re_serialised),
    }


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText

    print("=== email.generator demo ===")

    # Build a sample message
    outer = MIMEMultipart("alternative")
    outer["Subject"] = "Generator demo"
    outer["From"] = "[email protected]"
    outer["To"] = "[email protected]"
    outer.attach(MIMEText("Hello plain!", "plain", "utf-8"))
    outer.attach(MIMEText("<b>Hello HTML!</b>", "html", "utf-8"))

    # ── to_string ─────────────────────────────────────────────────────────
    print("\n--- to_string ---")
    text = to_string(outer)
    print(f"  length: {len(text)} chars")
    print(f"  first linesep: {repr(text[:text.index(chr(10))+1])}")

    # ── to_bytes (SMTP policy) ─────────────────────────────────────────────
    print("\n--- render_for_smtp ---")
    raw = render_for_smtp(outer)
    print(f"  length: {len(raw)} bytes")
    first_crlf = raw.find(b"\r\n")
    print(f"  CRLF present: {first_crlf >= 0} at offset {first_crlf}")

    # ── to_decoded_text ────────────────────────────────────────────────────
    print("\n--- to_decoded_text ---")
    decoded = to_decoded_text(outer)
    print(f"  length: {len(decoded)} chars")
    for line in decoded.splitlines()[:5]:
        print(f"  {line!r}")

    # ── part_sizes ────────────────────────────────────────────────────────
    print("\n--- part_sizes ---")
    for info in part_sizes(outer):
        print(f"  {info['content_type']:30s}  {info['size_bytes']:5d} bytes"
              f"  enc={info['encoding']}")

    # ── round_trip_check  ─────────────────────────────────────────────────
    print("\n--- round_trip_check ---")
    result = round_trip_check(raw)
    print(f"  match: {result['match']}")
    print(f"  orig={result['original_bytes']} → re-serialised={result['serialised_bytes']} bytes")
    if result["diffs"]:
        for d in result["diffs"]:
            print(f"  diff: {d}")

    # ── maxheaderlen comparison ───────────────────────────────────────────
    print("\n--- maxheaderlen 78 vs 200 ---")
    long_msg = MIMEText("body")
    long_msg["Subject"] = "This is a very long subject line that should get folded at 78 characters by default"
    long_msg["From"] = "[email protected]"
    long_msg["To"] = "[email protected]"
    text78  = to_string(long_msg, maxheaderlen=78)
    text200 = to_string(long_msg, maxheaderlen=200)
    subj_lines_78  = sum(1 for ln in text78.splitlines()  if ln.startswith(("Subject", " ", "\t")))
    subj_lines_200 = sum(1 for ln in text200.splitlines() if ln.startswith(("Subject", " ", "\t")))
    print(f"  maxheaderlen=78 : subject spans {subj_lines_78} line(s)")
    print(f"  maxheaderlen=200: subject spans {subj_lines_200} line(s)")

    print("\n=== done ===")

For the msg.as_string() / msg.as_bytes() shorthand — these methods on email.message.Message call Generator/BytesGenerator internally and are convenient for simple cases, but they don’t expose the policy parameter before Python 3.6; passing policy=email.policy.SMTP directly to BytesGenerator gives explicit control over CRLF and line length when targeting wire format output. For the aiofiles (PyPI) alternative — async with aiofiles.open("msg.eml", "wb") as f: await f.write(raw) handles the I/O layer when email.generator serialisation happens in an asyncio context — use aiofiles to pair async I/O with the synchronous BytesGenerator by serialising to a BytesIO buffer first, then writing the buffer with aiofiles.open. The Claude Skills 360 bundle includes email.generator skill sets covering to_string()/to_bytes()/to_decoded_text() core serialisers, save_to_eml()/load_from_eml()/append_to_mbox() file helpers, render_for_smtp()/render_for_smtputf8()/render_for_display() policy renderers, part_sizes() multipart inspector, and round_trip_check() parse-serialise-reparse verifier. Start with the free tier to try message serialisation patterns and email.generator pipeline code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free