How to Send Emails in Python?

Python provides a native Email class for handling email messages. This makes adding email sending functionality to your Python app an easy task. In this post, we will demonstrate how to use Python modules and classes to build and send emails via SMTP as well as how to properly test them. We will focus mainly on coding basic HTML emails, adding images, and attaching files.

Here’s how you can quickly send an email with Gmail using the Yagmail library:

  • Install Yagmail by running the command pip install yagmail in your shell.
  • Install Keyring by running the command pip install keyring in your shell.
  • Execute the following code snippet (specify your own username, password, and email content):
import yagmail
yag = yagmail.SMTP('username', 'password')
yag.send(to = 'receiver@email.com',
         subject = 'hi',
         contents = 'Just wanted to say "hi"')

In the remaining article, we will dive deeper into the different technologies to solve many problems that arise in practice (e.g. to test your email automations).

If you feel like your Python coding skills need some upgrades, feel free to download the high-resolution, free Python cheat sheets (PDF):

Email sending basics in Python

Before we start work on creating and sending emails, let’s quickly go over email infrastructure and have a look at the additional toolsets we can use. 

We have already mentioned that Python has built-in functionality for sending emails. The smtplib module handles email sending via an SMTP server. You can use any service you prefer, as long as you know its standard credentials: host, port, username, and password. 

For example, if you use Amazon SES, you should define it as follows: 

import smtplib
port = 587 
smtp_server = "email-smtp.us-west-2.amazonaws.com" # hostname depends on the AWS region you use 
login = "your-Amazon-SES-SMTP-name"
password = "your-Amazon-SES-SMTP-password" 
    with smtplib.SMTP(smtp_server, port) as server:
        server.login(login, password)

Note: this and all following code examples are made with Python version 3.7.2

If you prefer Gmail, the configuration will look similar:

import smtplib, ssl
port = 465  
password = input("your password")
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", port, context=context) as server:
    server.login("myname@gmail.com", password)

Just remember that you should authorize your Python application for use with your Gmail account. If you are concerned about security, it is recommended that you use the OAuth2 authorization protocol. Otherwise, you can just allow less secure apps (2-step verification has to be disabled for your account). 

There are several popular Gmail/SMTP clients designed to streamline email sending. One of them is the Python library Yagmail. If you use Gmail, this one is worth trying.

Email testing in Python

Another important aspect of email sending is email testing. When you start working with new technology, you must isolate your experiments from the production and inboxes of real users. You can use your own email address for testing purposes, but if you are checking sending functionality to multiple recipients (and we will do it in this tutorial!), it will be flooded with test emails pretty quickly. Another issue that can arise when using your production server to test emails, is the risk of damage to your domain reputation. 

Test email sending locally 

If you prefer working in the local environment, the local SMTP debugging server might be an option you’ll like. For this purpose, Python offers an smtpd module. It has a DebuggingServer feature, which will discard messages you are sending out and will print them to stdout. It is compatible with all operations systems.

Python provides smtpd module for testing purposes. It lets you use localhost for testing, with a DebuggingServer feature. With it, your sent messages will be discarded and printed to stdout. 

It can be used on two ports: 1025 and 25:

python -m smtpd -n -c DebuggingServer localhost:1025

But remember that root permissions are required for the port 25: 

sudo python -m smtpd -n -c DebuggingServer localhost:25

Full email testing with a dedicated service

With the localhost, you will be able to verify that your emails are sent and get a list of errors if something is wrong. But it won’t be possible to preview your messages and check that images are rendered properly and ensure that attachments are correctly encoded. 

From this perspective, we recommend using full-featured tools for email testing, which will allow you to check sending as well as inspect the HTML content. There are several tools, which can be integrated as an SMTP server. Mailtrap catches all your sent messages and display them in virtual inboxes, as well as help you review your spam score, possible problems with HTML, and more. Its forever free plan is enough for email experiments. For the paid plans, there is even an email forwarding and Bcc support. 

