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.