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 guest 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)!