Skip to main content

Lib/email/mime/text.py

cpython 3.14 @ ab2d84fe1023/Lib/email/mime/text.py

email.mime.text provides MIMEText, a convenience constructor for text/* messages. It is the most commonly used class in the email.mime subpackage: building a plain-text or HTML email body is almost always a single MIMEText(body) call.

The email.mime subpackage is a thin layer of convenience constructors sitting on top of the core email.message.Message class. Its inheritance tree is:

MIMEBase (email.mime.base) -- sets MIME-Version and Content-Type
MIMENonMultipart (email.mime.nonmultipart) -- raises on attach()
MIMEText (email.mime.text)
MIMEApplication (email.mime.application)
MIMEAudio (email.mime.audio)
MIMEImage (email.mime.image)
MIMEMultipart (email.mime.multipart) -- manages boundary and parts list

MIMEBase.__init__ calls Message.__init__ and then calls add_header('Content-Type', maintype + '/' + subtype, **params) to set the primary MIME header. MIMEText delegates to MIMENonMultipart, which in turn delegates to MIMEBase, so the whole chain bottoms out in a plain Message object with the appropriate headers pre-installed.

Map

LinesSymbolRolegopy
1-11Module header, importsImports MIMENonMultipart; defines __all__ = ['MIMEText'].(not ported)
12-40MIMEText.__init__Auto-detects charset (us-ascii vs utf-8) when _charset is None; calls MIMENonMultipart.__init__ with 'text', _subtype, charset; calls self.set_payload(_text, _charset).(not ported)
Lib/email/mime/base.py lines 1-40MIMEBase.__init__Root constructor; calls Message.__init__ and adds Content-Type: maintype/subtype plus any keyword params as header parameters.(not ported)
Lib/email/mime/multipart.py lines 1-47MIMEMultipart.__init__Sets Content-Type: multipart/_subtype; initializes _payload = []; optionally accepts initial sub-parts and a boundary string.(not ported)
Lib/email/mime/nonmultipart.pyMIMENonMultipart.attachOverrides Message.attach to raise errors.MultipartConversionError; non-multipart messages may not have sub-parts attached.(not ported)

Reading

MIMEText.__init__ charset auto-detection (lines 12 to 40)

cpython 3.14 @ ab2d84fe1023/Lib/email/mime/text.py#L12-40

class MIMEText(MIMENonMultipart):
def __init__(self, _text, _subtype='plain', _charset=None, *, policy=None):
# Auto-detect charset when not supplied.
if _charset is None:
try:
_text.encode('us-ascii')
_charset = 'us-ascii'
except UnicodeEncodeError:
_charset = 'utf-8'

MIMENonMultipart.__init__(self, 'text', _subtype, policy=policy,
charset=str(_charset))
self.set_payload(_text, _charset)

When _charset is None, MIMEText probes the body text by attempting to encode it as ASCII. If that succeeds, the charset is set to 'us-ascii', which keeps the Content-Transfer-Encoding as 7bit and avoids any payload transformation. If it fails (UnicodeEncodeError), the charset falls back to 'utf-8', which causes set_payload to encode the payload as quoted-printable or base64 depending on the policy and the proportion of non-ASCII bytes.

The charset=str(_charset) argument passed to MIMENonMultipart.__init__ becomes a parameter on the Content-Type header (e.g., Content-Type: text/plain; charset="utf-8"). str(_charset) handles the case where _charset is a Charset instance rather than a plain string.

set_payload(_text, _charset) on the parent Message object both stores the payload and sets Content-Transfer-Encoding to match the charset's encoding scheme. For us-ascii and utf-8 with the default compat32 policy the encoding is 7bit or quoted-printable respectively. Under the modern EmailPolicy (RFC 6532) the payload is stored as-is and the transfer encoding is omitted, since SMTP submissions are expected to be 8-bit clean.

MIMEMultipart boundary management (multipart.py lines 1 to 47)

cpython 3.14 @ ab2d84fe1023/Lib/email/mime/multipart.py#L1-47

class MIMEMultipart(MIMEBase):
def __init__(self, _subtype='mixed', boundary=None, _subparts=None,
*, policy=None, **_params):
MIMEBase.__init__(self, 'multipart', _subtype, policy=policy,
**_params)

# Initialize _payload as a list; Message.is_multipart() checks this.
self._payload = []

if _subparts:
for p in _subparts:
self.attach(p)
if boundary:
self.set_boundary(boundary)

MIMEMultipart is the root of all multi-part messages. It sets Content-Type: multipart/_subtype (defaulting to multipart/mixed), then initializes _payload as an empty list. Message.is_multipart() simply checks isinstance(self._payload, list), so this initialization is the mechanism that distinguishes multipart from single-part messages throughout the library.

The boundary parameter, when supplied, is installed immediately via set_boundary. When not supplied, the boundary is generated lazily by Generator._handle_multipart at serialization time. The generator computes a boundary that does not appear in any sub-part body, trying progressively longer hash prefixes until a unique one is found.

Sub-parts are added via attach(part) which appends to self._payload. The Generator iterates get_payload() (which returns the list) and renders each sub-part into its own buffer, then joins the buffers with --boundary delimiters and a closing --boundary-- line.

Building a complete email message

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

msg = MIMEMultipart('alternative')
msg['Subject'] = 'Hello'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'

plain = MIMEText('Hello, world.', 'plain')
html = MIMEText('<p>Hello, <b>world</b>.</p>', 'html')

msg.attach(plain)
msg.attach(html)

print(msg.as_string())

MIMEMultipart('alternative') sets Content-Type: multipart/alternative. Email clients display the last part whose type they understand, so the convention is to attach text/plain first and text/html last. Both MIMEText instances are constructed with their body text; charset detection runs automatically, setting Content-Transfer-Encoding and the charset parameter on each part's Content-Type header.

gopy mirror

Not yet ported. The email.mime convenience constructors are pure Python and have no C dependencies. They depend only on email.message.Message and email.charset.Charset, both of which are also pure Python. When ported, MIMEText, MIMEMultipart, MIMEBase, and MIMENonMultipart should live at module/email_mime/ and share a common Message struct with the rest of the email port.

CPython 3.14 changes

email.mime.text and email.mime.multipart received no API changes in CPython 3.14. The policy keyword argument (accepted by all MIME* constructors and forwarded to MIMEBase.__init__) has been present since Python 3.6 and was not modified. The email.headerregistry path activated by EmailPolicy gained internal fixes for structured address parsing, but those do not touch the mime/ subpackage directly.