Send HTML Email With Attachments via SMTP

Starting from Python 3.6, the standard library includes the email package to build email messages (which may eventually contain HTML code and attachments) and the smtplib module to send them through the SMTP protocol, with the possibility of using secure SSL and TLS connections. In this post we will learn how to build and send an email in Python through any SMTP server (Gmail, Outlook, Yahoo, etc. or a custom SMTP service).

Let's start by sending a plain text email. The first step is to import the necessary modules. The email package has several modules (email.message, email.parser, etc.) to work with emails. We are only interested in email.message, which contains the necessary class (EmailMessage) to create an email. On the other hand, we will need the smtplib module to send it through an SMTP server once the message has been created.

from email.message import EmailMessage
import smtplib

Next, let's define three variables to store the email sender and recipient, and the message itself (in plain text or HTML).

sender = "sender@example.com"
recipient = "recipient@example.com"
message = "Hello world!"

You will need to replace the content of sender and recipient with the actual email addresses. If you're using a service like Gmail, Outlook, or Yahoo, make sure to put in the sender variable the email address of the account you want to send the message from.

Now let's use this data to build the message via the email.message.EmailMessage class:

email = EmailMessage()
email["From"] = sender
email["To"] = recipient
email["Subject"] = "Sent from Python!"
email.set_content(message)

The email instance is like a dictionary in which the "From" (wherefrom the message is sent), "To" (where it is sent to) and "Subject" keys are defined. By default, the set_content() method, which defines the body of the email, assumes that the message passed as an argument is plain text.

Once the email has been created, we must send it through one of the many existing protocols. In this case we will use SMTP with the help of the standard module smtplib. The first thing to do is to connect to the proper server (typically a domain, such as smtp.gmail.com or smtp.example.com, or an IP address).

smtp = smtplib.SMTP("smtp.example.com")

Check your email provider's documentation for the exact SMTP server address. The smtplib.SMTP class establishes an insecure connection by default. The default port is 25. However, virtually all modern SMTP servers will require a secure connection using SSL or TLS. If your email provider requires TLS, use this code instead:

# The port of the TLS protocol is usually 587.
smtp = smtplib.SMTP("smtp.example.com", port=587)
# Start the secure connection via TLS.
smtp.starttls()

If the SMTP server requires a secure connection via SSL (instead of TLS), then we must use the smtplib.SMTP_SSL class (thus the starttls() call is not necessary):

smtp = smtplib.SMTP_SSL("smtp.example.com")

The smtplib.SMTP_SSL class uses port 465 by default, which is the one normally used for SSL connections. Many email servers (such as Gmail) support SSL and TLS interchangeably, so either of the two previous codes will work.

Once the connection to the SMTP server is established, we must authenticate with our credentials (username and password) and send the built email.

smtp.login(sender, "password123")
smtp.sendmail(sender, recipient, email.as_string())
smtp.quit()

The user that is passed as an argument to the login() method is usually the same email address from which we send the message, stored in sender. In services like Gmail, Outlook or Yahoo, the password passed as the second argument is the same as your account password. The sendmail() method is responsible for sending the email to the recipient. Finally, we close the connection to the server via the quit() method.

Full code:

from email.message import EmailMessage
import smtplib
sender = "sender@example.com"
recipient = "recipient@example.com"
message = "Hello world!"
email = EmailMessage()
email["From"] = sender
email["To"] = recipient
email["Subject"] = "Sent from Python!"
email.set_content(message)
smtp = smtplib.SMTP_SSL("smtp.example.com")
# Or if TLS is used:
# smtp = SMTP("smtp.example.com", port=587)
# smtp.starttls()
smtp.login(sender, "password123")
smtp.sendmail(sender, recipient, email.as_string())
smtp.quit()

Sending a HTML Email

In order to include HTML code in an email, just add the subtype="html" argument when defining the email body via the set_content() method.

from email.message import EmailMessage
import smtplib
sender = "sender@example.com"
recipient = "recipient@example.com"
message = "<strong>Hello</strong>, <em>world</em>!"
email = EmailMessage()
email["From"] = sender
email["To"] = recipient
email["Subject"] = "Sent from Python!"
email.set_content(message, subtype="html")
smtp = smtplib.SMTP_SSL("smtp.example.com")
# Or if TLS is used:
# smtp = SMTP("smtp.example.com", port=587)
# smtp.starttls()
smtp.login(sender, "password123")
smtp.sendmail(sender, recipient, email.as_string())
smtp.quit()

Attachments

We can attach files to an email by calling the add_attachment() method as many times as needed. For example, if we want to attach the file attachment.zip, after setting the body of the message we will do the following:

with open("attachment.zip", "rb") as f:
    email.add_attachment(
        f.read(),
        filename="attachment.zip",
        maintype="application",
        subtype="zip"
    )

filename="attachment.zip" defines the name that the file will have in the email, which may differ from the original file name as read via the open() function. The maintype and subtype arguments define the type of the file according to the MIME specification. You can see a list of valid values for these two arguments at https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types. So, for example, if the MIME type for .zip files is application/zip (according to the list in the previous link), then maintype should be "application" and subtype should be "zip".

Full code:

from email.message import EmailMessage
import smtplib
sender = "sender@example.com"
recipient = "recipient@example.com"
message = "<strong>Hello</strong>, <em>world</em>!"
email = EmailMessage()
email["From"] = sender
email["To"] = recipient
email["Subject"] = "Sent from Python!"
email.set_content(message, subtype="html")
with open("attachment.zip", "rb") as f:
    email.add_attachment(
        f.read(),
        filename="attachment.zip",
        maintype="application",
        subtype="zip"
    )
smtp = smtplib.SMTP_SSL("smtp.example.com")
# Or if TLS is used:
# smtp = SMTP("smtp.example.com", port=587)
# smtp.starttls()
smtp.login(sender, "password123")
smtp.sendmail(sender, recipient, email.as_string())
smtp.quit()