Skip to main content

Lib/email/message.py

cpython 3.14 @ ab2d84fe1023/Lib/email/message.py

Lib/email/message.py defines Message, the foundational email object, and EmailMessage, the higher-level subclass introduced in Python 3.6 that hooks into the Content-Manager protocol. Every other part of the email package builds on these two classes.

Map

LinesSymbolRole
1-800MessageCore email message object with header list, payload, and MIME tree API
801-950MIMEPartMixin that adds typed header access and content manager dispatch
951-1100EmailMessageModern subclass combining Message with MIMEPart

Reading

Header dict with case-insensitive keys

Message stores headers as a list of (name, value) pairs in self._headers. Lookup is case-insensitive because RFC 5322 says header names are case-insensitive. The __getitem__ and get methods both normalize the name with .lower() before comparing.

# CPython: Lib/email/message.py:132 get
def get(self, name, failobj=None):
name = name.lower()
for k, v in self._headers:
if k.lower() == name:
return self.policy.header_fetch_parse(k, v)
return failobj

The stored values are raw strings. header_fetch_parse (from the active Policy) is what turns them into structured objects when the new-style API is in use. Under Compat32 it is a near-identity transform.

get_payload and the decode flag

get_payload(decode=False) has two very different code paths depending on the decode argument and whether the message is multipart.

# CPython: Lib/email/message.py:254 get_payload
def get_payload(self, i=None, decode=False):
if self.is_multipart():
if decode:
return None
if i is None:
return self._payload
return self._payload[i]
if not decode:
return self._payload
cte = str(self.get('content-transfer-encoding', '')).lower()
if cte == 'quoted-printable':
return quopri.decodestring(self._payload.encode())
elif cte == 'base64':
...

When decode=True the method strips the Content-Transfer-Encoding layer and returns bytes. This is the correct entry point for reading binary attachments.

walk() and MIME tree traversal

walk() is a depth-first generator over the MIME part tree.

# CPython: Lib/email/message.py:438 walk
def walk(self):
yield self
if self.is_multipart():
for subpart in self.get_payload():
yield from subpart.walk()

A common idiom is iterating msg.walk() and filtering on part.get_content_type() to find attachments or the plain-text body.

gopy notes

Not yet ported. The planned package path is module/email/message. The Go type will need to represent the header list as []headerPair and implement case-insensitive lookup without converting to a map, preserving duplicate header ordering as CPython does.

CPython 3.14 changes

EmailMessage gained tighter integration with email.contentmanager. The get_body, iter_attachments, and iter_parts methods now delegate to raw_data_manager by default rather than falling back to the legacy Compat32 path.