We will use Mailtrap as an SMTP server in the code samples in this post, so here is how to set it up. Go to your inbox settings, enter your username and password, and paste them as follows: 

import smtplib

port = 2525 
smtp_server = "smtp.mailtrap.io"
login = "1a2b3c4d5e6f7g" # your login generated by Mailtrap
password = "1a2b3c4d5e6f7g" # your password generated by Mailtrap

Also, in the app, you will find the integration and a basic script example.  

Debugging 

Here is another helpful thing to mention before we start building email messages. The email package in Python uses exception and defect classes, which is very useful for debugging. We recommend you to import the gaierror component and then add the “try” and “except” blocks:

import smtplib
from socket import gaierror
# … 
try:
    #send your message with credentials specified above
    with smtplib.SMTP(smtp_server, port) as server:
        server.login(login, password)
        server.sendmail(sender, receiver, message)

    # tell the script to report if your message was sent or which errors need to be fixed 
    print('Sent')
except (gaierror, ConnectionRefusedError):
    print('Failed to connect to the server. Bad connection settings?')
except smtplib.SMTPServerDisconnected:
    print('Failed to connect to the server. Wrong user/password?')
except smtplib.SMTPException as e:
    print('SMTP error occurred: ' + str(e))

Finally, let’s explore how to craft an HTML email template. 

HTML emails in Python

The HTML email content is represented as the MIME message type and is handled by the email.mime module. You should add both HTML and plain text parts and then merge them. In Python, this is done with the following modules:

from email.mime.text import MIMEText 
from email.mime.multipart import MIMEMultipart 
message = MIMEMultipart("alternative") 

The full message code looks like this:

import smtplib 
from email.mime.text import MIMEText 
from email.mime.multipart import MIMEMultipart 
port = 2525 
smtp_server = "smtp.mailtrap.io" 
login = "1a2b3c4d5e6f7g" # paste your login generated by Mailtrap 
password = "1a2b3c4d5e6f7g" # paste your password generated by Mailtrap
sender_email = "mailtrap@example.com" 
receiver_email = "new@example.com" 
message = MIMEMultipart("alternative") 
message["Subject"] = "multipart test" 
message["From"] = sender_email 
message["To"] = receiver_email 
# write the plain text part text = """\ Hi, Check out our new post on sending emails with Python! """ 
# write the HTML part html = """\ <html> <body> <p>Hi,<br> Check out our new post on sending emails with Python!</p> </body> </html> """
# convert both parts to MIMEText objects and add them to the MIMEMultipart message 
part1 = MIMEText(text, "plain") 
part2 = MIMEText(html, "html") 
message.attach(part1)
 message.attach(part2) 
# send your email with smtplib.SMTP("smtp.mailtrap.io", 2525) as server: server.login(login, password) 
server.sendmail( sender_email, receiver_email, message.as_string() ) 
print('Sent')

You will receive this message in your Mailtrap inbox right after you see the Sent result in Shell: 

How to add images and files 

The most common use cases of sending emails from your Python apps are welcome emails, resetting password, or order confirmations.  So, you need to add images and file attachments to your messages. In Python, you can do this with the help of appropriate modules. 

Images can be added to emails in several ways: CID attachments, inline embedding (base64), or linking to a hosted picture. You can experiment with each of them in your code. Now, we’ll review how to work with inline embedding. Indeed, it’s super easy, you just need to import the base64 module – it will encode the picture to the appropriate format. 

If we want to attach a file, we need to import two more modules:

from email import encoders
from email.mime.base import MIMEBase

Here is how our code will look for an order confirmation message, which contains both image and PDF files:

# import the necessary components first
import smtplib
import base64
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email import encoders
from email.mime.base import MIMEBase


port = 2525
smtp_server = "smtp.mailtrap.io"
login = "40d081ab7d7700" # paste your login generated by Mailtrap
password = "6ff78f7a8ac4a4" # paste your password generated by Mailtrap

