Sending Emails With Python - Real Python
Sending Emails With Python - Real Python
Table of Contents
Getting Started
Sending a Plain-Text Email
Sending Fancy Emails
Sending Multiple Personalized Emails
Yagmail
Sending Emails With Python Transactional Email Services
Sendgrid Code Example
by Joska de Langen ! 45 Comments " intermediate web-dev
Conclusion
Getting Started
Option 1: Setting up a Gmail Account for Development ' Recommended Video Course
Option 2: Setting up a Local SMTP Server Sending Emails With Python
Sending a Plain-Text Email
Starting a Secure SMTP Connection
Sending Your Plain-text Email
Sending Fancy Emails
Including HTML Content
Adding Attachments Using the email Package
Sending Multiple Personalized Emails
Make a CSV File With Relevant Personal Info
Loop Over Rows to Send Multiple Emails
Personalized Content
Code Example
Yagmail
Transactional Email Services
Sendgrid Code Example
Conclusion
' Watch Now This tutorial has a related video course created by the Real Python team.
Watch it together with the written tutorial to deepen your understanding: Sending
Emails With Python
You probably found this tutorial because you want to send emails using Python. Perhaps
you want to receive email reminders from your code, send a confirmation email to users
when they create an account, or send emails to members of your organization to remind
them to pay their dues. Sending emails manually is a time-consuming and error-prone task,
but it’s easy to automate with Python.
Send emails with HTML content and attachments using the email package
Send multiple personalized emails using a CSV file with contact data
Use the Yagmail package to send email through your Gmail account using only a few
lines of code
You’ll find a few transactional email services at the end of this tutorial, which will come in
useful when you want to send a large number of emails.
Free Bonus: Click here to get access to a chapter from Python Tricks: The Book
that shows you Python’s best practices with simple examples you can apply instantly
to write more beautiful + Pythonic code.
Getting Started
Python comes with the built-in smtplib module for sending emails using the Simple Mail
Transfer Protocol (SMTP). smtplib uses the RFC 821 protocol for SMTP. The examples in this
tutorial will use the Gmail SMTP server to send emails, but the same principles apply to
other email services. Although the majority of email providers use the same connection
ports as the ones in this tutorial, you can run a quick Google search to confirm yours.
To get started with this tutorial, set up a Gmail account for development, or set up an SMTP
debugging server that discards emails you send and prints them to the command prompt
instead. Both options are laid out for you below. A local SMTP debugging server can be
useful for fixing any issues with email functionality and ensuring your email functions are
bug-free before sending out any emails.
A nice feature of Gmail is that you can use the + sign to add any modifiers to your email
address, right before the @ sign. For example, mail sent to [email protected] and
[email protected] will both arrive at [email protected]. When testing email functionality,
you can use this to emulate multiple addresses that all point to the same inbox.
If you don’t want to lower the security settings of your Gmail account, check out Google’s
documentation on how to gain access credentials for your Python script, using the OAuth2
authorization framework.
You can start a local SMTP debugging server by typing the following in Command Prompt:
Shell
Any emails sent through this server will be discarded and shown in the terminal window as a
bytes object for each line:
Shell
For the rest of the tutorial, I’ll assume you’re using a Gmail account, but if you’re using a
local debugging server, just make sure to use localhost as your SMTP server and use port
1025 rather than port 465 or 587. Besides this, you won’t need to use login() or encrypt the
communication using SSL/TLS.
There are two ways to start a secure connection with your email server:
Start an SMTP connection that is secured from the beginning using SMTP_SSL().
Start an unsecured SMTP connection that can then be encrypted using .starttls().
In both instances, Gmail will encrypt emails using TLS, as this is the more secure successor
of SSL. As per Python’s Security considerations, it is highly recommended that you use
create_default_context() from the ssl module. This will load the system’s trusted CA
certificates, enable host name checking and certificate validation, and try to choose
reasonably secure protocol and cipher settings.
If you want to check the encryption for an email in your Gmail inbox, go to More → Show
original to see the encryption type listed under the Received header.
smtplib is Python’s built-in module for sending emails to any Internet machine with an
SMTP or ESMTP listener daemon.
I’ll show you how to use SMTP_SSL() first, as it instantiates a connection that is secure from
the outset and is slightly more concise than the .starttls() alternative. Keep in mind that
Gmail requires that you connect to port 465 if using SMTP_SSL(), and to port 587 when using
.starttls().
Python
It’s not safe practice to store your email password in your code, especially if you intend to
share it with others. Instead, use input() to let the user type in their password when
running the script, as in the example above. If you don’t want your password to show on
your screen when you type it, you can import the getpass module and use .getpass()
instead for blind input of your password.
The code snippet below uses the construction server = SMTP(), rather than the format
with SMTP() as server: which we used in the previous example. To make sure that your
code doesn’t crash when something goes wrong, put your main code in a try block, and let
an except block print any error messages to stdout:
Python
smtp_server = "smtp.gmail.com"
port = 587 # For starttls
sender_email = "[email protected]"
password = input("Type your password and press enter: ")
To identify yourself to the server, .helo() (SMTP) or .ehlo() (ESMTP) should be called after
creating an .SMTP() object, and again after .starttls(). This function is implicitly called
by .starttls() and .sendmail() if needed, so unless you want to check the SMTP service
extensions of the server, it is not necessary to use .helo() or .ehlo() explicitly.
Python
I recommend defining the email addresses and message content at the top of your script,
after the imports, so you can change them easily:
Python
sender_email = "[email protected]"
receiver_email = "[email protected]"
message = """\
Subject: Hi there
The message string starts with "Subject: Hi there" followed by two newlines (\n). This
ensures Hi there shows up as the subject of the email, and the text following the newlines
will be treated as the message body.
Python
context = ssl.create_default_context()
with smtplib.SMTP_SSL(smtp_server, port, context=context) as server:
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, message)
For comparison, here is a code example that sends a plain-text email over an SMTP
connection secured with .starttls(). The server.ehlo() lines may be omitted, as they
are called implicitly by .starttls() and .sendmail(), if required:
Python
context = ssl.create_default_context()
with smtplib.SMTP(smtp_server, port) as server:
server.ehlo() # Can be omitted
server.starttls(context=context)
server.ehlo() # Can be omitted
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, message)
As not all email clients display HTML content by default, and some people choose only to
receive plain-text emails for security reasons, it is important to include a plain-text
alternative for HTML messages. As the email client will render the last multipart attachment
first, make sure to add the HTML message after the plain-text version.
In the example below, our MIMEText() objects will contain the HTML and plain-text versions
of our message, and the MIMEMultipart("alternative") instance combines these into a
single message with two alternative rendering options:
Python
sender_email = "[email protected]"
receiver_email = "[email protected]"
password = input("Type your password and press enter:")
message = MIMEMultipart("alternative")
message["Subject"] = "multipart test"
message["From"] = sender_email
message["To"] = receiver_email
In this example, you first define the plain-text and HTML message as string literals, and then
store them as plain/html MIMEText objects. These can then be added in this order to the
MIMEMultipart("alternative") message and sent through your secure connection with
the email server. Remember to add the HTML message after the plain-text alternative, as
email clients will try to render the last subpart first.
The code example below shows how to send an email with a PDF file as an attachment:
Python
Check out the documentation for Python’s email.mime module to learn more about using
MIME classes.
Below are the contents of the file contacts_file.csv, which I saved in the same folder as
my Python code. It contains the names, addresses, and grades for a set of fictional people. I
used [email protected] constructions to make sure all emails end up in my own
inbox, which in this example is [email protected]:
CSV
name,email,grade
Ron Obvious,[email protected],B+
Killer Rabbit of Caerbannog,[email protected],A
Brian Cohen,[email protected],C
When creating a CSV file, make sure to separate your values by a comma, without any
surrounding whitespaces.
Python
import csv
In the example above, using with open(filename) as file:makes sure that your file
closes at the end of the code block. csv.reader() makes it easy to read a CSV file line by
line and extract its values. The next(reader) line skips the header row, so that the following
line for name, email, grade in reader: splits subsequent rows at each comma, and
stores the resulting values in the strings name, email and grade for the current contact.
If the values in your CSV file contain whitespaces on either or both sides, you can remove
them using the .strip() method.
Personalized Content
You can put personalized content in a message by using str.format() to fill in curly-bracket
placeholders. For example, "hi {name}, you {result} your
assignment".format(name="John", result="passed") will give you "hi John, you
passed your assignment".
As of Python 3.6, string formatting can be done more elegantly using f-strings, but these
require the placeholders to be defined before the f-string itself. In order to define the email
message at the beginning of the script, and fill in placeholders for each contact when
looping over the CSV file, the older .format() method is used.
With this in mind, you can set up a general message body, with placeholders that can be
tailored to individuals.
Code Example
The following code example lets you send personalized emails to multiple contacts. It loops
over a CSV file with name,email,grade for each contact, as in the example above.
The general message is defined in the beginning of the script, and for each contact in the
CSV file its {name} and {grade} placeholders are filled in, and a personalized email is sent
out through a secure connection with the Gmail server, as you saw before:
Python
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
server.login(from_address, password)
with open("contacts_file.csv") as file:
reader = csv.reader(file)
next(reader) # Skip header row
for name, email, grade in reader:
server.sendmail(
from_address,
email,
message.format(name=name,grade=grade),
)
Yagmail
There are multiple libraries designed to make sending emails easier, such as Envelopes,
Flanker and Yagmail. Yagmail is designed to work specifically with Gmail, and it greatly
simplifies the process of sending emails through a friendly API, as you can see in the code
example below:
Python
import yagmail
receiver = "[email protected]"
body = "Hello there from Yagmail"
filename = "document.pdf"
yag = yagmail.SMTP("[email protected]")
yag.send(
to=receiver,
subject="Yagmail test with attachment",
contents=body,
attachments=filename,
)
This code example sends an email with a PDF attachment in a fraction of the lines needed
for our example using email and smtplib.
When setting up Yagmail, you can add your Gmail validations to the keyring of your OS, as
described in the documentation. If you don’t do this, Yagmail will prompt you to enter your
password when required and store it in the keyring automatically.
Below is an overview of the free plans for some of the major transactional email services.
Clicking on the provider name will take you to the pricing section of their website.
You can run a Google search to see which provider best fits your needs, or just try out a few
of the free plans to see which API you like working with most.
Python
import os
import sendgrid
from sendgrid.helpers.mail import Content, Email, Mail
sg = sendgrid.SendGridAPIClient(
apikey=os.environ.get("SENDGRID_API_KEY")
)
from_email = Email("[email protected]")
to_email = Email("[email protected]")
subject = "A test email from Sendgrid"
content = Content(
"text/plain", "Here's a test email sent through Python"
)
mail = Mail(from_email, subject, to_email, content)
response = sg.client.mail.send.post(request_body=mail.get())
More information on how to set up Sendgrid for Mac and Windows can be found in the
repository’s README on Github.
Conclusion
You can now start a secure SMTP connection and send multiple personalized emails to the
people in your contacts list!
You’ve learned how to send an HTML email with a plain-text alternative and attach files to
your emails. The Yagmail package simplifies all these tasks when you’re using a Gmail
account. If you plan to send large volumes of email, it is worth looking into transactional
email services.
This tutorial has a related video course created by the Real Python team.
' Watch Now
Watch it together with the written tutorial to deepen your understanding: Sending
Emails With Python
Each tutorial at Real Python is created by a team of developers so that it meets our high
quality standards. The team members who worked on this tutorial are:
Real Python Comment Policy: The most useful comments are those written
with the goal of learning from or helping out other readers—after reading the
whole article and all the earlier comments. Complaints and insults generally
won’t make the cut here.
What’s your #1 takeaway or favorite thing you learned? How are you going to put your
newfound skills to use? Leave a comment below and let us know.
Keep Learning
Happy Pythoning!