Constructing multipart MIME messages for sending emails in Python
Most Internet services need to send emails to their users. Constructing the message is a separate task to sending the email. This post demonstrates proper construction of Multipurpose Internet Mail Extensions (MIME) messages in Python.
MIME messages have many subtypes to encapsulate different sorts of content. Python's email MIME library provides wrappers for working with them. We will be exploring the following subtypes to demonstrate how to package messages properly:
mixedused to send content of different types, inline or as attachments
alternativeused to send alternate versions of the same content e.g. group plain text and HTML versions of an email body
relatedused to include related resources inline or as attachments e.g. images used in your HTML content
The email message we will create in this post will contain:
- HTML and plain text versions of the body content
- An attached file (which in this instance is a generated
- Optionally have an image in the HTML content
Let's begin by creating a
mixed MIME multipart message which will house the various components (email body, images displayed inline and downloadable attachments) of our message:
from email.mime.multipart import MIMEMultipart # By default the type is set to mixed message = MIMEMultipart() message["Subject"] = "Hello World" message["From"] = "Anomaly Support <email@example.com>" message["To"] = "Anomaly Reception <firstname.lastname@example.org>" # If a Reply-To is explicitly provided then the email client will # use this address when the user hit the reply button message["Reply-To"] = "Anomaly Support <email@example.com>"
HTML and plain text body content
We will create an
alternative message that has two attachments, first containing the plain text version of the email message followed by the HTML version.
HTML and text content would typically be produced with the assistance of a templating engine. For the purposes of this post I assume that you've already generated the content and it is available as a Python
alternative message is created by instantiating a
MIMEMultipart object, this time asking the constructor to use the
textual_message = MIMEMultipart('alternative')
MIMEText helper to create the plain text and HTML attachments.
from email.mime.text import MIMEText # I assume that you have a variable called txt_content # with the textual rendition of the message, this could # have been generated via a templating engine plaintext_part = MIMEText(txt_content, 'plain') textual_message.attach(plaintext_part) # likewise a variable called html_content with the HTML html_part = MIMEText(html_content, 'html') textual_message.attach(html_part)
alternative message to the parent
mixed message to create the textual body of the email message.
# Attach this to the mixed type message message.attach(textual_message)
Textual or binary files can be attached to your email by using the
MIMEApplication helper. It's instantiated with the contents of the attachment followed by the MIME type of the attachment i.e. for an
application/pdf you supply it the
By default the contents would be contained inline. To make this an attachment, use the
add_header method on the
MIMEApplication instance and declare it to be an attachment. Since the contents of the attachment are now part of an encoded message, you must explicitly provide meta information like a filename.
from email.mime.application import MIMEApplication pdf_file = MIMEApplication(pdf_file_data, "pdf") pdf_file.add_header('Content-Disposition', 'attachment', filename='report.pdf')
MIMEApplication helper automatically
Base64 encodes the contents when they are attached to messages:
Optional image for use in the HTML body
Consider an image you want to display in the email body but don't want to offer to the recipient for download. You would need to use a
related message to wrap the email body contained in the
alternate message that we created earlier and attach the
related message to the original
You will also require the use of the
MIMEImage subclass to ensure the image is encoded properly. First thing's first, create a
MIMEMultipart message of subtype
related_message = MIMEMultipart('related')
Create an instance of
MIMEImage that will contain the image contents:
from email.MIMEImage import MIMEImage # Content of an image that relates to the HTML message image = MIMEImage(image_binary_contents) # Lets you reference the image locally in HTML image.add_header('Content-ID', '<image1>')
Content-ID header will let you reference the image in the HTML content as
<img src="cid:image1">. Attach the originally created
alternate message to the
related message followed by the
# Attach the alternative textural message to the # related message related_message.attach(textual_message) # Attach the image to the related message related_message.attach(image)
Finally attach the
related message to the
# Attach the related message to our parent message message.attach(related_message)
Encoding and sending the email
Once you have mixed and matched various
MIME types and subtypes, your message can be encoded by calling the
as_string function. This, in turn, asks every submessage and attachment to render themselves as one coherent message. The result is the string you can send over
SMTP as your email.
encoded_email = message.as_string()
smtplib provides an
SMTP client that can deliver this message for you. It's pretty straightforward. Depending on your application and server setup you may choose to use local relays or relay directly to an available SMTP server. I will leave that to your applications' specific needs.
from smtplib import SMTP smtp = SMTP("localhost", 25) smtp.sendmail(message["From"], message["To"], encoded_email) smtp.quit()
Reply-To directive is not part of the SMTP payload; that's handled as part of the