You most likely came across this lesson because you are interested in using Python to send emails. You could want to get email reminders from your code, send a confirmation email to users when they register an account, or send emails to members of your organisation to remind them to pay their dues. These are all examples of possible uses of email reminders. Sending emails by hand is a chore that is both time-consuming and prone to errors; fortunately, it is simple to automate using Python. You are going to learn how to do things like in this lesson.
:
-
Set up a
secure connection
using
SMTP_SSL()
and
.starttls()
-
Use Python’s built-in
smtplib
library to send
basic emails
-
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
At the conclusion of this guide, you will discover a selection of transactional email services that may be of use to you in situations in which you need to send a significant number of
emails.
Getting Started
To facilitate the transmission of emails over the Simple Mail Transfer Protocol, Python includes the smtplib module as part of its standard distribution (SMTP). The SMTP service provided by smtplib follows the guidelines outlined in RFC 821. While sending emails, the examples in this tutorial will utilise Google’s SMTP server; however, the ideas discussed here are applicable to sending emails using any email provider. You may validate which ports your email provider uses by doing a fast search on Google. The vast majority of email service providers utilise the same connection ports that are outlined in this guide.
In order to get started with this tutorial, you will need to create a Gmail account that is dedicated to development. Instead, you may create an SMTP debugging server that will ignore the emails you send and instead output them to the command line. Both choices have been outlined for your convenience below. Before you send out any emails, it is important to utilise a local SMTP debugging server to ensure that your email functions are error-free and to troubleshoot any problems that may arise with your email’s operation.
emails.
Option 1: Setting up a Gmail Account for Development
In the event that you make the decision to send your emails via a Google account, it is strongly recommended that you create a disposable account in order to work on the development of your code. This is due to the fact that you will need to modify the security settings of your Gmail account in order to enable access from your Python code, as well as the fact that there is a possibility that you might inadvertently reveal your login information. In addition, I discovered that the inbox of my testing account quickly filled up with test emails. This is one of the reasons why I decided to create a separate Gmail account just for development purposes.
You may use the plus sign (plus sign) to add any modifiers to your email address before the at sign when you use Gmail. This is a handy tool that Google offers. For instance, e-mails sent to my+person1@gmail.com and my+person2@gmail.com will be received by the same recipient: my@gmail.com. You may use this to simulate several email addresses that all forward to the same inbox so that you can test the operation of email programmes.
Follow these steps to create a Gmail account for use in testing your code:
following:
-
Create a new Google account
. -
Turn
Allow less secure apps
to
ON
. Be aware that this makes it easier for others to gain access to your account.
Check out the instructions on Google’s website to learn how to get access credentials for your Python script by making use of the OAuth2 authorization flow if you do not want to reduce the security settings of your Gmail account.
framework.
Option 2: Setting up a Local SMTP Server
You are able to test the operation of email by running a local SMTP debugging server with the help of the smtpd module, which is pre-installed with Python by default. It will not send the emails to the address that has been supplied; instead, it will delete the emails and print their contents to the console. While a local debugging server is being run, it is not essential to deal with the encryption of messages or to utilise credentials in order to log in to an email server. This frees up a significant amount of time.
If you type the following into the Command prompt, you will be able to start a local SMTP debugging server:
Prompt:
$ python -m smtpd -c DebuggingServer -n localhost:1025
On Linux, run the command as normal, but preface it with sudo.
All emails that are sent via this server will be ignored, and the corresponding bytes object will be shown in the terminal window for each one.
line:
---------- MESSAGE FOLLOWS ----------
b'X-Peer: ::1'
b''
b'From: my@address.com'
b'To: your@address.com'
b'Subject: a local test mail'
b''
b'Hello there, here is a test email'
------------ END MESSAGE ------------
For the remainder of the guide, I will assume that you are sending messages from a Gmail account; however, if you are sending messages from a local debugging server, be sure to specify localhost as your SMTP server and use port 1025 rather than ports 465 or 587. In addition to this, there will be no requirement for you to utilise login() or encrypt the connection utilising using.
SSL/TLS.
Sending a Plain-Text Email
You will first get familiar with the basics of using Python to send plain-text emails before moving on to sending emails with HTML content and attachments. These are the kind of emails that you may perhaps compose with a basic text editor. There is none of the fancy extras that you might expect, such as text formatting or hyperlinks. You’ll pick up on it in due time.
later.
Starting a Secure SMTP Connection
When sending emails with Python, you should make sure that the connection to your SMTP server is secured. This will prevent anyone from reading your messages or gaining access to your login credentials inadvertently. A Secure Mail Transfer Protocol (SMTP) connection may be encrypted with either SSL (Secure Sockets Layer) or TLS (Transport Layer Security), which are both protocols. While working with a local development server, it is not required that you utilise any of these options.
There are two different approaches you may use to initiate a safe 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 any case, Gmail will make use of TLS, the more robust successor to SSL, in order to encrypt the messages that are sent over the service. It is strongly suggested that you utilise the create default context() function that is available in the ssl module. This is because Python’s Security concerns mandate its usage. This will load the trusted CA certificates into the system, enable host name verification and certificate validation, and attempt to choose protocol and cypher settings that are adequately secure.
If there is an email in your Gmail inbox that you wish to verify the encryption for, go to the More menu and choose the Display original option. You will then see the encryption type stated under the Received header. Python has a built-in module called smtplib that may be used to send emails to any computer connected to the internet that has an SMTP or ESMTP listening daemon.
I’m going to begin by demonstrating how to utilise the SMTP SSL() function since it not only initiates a connection that is secure from the beginning but is also somewhat more condensed than the alternative, which is.starttls(). It is important to keep in mind that Gmail necessitates that you connect to port 465 if you are going to use SMTP SSL() and port 587 if you are going to use.starttls ()
.
Option 1: Using
Using the SMTP SSL() function of smtplib, the following sample of code establishes a safe connection with the SMTP server used by Google. This connection is secured using Transport Layer Security (TLS). The secure sockets layer (SSL) default context is responsible for validating the host name as well as its certificates and maximising the connection’s level of security. Be sure to use your actual e-mail address rather than my@gmail.com while filling out the form.
:
import smtplib, ssl
port = 465 # For SSL
password = input("Type your password and press enter: ")
# Create a secure SSL context
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", port, context=context) as server:
server.login("my@gmail.com", password)
# TODO: Send email here
Using with the smtplib tool.
The SMTP SSL() function when used as the server ensures that the connection is immediately ended when the indented code block comes to an end. if the port number is 0 or if it is not supplied,. SMTP over SSL connections made using SMTP SSL() will utilise the default port (port 465).
It’s not a good idea to have your email password written down in your code, particularly if you plan on showing it to other people in the future. Use the input() function instead, as seen in the preceding example, to enable the user to key in their password while executing the script. You may import the password into a text file if you do not want it to appear on your screen while you are typing it.
getpass module, and be sure to utilise it.
getpass() should be used in its place for the blind input of your
password.
Option 2: Using
We might start by establishing an unprotected SMTP connection and then encrypt it using the.starttls() function rather than using.SMTP SSL() to establish a connection that is secure from the very beginning.
In order to do this, you will need to build an instance of the smtplib.SMTP class. This class wraps an SMTP connection and gives you access to the functions of that connection. In order to make the process of configuring your SMTP server and port as simple as possible, it is highly recommended that you do it at the very beginning of your script.
The next line of code utilises the construction server = SMTP() rather than the format with SMTP() as server: that we used in the earlier example. This is because the construction server = SMTP() is more efficient. The best way to prevent your programme from crashing in the event that anything goes wrong is to enclose the main code in a try block and have an except block direct any error messages to the standard output.
:
import smtplib, ssl
smtp_server = "smtp.gmail.com"
port = 587 # For starttls
sender_email = "my@gmail.com"
password = input("Type your password and press enter: ")
# Create a secure SSL context
context = ssl.create_default_context()
# Try to log in to server and send email
try:
server = smtplib.SMTP(smtp_server,port)
server.ehlo() # Can be omitted
server.starttls(context=context) # Secure the connection
server.ehlo() # Can be omitted
server.login(sender_email, password)
# TODO: Send email here
except Exception as e:
# Print any error messages to stdout
print(e)
finally:
server.quit()
After establishing an.SMTP() object, you should call.helo() (SMTP) or.ehlo() (ESMTP) in order to identify yourself to the server. This should be done twice: once after.starttls() and again after.helo(). This method is automatically called by.starttls() and.sendmail() if they are required, thus unless you want to verify the SMTP service extensions of the server, there is no need to use.helo() or.ehlo() unless you want to check the SMTP service extensions of the server ()
explicitly.
Sending Your Plain-text Email
You may send your email using the.sendmail() function after you have established a secure SMTP connection by using one of the ways described above. This function does exactly what it says on the tin.
tin:
server.sendmail(sender_email, receiver_email, message)
It is best practise to define the email addresses and the message text at the very beginning of your script, after the imports, so that you have the flexibility to modify them as needed.
easily:
sender_email = "my@gmail.com"
receiver_email = "your@gmail.com"
message = """\
Subject: Hi there
This message is sent from Python."""
# Send email here
The “Subject: Hey there” greeting is followed by two newlines denoted by the characters n at the beginning of the message string. This will guarantee that “Hello there” is shown as the subject of the email, and the text that comes after the newlines will be considered the message body.
The following snippet of code illustrates how to send a plain-text email using SMTP SSL ()
:
import smtplib, ssl
port = 465 # For SSL
smtp_server = "smtp.gmail.com"
sender_email = "my@gmail.com" # Enter your address
receiver_email = "your@gmail.com" # Enter receiver address
password = input("Type your password and press enter: ")
message = """\
Subject: Hi there
This message is sent from 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)
As a point of reference, below is an example of some code that sends a plain-text email over an SMTP connection that has been encrypted using the.starttls() function. If.starttls() and.sendmail() are called, the server.ehlo() lines do not need to be included since they are called implicitly by those functions.
required:
import smtplib, ssl
port = 587 # For starttls
smtp_server = "smtp.gmail.com"
sender_email = "my@gmail.com"
receiver_email = "your@gmail.com"
password = input("Type your password and press enter:")
message = """\
Subject: Hi there
This message is sent from 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)
Sending Fancy Emails
The built-in email package of Python enables you to create fancier email structures, which can subsequently be transmitted using smtplib just as you have been doing up until now. You will learn how to utilise the email package to send emails with HTML text and attachments in the next section.
attachments.
Including HTML Content
HTML comes in extremely helpful if you want to style the text in your email (bold, italics, and so on), or if you want to include any photos, hyperlinks, or material that responds to the user’s actions, and if you want to do any of these things. The MIME (Multipurpose Internet Mail Extensions) Multipart email, which combines HTML and plain text, is now the most used form of electronic communication. The email.mime module in Python is responsible for processing MIME messages. You should look at the manual for a more in-depth explanation.
It is crucial to provide a plain-text alternative for HTML messages since not all email clients show HTML information by default, and some users prefer to only receive plain-text emails for security concerns. Be careful to put the HTML message after the plain-text version of the message, since the email client will display the most recent multipart attachment first.
In the example that follows, our MIMEText() objects will have the HTML and plain-text versions of our message, and the MIMEMultipart(“alternative”) instance merges them into a single message with two different alternative renderings.
options:
import smtplib, ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
sender_email = "my@gmail.com"
receiver_email = "your@gmail.com"
password = input("Type your password and press enter:")
message = MIMEMultipart("alternative")
message["Subject"] = "multipart test"
message["From"] = sender_email
message["To"] = receiver_email
# Create the plain-text and HTML version of your message
text = """\
Hi,
How are you?
Real Python has many great tutorials:
www.realpython.com"""
html = """\
<html>
<body>
<p>Hi,<br>
How are you?<br>
<a href="http://www.realpython.com">Real Python</a>
has many great tutorials.
</p>
</body>
</html>
"""
# Turn these into plain/html MIMEText objects
part1 = MIMEText(text, "plain")
part2 = MIMEText(html, "html")
# Add HTML/plain-text parts to MIMEMultipart message
# The email client will try to render the last part first
message.attach(part1)
message.attach(part2)
# Create secure connection with server and send email
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
server.login(sender_email, password)
server.sendmail(
sender_email, receiver_email, message.as_string()
)
In this demonstration, you will begin by defining the plain-text message and the HTML message as string literals. Then, you will save the plain-text and HTML messages as plain/html MIMEText objects. After that, you may add them in this sequence to the MIMEMultipart message (also known as “alternative”), and then send the message using the secure connection you have established with the email server. Keep in mind that you should put the HTML message after the plain-text alternative, since email clients will attempt to display the final portion of the message.
first.
Adding Attachments Using the
Encoding binary files before to transit is necessary in order to accomplish sending them to an email server that is intended to operate with textual data. The most popular method for accomplishing this goal is known as base64, which converts binary data into ASCII letters that may be printed.
The following snippet of code is an example of how to send an email attachment that is a PDF file.
attachment:
import email, smtplib, ssl
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
subject = "An email with attachment from Python"
body = "This is an email with attachment sent from Python"
sender_email = "my@gmail.com"
receiver_email = "your@gmail.com"
password = input("Type your password and press enter:")
# Create a multipart message and set headers
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = subject
message["Bcc"] = receiver_email # Recommended for mass emails
# Add body to email
message.attach(MIMEText(body, "plain"))
filename = "document.pdf" # In same directory as script
# Open PDF file in binary mode
with open(filename, "rb") as attachment:
# Add file as application/octet-stream
# Email client can usually download this automatically as attachment
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
# Encode file in ASCII characters to send by email
encoders.encode_base64(part)
# Add header as key/value pair to attachment part
part.add_header(
"Content-Disposition",
f"attachment; filename= {filename}",
)
# Add attachment to message and convert message to string
message.attach(part)
text = message.as_string()
# Log in to server using secure context and send email
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, text)
The parameters are accepted by the MIMEultipart() message in the form of RFC5233-style key/value pairs. These key/value pairs are then saved in a dictionary and supplied to the.add header function of the Message base class.
Learn more about working with MIME by consulting the documentation that is available for the email.mime module in Python.
classes.
Sending Multiple Personalized Emails
Let’s say you want to send emails to the people who are a part of your organisation as a gentle reminder to pay their contribution fees. Or maybe you want to provide the students in your class with individualised emails including the grades that you have given them for their most recent task. These responsibilities are a piece of cake in
Python.
Make a CSV File With Relevant Personal Info
Creating a CSV (comma-separated values) file that includes all of the necessary personal information is a straightforward way to get started with sending many customised emails. (Be cautious that you do not reveal the private information of other individuals without first obtaining their permission.) One way to think of a CSV file is as a straightforward table, with the column headings often located on the very first line of the file.
The contents of the contacts file.csv file, which I stored in the same folder as my Python code, are shown in the following table. A group of fictitious people’s names, residences, and grades are all included in this document. To ensure that all of my emails are received in my own inbox, which in this scenario is my@gmail.com, I made use of constructs that included my+modifier@gmail.com.
:
name,email,grade
Ron Obvious,my+ovious@gmail.com,B+
Killer Rabbit of Caerbannog,my+rabbit@gmail.com,A
Brian Cohen,my+brian@gmail.com,C
When developing a CSV file, ensure that each of your values is denoted by a comma and that there is no surrounding whitespace.
whitespaces.
Loop Over Rows to Send Multiple Emails
The following piece of code serves as an example of how to open a CSV file and iterate through the lines of material contained inside it (skipping the header row). Before you send emails to all of your contacts, I’ve printed “Sending email to… ” for each contact. This will allow me to verify that the code is functioning properly before you send the emails. We may replace this text in the future with functionality that actually sends the emails.
emails:
import csv
with open("contacts_file.csv") as file:
reader = csv.reader(file)
next(reader) # Skip header row
for name, email, grade in reader:
print(f"Sending email to {name}")
# Send email here
The use of with open(filename) as file: in the example that was just shown guarantees that your file will be closed when the relevant code block is finished. The csv.reader() function makes it simple to extract the data from a CSV file and read them line by line. The following line for name, email, and 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. Because the next(reader) line skips the header row, the following line for name, email, and grade in reader: splits subsequent rows at each comma.
If the values in your CSV file include whitespaces on either or both sides, you may remove them by using the.strip command, which is located in the CSV file’s directory structure ()
method.
Personalized Content
You are able to tailor the text of a message by using the str.format() function to fill in the placeholders that are represented by curly brackets.
For instance, “hello, your name, here is the outcome of your assignment.”
The expression “hello John, you passed your assignment” will be returned to you if you run the format(name=”John”, result=”passed) command.
With Python 3.6, string formatting may be accomplished in a more elegant manner by utilising f-strings. However, these f-strings need the placeholders to be declared before the f-string itself is created. The more traditional.format() approach is used here so that the email message can be defined at the beginning of the script, and the placeholders for each contact can be filled in as the script iteratively processes the CSV file.
With this in mind, you may create a generic message body that has placeholders that can be customised to
individuals.
Code Example
The following code sample enables you to send customised emails to a number of different recipients at once. As seen in the last example, it iteratively processes a CSV file that contains the name, email address, and grade of each contact.
You just saw how the basic message is established at the beginning of the script. Next, for each contact in the CSV file, the placeholders for its “name” and “grade” are filled in. Finally, a customised email is sent out using a secure connection with the Gmail server.
before:
import csv, smtplib, ssl
message = """Subject: Your grade
Hi {name}, your grade is {grade}"""
from_address = "my@gmail.com"
password = input("Type your password and press enter: ")
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
A number of other libraries, such as Envelopes, Flanker, and Yagmail, have been developed specifically to simplify the process of sending emails. You can see in the code sample how Yagmail, which is meant to operate only with Google, makes the process of sending emails much more straightforward by using a user-friendly application programming interface (API).
below:
import yagmail
receiver = "your@gmail.com"
body = "Hello there from Yagmail"
filename = "document.pdf"
yag = yagmail.SMTP("my@gmail.com")
yag.send(
to=receiver,
subject="Yagmail test with attachment",
contents=body,
attachments=filename,
)
This code snippet is an example of how to send an email with a PDF attachment in a fraction of the lines that are required for our demonstration utilising.
email and
smtplib .
As the manual explains, while you are configuring Yagmail, you have the option of adding your Gmail validations to the keyring of your operating system. If you do not do this, Yahtzee will ask you to enter your password whenever it is necessary and will save it in the keychain.
automatically.
Transactional Email Services
Transactional email services are something you should look into if you want to send a significant number of emails, want to examine data on those emails, and want to guarantee that they are delivered in a dependable manner.
The following services each provide premium plans for sending big quantities of emails, but they also come with a free plan so that you may test them out before committing to a paid subscription. Some of these free plans are good for an endless amount of time and can be enough for your email requirements.
A summary of the free features offered by some of the most popular transactional email providers is provided below. When you click on the provider’s name, you will be sent to the price page of their website.
website.
Provider | Free plan |
---|---|
Sendgrid |
40,000 emails for your first 30 days, then 100/day |
Sendinblue |
300 emails/day |
Mailgun |
First 10,000 emails free |
Mailjet |
200 emails/day |
Amazon SES |
62,000 emails/month |
You may do a search on Google to see which provider best meets your requirements, or you can sign up for a couple of the providers’ free plans to determine which API you have the most success with.
most.
Sendgrid Code Example
You may get a feel for how to utilise a transactional email service like Sendgrid by looking at the following code sample, which demonstrates how to send emails using Sendgrid.
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("my@gmail.com")
to_email = Email("your@gmail.com")
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())
# The statements below can be included for debugging purposes
print(response.status_code)
print(response.body)
print(response.headers)
You are need to in order to execute this code.
first:
-
Sign up for a (free) Sendgrid account
-
Request an API key
for user validation -
Add your API key by typing
setx SENDGRID_API_KEY "YOUR_API_KEY"
in Command Prompt (to store this API key permanently) or
set SENDGRID_API_KEY YOUR_API_KEY
to store it only for the current client session
The README file of the Sendgrid repository on Github contains additional information that may be used to configure Sendgrid on a Mac or a Windows computer.