Constructing Multipart MIME Messages for Sending Emails in Python

, a 12-minute piece by Dev Mukherjee Dev Mukherjee

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:

The email message we will create in this post will contain:

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 <support@anomaly.net.au>"
message["To"] = "Anomaly Reception <hello@anomaly.net.au>"
# 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 <different-address@anomaly.net.au>"

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 str variable.

The alternative message is created by instantiating a MIMEMultipart object, this time asking the constructor to use the alternative subtype.

textual_message = MIMEMultipart('alternative')

Use the 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)

Attach the 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)

Downloadable Attachment

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 pdf portion.

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')

The MIMEApplication helper automatically Base64 encodes the contents when they are attached to messages:

message.attach(pdf_file)

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 mixed message.

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:

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>')

Adding a 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 MIMEImage instance:

# 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 mixed message:

# 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()

Note: The Reply-To directive is not part of the SMTP payload; that's handled as part of the MIME message.

Next Up: a 3-minute piece by Dev Mukherjee Dev Mukherjee

Is Rigidly Following Agile Methodologies Getting in the Way of Agility?

Read more