Claude Code for email.contentmanager: Python Email Content Accessors — Claude Skills 360 Blog
Blog / AI / Claude Code for email.contentmanager: Python Email Content Accessors
AI

Claude Code for email.contentmanager: Python Email Content Accessors

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

Python’s email.contentmanager module provides the ContentManager machinery that powers EmailMessage.get_content() and set_content() in the modern email API (Python 3.6+). from email import contentmanager. Two built-in content managers: contentmanager.content_manager — the standard one used by email.policy.default; handles text/*, multipart/*, message/rfc822, and binary parts (returns decoded str for text, bytes for others). contentmanager.raw_data_manager — returns raw decoded payload without content-type awareness. Use via EmailMessage: msg.get_content() → decoded str/bytes; msg.set_content(text_or_bytes, subtype, ...) → sets MIME type, charset, and transfer encoding in one call; msg.make_alternative() → convert to multipart/alternative; msg.make_mixed()multipart/mixed; msg.make_related()multipart/related; msg.add_attachment(data, maintype, subtype, ...) → attach data with auto-encoding. Custom ContentManager: subclass and call cm.add_get_handler(content_type, fn) / cm.add_set_handler(type_, fn) to register custom reader/writer functions. Claude Code generates high-level email readers, attachment extractors, text body accessors, HTML readers, and policy-aware MIME construction pipelines.

CLAUDE.md for email.contentmanager

## email.contentmanager Stack
- Stdlib: from email import contentmanager, policy
-         from email.message import EmailMessage
- Use via EmailMessage (policy.default):
-   msg = EmailMessage()
-   msg.set_content("Plain text")                 # text/plain
-   msg.set_content("<b>Hi</b>", subtype="html")  # text/html
-   text = msg.get_content()                      # → str (decodes charset)
-   msg.make_alternative()                        # → multipart/alternative
-   msg.add_attachment(img_bytes, maintype="image", subtype="png",
-                      filename="photo.png")
- Raw:  raw_data_manager.get_content(part)        # raw payload
- Custom:
-   cm = contentmanager.ContentManager()
-   cm.add_get_handler("text/x-mytype", my_reader)
-   cm.add_set_handler(MyClass, my_writer)

email.contentmanager High-Level Email Pipeline

# app/emailcontentmanagerutil.py — get, set, attach, extract, custom
from __future__ import annotations

import io
import mimetypes
import os
from dataclasses import dataclass, field
from email import contentmanager, policy as _policy
from email.message import EmailMessage, Message
from email.parser import BytesParser
from typing import Any


# ─────────────────────────────────────────────────────────────────────────────
# 1. EmailMessage builders using set_content / add_attachment
# ─────────────────────────────────────────────────────────────────────────────

def build_plain(subject: str, body: str,
                from_addr: str, to_addrs: "list[str]") -> EmailMessage:
    """
    Build a plain-text EmailMessage using the modern set_content() API.

    Example:
        msg = build_plain("Hello", "Body text.", "[email protected]", ["[email protected]"])
        print(msg.as_string())
    """
    from email.utils import formatdate, make_msgid
    msg = EmailMessage(policy=_policy.default)
    msg["Subject"] = subject
    msg["From"] = from_addr
    msg["To"] = ", ".join(to_addrs)
    msg["Date"] = formatdate(localtime=True)
    msg["Message-ID"] = make_msgid()
    msg.set_content(body)
    return msg


def build_html_alternative(subject: str,
                             plain: str, html: str,
                             from_addr: str,
                             to_addrs: "list[str]") -> EmailMessage:
    """
    Build a multipart/alternative message with plain + HTML bodies.
    Uses modern make_alternative() API.

    Example:
        msg = build_html_alternative(
            "Newsletter",
            "Read online.",
            "<h1>Hello!</h1>",
            "[email protected]", ["[email protected]"],
        )
    """
    from email.utils import formatdate, make_msgid
    msg = EmailMessage(policy=_policy.default)
    msg["Subject"] = subject
    msg["From"] = from_addr
    msg["To"] = ", ".join(to_addrs)
    msg["Date"] = formatdate(localtime=True)
    msg["Message-ID"] = make_msgid()
    msg.set_content(plain)
    msg.make_alternative()
    msg.get_payload()[1].set_content(html, subtype="html")  # type: ignore[index]
    return msg


def build_with_files(subject: str, body: str,
                      from_addr: str, to_addrs: "list[str]",
                      file_paths: "list[str]") -> EmailMessage:
    """
    Build a multipart/mixed message with file attachments.
    Uses add_attachment() to attach each file with auto-detected MIME type.

    Example:
        msg = build_with_files(
            "Report",
            "See attached.",
            "[email protected]", ["[email protected]"],
            ["/tmp/report.pdf", "/tmp/data.csv"],
        )
    """
    from email.utils import formatdate, make_msgid
    msg = EmailMessage(policy=_policy.default)
    msg["Subject"] = subject
    msg["From"] = from_addr
    msg["To"] = ", ".join(to_addrs)
    msg["Date"] = formatdate(localtime=True)
    msg["Message-ID"] = make_msgid()
    msg.set_content(body)

    for path in file_paths:
        filename = os.path.basename(path)
        ctype, _ = mimetypes.guess_type(path)
        maintype, subtype = (ctype or "application/octet-stream").split("/", 1)
        with open(path, "rb") as f:
            data = f.read()
        msg.add_attachment(data, maintype=maintype, subtype=subtype,
                            filename=filename)
    return msg


# ─────────────────────────────────────────────────────────────────────────────
# 2. Content extractors
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class BodyParts:
    plain:  str = ""
    html:   str = ""


def extract_body(msg: "EmailMessage | Message") -> BodyParts:
    """
    Extract plain-text and HTML body parts from an (optionally multipart) message.

    Example:
        with open("msg.eml", "rb") as f:
            msg = BytesParser(policy=_policy.default).parse(f)
        body = extract_body(msg)
        print(body.plain[:200])
    """
    parts = BodyParts()
    if not msg.is_multipart():
        ct = msg.get_content_type()
        try:
            content = msg.get_content()  # type: ignore[attr-defined]
        except Exception:
            content = ""
        if ct == "text/plain":
            parts.plain = content
        elif ct == "text/html":
            parts.html = content
        return parts

    for part in msg.walk():
        ct = part.get_content_type()
        if ct not in ("text/plain", "text/html"):
            continue
        try:
            content = part.get_content()  # type: ignore[attr-defined]
        except Exception:
            content = ""
        if ct == "text/plain" and not parts.plain:
            parts.plain = content
        elif ct == "text/html" and not parts.html:
            parts.html = content
    return parts


@dataclass
class Attachment:
    filename:     str
    content_type: str
    data:         bytes
    maintype:     str
    subtype:      str


def extract_attachments(msg: "EmailMessage | Message") -> list[Attachment]:
    """
    Extract all non-body MIME parts as Attachment objects.
    Skips text/plain and text/html parts without a filename.

    Example:
        attachments = extract_attachments(msg)
        for att in attachments:
            print(att.filename, len(att.data))
            with open(att.filename, "wb") as f:
                f.write(att.data)
    """
    result: list[Attachment] = []
    for part in msg.walk():
        ct = part.get_content_type()
        maintype, subtype = ct.split("/", 1)
        filename = part.get_filename("")
        # Skip body parts that are not explicit attachments
        disposition = part.get_content_disposition()
        if not filename and disposition not in ("attachment", "inline"):
            if ct in ("text/plain", "text/html", "multipart/mixed",
                      "multipart/alternative", "multipart/related"):
                continue
        try:
            data = part.get_payload(decode=True) or b""
        except Exception:
            data = b""
        if not isinstance(data, bytes):
            continue
        result.append(Attachment(
            filename=filename or f"unnamed.{subtype}",
            content_type=ct,
            data=data,
            maintype=maintype,
            subtype=subtype,
        ))
    return result


# ─────────────────────────────────────────────────────────────────────────────
# 3. Custom ContentManager extension
# ─────────────────────────────────────────────────────────────────────────────

def make_custom_content_manager() -> contentmanager.ContentManager:
    """
    Return a ContentManager that extends the standard one with a
    custom text/csv get handler returning a list of row dicts.

    Example:
        cm = make_custom_content_manager()
        # attach as policy: pol = policy.default.clone(content_manager=cm)
    """
    import csv

    cm = contentmanager.ContentManager()

    # Standard get handlers
    cm.add_get_handler(
        "text/plain",
        lambda msg, *a, **kw: contentmanager.content_manager.get_content(msg, *a, **kw),
    )
    cm.add_get_handler(
        "text/html",
        lambda msg, *a, **kw: contentmanager.content_manager.get_content(msg, *a, **kw),
    )

    # Custom CSV handler
    def get_csv(msg: Any, *args: Any, **kwargs: Any) -> list[dict]:
        raw = contentmanager.raw_data_manager.get_content(msg)
        if isinstance(raw, bytes):
            raw = raw.decode("utf-8", errors="replace")
        reader = csv.DictReader(raw.splitlines())
        return list(reader)

    cm.add_get_handler("text/csv", get_csv)
    return cm


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

if __name__ == "__main__":
    print("=== email.contentmanager demo ===")

    # ── build_plain ────────────────────────────────────────────────────────
    print("\n--- build_plain ---")
    msg_plain = build_plain("Hello!", "Plain text body.", "[email protected]", ["[email protected]"])
    print(f"  content-type: {msg_plain.get_content_type()!r}")
    print(f"  get_content : {msg_plain.get_content()!r}")

    # ── build_html_alternative ────────────────────────────────────────────
    print("\n--- build_html_alternative ---")
    msg_alt = build_html_alternative(
        "Newsletter",
        "Read online at https://example.com",
        "<h1>Welcome!</h1><p>Read <a href='https://example.com'>online</a>.</p>",
        "[email protected]", ["[email protected]"],
    )
    print(f"  content-type: {msg_alt.get_content_type()!r}")
    body = extract_body(msg_alt)
    print(f"  plain: {body.plain!r}")
    print(f"  html : {body.html[:50]!r}")

    # ── add_attachment programmatically ───────────────────────────────────
    print("\n--- add_attachment ---")
    msg_attach = EmailMessage(policy=_policy.default)
    msg_attach["Subject"] = "With attachment"
    msg_attach["From"] = "[email protected]"
    msg_attach["To"] = "[email protected]"
    msg_attach.set_content("See attached blob.")
    msg_attach.add_attachment(b"\x89PNG\r\n\x1a\n" + b"\x00" * 100,
                               maintype="image", subtype="png",
                               filename="screenshot.png")
    atts = extract_attachments(msg_attach)
    print(f"  attachments: {[(a.filename, a.content_type, len(a.data)) for a in atts]}")

    # ── raw_data_manager ──────────────────────────────────────────────────
    print("\n--- raw_data_manager ---")
    raw = contentmanager.raw_data_manager.get_content(msg_plain)
    print(f"  raw type: {type(raw).__name__}  len={len(raw)}")

    # ── content_manager constants ─────────────────────────────────────────
    print("\n--- contentmanager module attributes ---")
    attrs = [a for a in dir(contentmanager) if not a.startswith("_")]
    print(f"  public attrs: {attrs}")

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

For the email.mime.* companion — email.mime.text.MIMEText, MIMEMultipart, and MIMEApplication are the older, lower-level API for building MIME messages; EmailMessage.set_content() and add_attachment() internally use the same MIME machinery but expose a cleaner, policy-aware interface — use EmailMessage + set_content() for all new code (Python 3.6+); use email.mime.* only when maintaining legacy code or needing explicit transfer-encoding control not exposed by set_content(). For the python-email-validator + aiofiles (PyPI) combination — for async attachment handling, serialise the EmailMessage to bytes with BytesGenerator, then write with aiofiles.open("msg.eml", "wb") — there is no async MIME builder in the stdlib; compose synchronously, then ship bytes to async I/O. The Claude Skills 360 bundle includes email.contentmanager skill sets covering build_plain()/build_html_alternative()/build_with_files() message builders, BodyParts/extract_body() body extractor, Attachment/extract_attachments() attachment extractor, make_custom_content_manager() registry extension, and raw_data_manager raw payload access. Start with the free tier to try high-level email content patterns and email.contentmanager pipeline code generation.

Keep Reading

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
AI

Claude Code for xml.sax.handler: Python SAX2 Handler Base Classes

Implement standards-compliant SAX2 event handlers for streaming XML with Python's xml.sax.handler module and Claude Code — xml sax handler ContentHandler for the base class providing startDocument endDocument startElement endElement characters startPrefixMapping endPrefixMapping ignorableWhitespace processingInstruction skippedEntity and setDocumentLocator no-op defaults ready to override, xml sax handler ErrorHandler for the base class with warning error and fatalError methods for receiving recoverable warnings non-fatal errors and fatal parsing errors from the SAX parser, xml sax handler EntityResolver for the base class with a resolveEntity method that produces an InputSource for external entity URIs enabling custom entity lookup or blocking, xml sax handler DTDHandler for the base class with notationDecl and unparsedEntityDecl methods for receiving DTD-level notation and entity declarations, xml sax handler feature_namespaces for the boolean feature URI that enables XML namespace processing in the SAX parser, xml sax handler feature_validation for the feature URI that enables DTD validation if supported by the backend parser, xml sax handler property_lexical_handler for the property URI used to register a LexicalHandler for comments CDATA sections and entity start and end events, xml sax handler all_features and all_properties for the lists of standard SAX2 feature and property URIs, and xml sax handler integration with xml.sax and xml.sax.saxutils and xml.parsers.expat and io for building content accumulator handlers streaming element collectors schema-like structure validators event loggers and multi-pass XML pipeline stages.

5 min read Feb 9, 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