sender_email = "mailtrap@example.com"
receiver_email = "new@example.com"
message = MIMEMultipart("alternative")
message["Subject"] = "order confirmation with image and PDF file"
message["From"] = sender_email
message["To"] = receiver_email

# place the image file is in the same directory that you run your Python script from
encoded = base64.b64encode(open("mailtrap-flying.png", "rb").read()).decode()

html = f"""\
<html>
 <body>
 <p>This is an example of how you can send an order confirmation with an image and attached file in Python</p>
   <img src="data:image/jpg;base64,{encoded}">
 </body>
</html>
"""

# Add plain text body to email
body = "This is an example of how you can send an order confirmation with an image and attached file in Python"
message.attach(MIMEText(body, "plain"))

filename = "your_order.pdf"
# Open PDF file in binary mode

# Place the file in the same directory where you run your Python script from
with open(filename, "rb") as attachment:
    # The content type "application/octet-stream" means that a MIME attachment is a binary file
    part = MIMEBase("application", "octet-stream")
    part.set_payload(attachment.read())

# Encode to base64
encoders.encode_base64(part)

# Add header 
part.add_header(
    "Content-Disposition",
    f"attachment; filename= {filename}",
)

# Add attachment to your message and convert it to string
message.attach(part)
text = message.as_string()
part = MIMEText(html, "html")
message.attach(part)

# send your email
with smtplib.SMTP("smtp.mailtrap.io", 2525) as server:
   server.login(login, password)
   server.sendmail(
       sender_email, receiver_email, message.as_string()
   )
print('Sent')

Let’s verify the results in Mailtrap:

We can see the embedded image and our file attached as a your_order.pdf. It means that we did everything right!

How to send multiple emails with dynamic content

This is probably our favorite part about sending emails with Python: customized messages can be easily sent to multiple recipients, with the help of variables and loops. 

One of the easiest ways to do this is by creating a .csv database (don’t forget to place it to the folder with your Python script). 

For example, let’s use the name and email address, the most popular variables: 

#name,email
John Johnson,john@johnson.com
Peter Peterson,peter@peterson.com

With the following script, this file will be opened:

with open("filename.csv") as file:
       reader = csv.reader(file)

And {name} will be inserted from the corresponding column:

    for name, email in reader:
            server.sendmail(
               sender,
                email,
                message.format(name=name, recipient=email, sender=sender)
            )

Let’s check the full message code with Mailtrap:

import csv, smtplib

port = 2525 
smtp_server = "smtp.mailtrap.io"
login = "1a2b3c4d5e6f7g" # paste your login generated by Mailtrap
password = "1a2b3c4d5e6f7g" # paste your password generated by Mailtrap

message = """Subject: Order confirmation
To: {recipient}
From: {sender}

Hi {name}, thanks for your order! We are processing it now and will contact you soon"""
sender = "new@example.com"

with smtplib.SMTP("smtp.mailtrap.io", 2525) as server:
    server.login(login, password)
    with open("contacts.csv") as file:
        reader = csv.reader(file)
        next(reader)  # it skips the header row
        for name, email in reader:
            server.sendmail(
               sender,
                email,
                message.format(name=name, recipient=email, sender=sender)
            )
            print(f'Sent to {name}')

The  response should look as below:

Sent to John Johnson
Sent to Peter Peterson
>>>

As a result, we should get two messages to the Mailtrap inboxes, per each recipient: 

To wrap up

In this post, we have gone through the main options for sending emails from a Python app. The native functionality is quite rich so that it makes experimenting with emails easy and fun. To deepen your knowledge, we recommend referring to the official framework documentation

Also, you can try several libraries for email sending available on PyPi. The Python library Yagmail is one of the most prominent examples.

Remember: don’t spam (with great power comes great responsibility), be good, and don’t forget to test your emails!

This article was originally published on the Mailtrap’s blog: Sending emails with Python

If you feel like you lack a thorough education of the Python basics, check out the Finxter free Python cheat sheet course (with printable, high-res PDFs)!

Leave a Comment

Your email address will not be published. Required fields are marked *