How to Send an Email in Python

On January 26, 2024
14min read
Aleksandr Varnin Full Stack Developer @ Railsware
How to Send an Email in Python

What do you need in order to send an email with Python? Some basic programming and web knowledge along with elementary Python skills. We assume you’ve already had a web app built with this language and now you need to extend its functionality with notifications or other email sending options. 

With numerous code examples, we’ll guide you through the fundamental steps of sending different types of emails via an SMTP (Simple Mail Transfer Protocol) server using Python. 

Note: Written and tested on Python 3.6.9. See more Python-related repositories on Github.

What email sending options with Python are there?

The two primary ways of sending emails in Python are using an SMTP method and the second one is a transactional email service. Let’s take a closer look at the two and unpack how they work. 

Send emails with Python and SMTP

The first good news about Python is that in its standard library there is a built-in smtplib module that is used for sending emails via SMTP connection. The module uses the standard RFC 821 protocol, so no extra installations or tricks are required. 

Send emails with Python using Transactional Email Services

If you decide to set up the sending functionality in your Python app without built-in SMTP, you can integrate third-party transactional email API. 

One of the main reasons to go this route is if a strong deliverability rate is a critical factor for you. Most third-party API services have analytical tools and provide scalability opportunities you might need as your project grows. 

Sending emails in Python: A Step-by-Step Guide

How to send emails using SMTP

The built-in smtplib module can be imported using the following statement:

import smtplib

To send an email later, create one SMTP object:

smtpObj = smtplib.SMTP( [host [, port]] )

Parameter details: 

  • host − this is an optional argument and is the host running your SMTP server. The IP address of the host or a domain name can be specified. 
  • port − if the host argument is specified, specify a port, where the SMTP server is listening. 
  • local_hostname − if the used SMTP server is running on your local machine, specify localhost

An SMTP object has an instance method called sendmail that is used to send a message and has three parameters:

  • sender − string with the address of the sender.
  • receivers − list of strings, one for each recipient.
  • message − a message as a string formatted as specified in RFCs.

smtpObj.sendmail(sender, receivers, message)

To make sure that the email module has been imported properly and get the full description of its classes and arguments, type in an interactive Python session:

help(smtplib)

Refer to Python documentation to further review the rest of the SMTP Objects (e.g., smtp.ehlo; smtp.send_message(msg) etc.) and how to apply them.

Below you can find an example of a simple Python script that shows how one can send an email from a local SMTP. 

import smtplib

sender = 'from@example.com'
receivers = ['to@example.com']
message = """From: From Person <from@example.com>
To: To Person <to@example.com>
Subject: SMTP email example


This is a test message.
"""

try:
    smtpObj = smtplib.SMTP('localhost')
    smtpObj.sendmail(sender, receivers, message)         
    print("Successfully sent email")
except SMTPException:
    pass

However, this code wouldn’t work.

Firstly, you’ll need an actual working SMTP server. Secondly, most recipients reject emails from untrusted sources. On top of that, getting all the relevant validations and certifications for other servers to approve your emails is not easy.

Using a third-party API for sending functionality in your Python app is the best way to go about it. One like Mailtrap Email Sending, where the tedious work of ensuring your emails get delivered is already done, and there is no need to set up your own SMTP server. 

How to send emails using Email Sending

With the help of actionable analytics, our Email Sending helps devs gain full control over their email deliverability, allowing a sending throughput of roughly 10,000 emails a second.

Before you’re able to send out any type of email with Mailtrap, you’ll need to connect and verify your domain name. Check out our detailed guide and a bonus video instruction on how to do this.

Once that’s done, go back to your Python app and install Mailtrap’s official Python client with the following command: 

pip install mailtrap

Note: make sure the package version is 2.0.0. or higher. 

Then you should create the mail object and fill in variables (such as email and name) with your Mailtrap credentials. Keep in mind that you should indicate the address with the verified sending domain in it. 

import mailtrap as mt

# create mail object
mail = mt.Mail(
    sender=mt.Address(email="mailtrap@example.com", name="Mailtrap Test"),
    to=[mt.Address(email="your@email.com")],
    subject="You are awesome!",
    text="Congrats for sending test email with Mailtrap!",
)

To send a simple email, you’ll also need to create the client with your API token. You’ll find it in your Mailtrap account by expanding ‘Settings’ dropdown menu in the left navigation panel and choosing ‘API Tokens’ tab. You can copy the credentials by pressing ‘Copy’ next to your token. 

Copying Mailtrap Email Sending API token

After fetching your token, use the following script to send an email: 

# create client and send
client = mt.MailtrapClient(token="your-api-key")
client.send(mail)

Here’s a full sample code to send more complex emails (with Bcc and Cc recipients, HTML body, attachments, etc): 

import base64
from pathlib import Path

import mailtrap as mt

welcome_image = Path(__file__).parent.joinpath("welcome.png").read_bytes()

mail = mt.Mail(
    sender=mt.Address(email="mailtrap@example.com", name="Mailtrap Test"),
    to=[mt.Address(email="your@email.com", name="Your name")],
    cc=[mt.Address(email="cc@email.com", name="Copy to")],
    bcc=[mt.Address(email="bcc@email.com", name="Hidden Recipient")],
    subject="You are awesome!",
    text="Congrats for sending test email with Mailtrap!",
    html="""
    <!doctype html>
    <html>
      <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      </head>
      <body style="font-family: sans-serif;">
        <div style="display: block; margin: auto; max-width: 600px;" class="main">
          <h1 style="font-size: 18px; font-weight: bold; margin-top: 20px">
            Congrats for sending test email with Mailtrap!
          </h1>
          <p>Inspect it using the tabs you see above and learn how this email can be improved.</p>
          <img alt="Inspect with Tabs" src="cid:welcome.png" style="width: 100%;">
          <p>Now send your email using our fake SMTP server and integration of your choice!</p>
          <p>Good luck! Hope it works.</p>
        </div>
        <!-- Example of invalid for email html/css, will be detected by Mailtrap: -->
        <style>
          .main { background-color: white; }
          a:hover { border-left-width: 1em; min-height: 2em; }
        </style>
      </body>
    </html>
    """,
    category="Test",
    attachments=[
        mt.Attachment(
            content=base64.b64encode(welcome_image),
            filename="welcome.png",
            disposition=mt.Disposition.INLINE,
            mimetype="image/png",
            content_id="welcome.png",
        )
    ],
    headers={"X-MT-Header": "Custom header"},
    custom_variables={"year": 2023},
)

client = mt.MailtrapClient(token="your-api-key")
client.send(mail)

Try Mailtrap for Free

How to send HTML emails in Python?

As mentioned above, you can easily send HTML emails with Mailtrap’s Python client. However, other packages, such as the Python email package also support HTML emails.  

We will deal with the MIME message type, which can combine HTML/CSS and plain text. In Python, it is handled by the email.mime module. 

It is better to write a text version and an HTML version separately and then merge them with the MIMEMultipart("alternative") instance. It means that such a message has two rendering options accordingly. In case an HTML isn’t rendered successfully for some reason, a text version will still be available. 

Input:

# import the necessary components first
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

port = 587 
smtp_server = "live.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 text/plain part
text = """\
Hi,
Check out the new post on the Mailtrap blog:
SMTP Server for Testing: Cloud-based or Local?
https://blog.mailtrap.io/2018/09/27/cloud-or-local-smtp-server/
Feel free to let us know what content would be useful for you!"""

# write the HTML part
html = """\
<html>
  <body>
    <p>Hi,<br>
       Check out the new post on the Mailtrap blog:</p>
    <p><a href="https://blog.mailtrap.io/2018/09/27/cloud-or-local-smtp-server">SMTP Server for Testing: Cloud-based or Local?</a></p>
    <p> Feel free to <strong>let us</strong> know what content would be useful for you!</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("live.smtp.mailtrap.io", 587) as server:
    server.login(login, password)
    server.sendmail(
        sender_email, receiver_email, message.as_string()
    )

print('Sent')

Output:

Multipart test output in Python

How to send emails with attachments?

The next step in mastering sending emails with Python is attaching files. Attachments are still the MIME objects, but we need to encode them with the base64 module that encodes all binary data into ASCII characters.

A couple of important points about the attachments:

  1. Python lets you attach standard .txt, rich text format, and all other main text files, images, audio files, and even applications. You just need to use the appropriate email class like:

    email.mime.audio.MIMEAudio or email.mime.image.MIMEImage
  2. For the full information, refer to this section of the Python documentation. Also, you can check the examples provided by Python for a better understanding. 
  3. Remember about the file size: sending files over 20MB is a bad practice. 

In transactional emails, PDF files are the most frequently used: we usually get receipts, tickets, boarding passes, order confirmations, and many other authentication-related documents. So let’s review how to send a boarding pass as a PDF file. 

Input:

import smtplib

# import the corresponding modules
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

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

subject = "An example of boarding pass"
sender_email = "mailtrap@example.com"
receiver_email = "new@example.com"

message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = subject

# Add body to email
body = "This is an example of how you can send a boarding pass in attachment with Python"
message.attach(MIMEText(body, "plain"))

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

# We assume that the file is in the 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()

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

Output:

The result of sending email with attachment in Python

To attach several files, you can call the message.attach() method several times.

How to send emails to multiple recipients using Python?

Sending multiple emails to different recipients and making them personal is the special thing about emails in Python. 

To add several more recipients, you can just type their addresses separated by a comma and add CC and BCC. But if you work with bulk email sending, Python will save you with loops. 

One of the options is to create a database in a .csv format (we assume it is saved to the same folder as your Python script). 

We often see our names in transactional or even promotional examples. Here is how we can make it with Python.

Let’s organize the list in a simple table with just two columns: name and email address. It should look like the following example:

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


The code below will open the file and loop over its rows line by line, replacing the {name} with the value from the “name” column.

Input:

import csv, smtplib

port = 587 
smtp_server = "live.smtp.mailtrap.io"
login = "1a2b3c4d5e6f7g" # paste your login generated by Mailtrap
password = "1a2b3c4d5e6f7g" # paste your password generated by Mailtrap
sender = "new@example.com"
message = """Subject: Order confirmation
To: {recipient}
From: {sender}

Hi {name}, thanks for your order! We are processing it now and will contact you soon"""

with smtplib.SMTP("live.smtp.mailtrap.io", 587) 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}')

After running the script, we get the following response:

Sent to John Johnson
Sent to Peter Peterson
>>> 


Output:

In our Mailtrap inbox, we see two messages: one for John Johnson and another for Peter Peterson, delivered simultaneously:

Email sent to multiple recipients

How to send emails with images? 

Images, even if they are a part of the email message body, are still attachments as well. There are three types of them:

  • CID attachments (embedded as a MIME object) 
  • Base64 images (inline embedding)
  • Linked images 

We have described their peculiarities, pros and cons, and compatibility with most email clients in this post

To add a CID attachment, we will create a MIME multipart message with MIMEImage component:

# import all necessary components
import smtplib
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart

port = 587
smtp_server = "live.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"] = "CID image test"
message["From"] = sender_email
message["To"] = receiver_email

# write the HTML part
html = """\
<html>
 <body>
   <img src="cid:Mailtrapimage">
 </body>
</html>
"""

part = MIMEText(html, "html")
message.attach(part)

# We assume that the image file is in the same directory that you run your Python script from
fp = open('mailtrap.jpg', 'rb')
image = MIMEImage(fp.read())
fp.close()

# Specify the  ID according to the img src in the HTML part
image.add_header('Content-ID', '<Mailtrapimage>')
message.attach(image)

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

Output:

CID image test output in Python

The CID image is shown as part of the HTML message and as an attachment. Messages with this image type are often considered spam: check the Analytics tab in Mailtrap to see the spam rate and recommendations for improvement. In most cases, many email clients  – Gmail in particular – don’t display CID images. So let’s review how to embed a base64 encoded image.

Here we will use base64 module and experiment with the same image file:

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

port = 587
smtp_server = "live.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"] = "inline embedding"
message["From"] = sender_email
message["To"] = receiver_email

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

html = f"""\
<html>
 <body>
   <img src="data:image/jpg;base64,{encoded}">
 </body>
</html>
"""

part = MIMEText(html, "html")
message.attach(part)

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

Output:

How inline embedding would look in Python

Now the image is embedded into the HTML message and is not available as an attached file. Python has encoded our jpg image, and if we go to the HTML Source tab, we will see the long image data string in the img src

Sending emails with Python via Gmail

You can configure your production server when ready to send your custom emails to a real recipient’s email address. It also depends on your needs, goals, and preferences: your local host or any external SMTP. 

One of the most popular options is Gmail so let’s take a closer look at it. 

We often see titles like “How to set up a Gmail account for development”. It means you will create a new Google account and use it for a particular purpose. 

To be able to send emails via your Gmail account, you need to provide access to it for your application. You take advantage of the OAuth2 authorization protocol. It’s way more difficult but recommended due to security reasons. 

Further, to use Gmail’s SMTP server, you need to know:

  • the server name = smtp.gmail.com
  • port = 465 for SSL/TLS connection (preferred)
  • or port = 587 for STARTTLS connection
  • username = your Gmail email address
  • password = your password
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("my@gmail.com", password)

If you tend to simplicity, then you can use Yagmail, the dedicated Gmail/SMTP. It makes email sending really easy. Just compare the above examples with these several lines of code:

import yagmail

yag = yagmail.SMTP()
contents = [
    "This is the body, and here is just text http://somedomain/image.png",
    "You can find an audio file attached.", '/local/path/to/song.mp3'
]
yag.send('to@someone.com', 'subject', contents)

Important Note: Google stopped supporting “Allow less secure apps” feature in 2022 so you won’t be able to run the integration that way. Take the time to set up OAuth2.

Test emails with Mailtrap Email Testing

When creating a new app or adding any functionality, especially when doing it for the first time, it’s essential to experiment on a test server. Here is a brief list of reasons:

  1. You won’t hit your friends’ and customers’ inboxes. This is vital when you test bulk email sending or work with an email database.
  2. You won’t flood your inbox with testing emails. 
  3. Your domain won’t be blacklisted for spam.

A testing SMTP server environment imitates the work of a real 3rd party web server. In the following examples, we’ll use Mailtrap Email Testing, which allows devs to capture SMTP traffic from staging and inspect and debug emails before they go out to actual recipients.

On top of that, Email Testing can help validate your HTML/CSS, analyze the email’s content, and give a relevant spam score. The tool is easy to set up; all you need is to copy the credentials generated by the app and paste them into your code. 

Mailtrap Email Testing SMTP credentials

Here is how it looks in practice:

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

Mailtrap makes things even easier. Go to the Integrations section in the SMTP settings tab and get the ready-to-use template of the simple text message with your Mailtrap credentials. The most basic option for instructing your Python code on who sends what to who is the sendmail() instance method:

Mailtrap Email Testing smtplib integration

The code snippet looks pretty straightforward, right? Let’s take a closer look at it and add some error handling (see the #explanations in between). To catch errors, we use the “try” and “except” blocks. Refer to the documentation for the list of exceptions here. 

# the first step is always the same: import all necessary components:
import smtplib
from socket import gaierror

# now you can play with your code. Let’s define the SMTP server separately here:
port = 2525 
smtp_server = "sandbox.smtp.mailtrap.io"
login = "1a2b3c4d5e6f7g" # paste your login generated by Mailtrap
password = "1a2b3c4d5e6f7g" # paste your password generated by Mailtrap
# specify the sender’s and receiver’s email addresses
sender = "from@example.com"
receiver = "mailtrap@example.com"
# type your message: use two newlines (\n) to separate the subject from the message body, and use 'f' to  automatically insert variables in the text
message = f"""\
Subject: Hi Mailtrap
To: {receiver}
From: {sender}

This is my first message with Python."""

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

Once you get the Sent result in Shell, you should see your message in your Mailtrap inbox:

Test email in Mailtrap Email Testing virtual inbox

If you prefer working in the local environment, the local SMTP debugging server might be an option. 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.

Set your SMTP server to localhost:1025

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

In order to run SMTP email server on port number 25, you’ll need root permissions:

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

It will help you verify whether your code is working and point out the possible problems if there are any. However, it won’t allow you to check how your HTML email template is rendered.

Next steps with emails in Python

We have demonstrated just basic options of sending emails with Python to describe the logic and a range of its capabilities. We recommend reviewing the Python documentation and experimenting with your own code to get great results!

There are a bunch of various Python frameworks and libraries which make creating apps more elegant and dedicated. In particular, some of them can help improve your experience with building email sending functionality:

The most popular frameworks are:

  1. Flask, which offers a simple interface for email sending— Flask Mail. Feel free to learn more in our guide on how to send emails with Flask.
  2. Django can be a great option for building HTML templates. Also, take a look at our Django email sending tutorial.
  3. Zope comes in handy for website development. 
  4. Marrow Mailer is a dedicated mail delivery framework adding various helpful configurations.
  5. Plotly and its Dash can help with mailing graphs and reports.

Good luck, and don’t forget to stay on the safe side when sending your emails!

Article by Aleksandr Varnin Full Stack Developer @ Railsware

Comments

1 replies

Chris

You can send emails with attachments using Apprise. It supports Google, Hotmail, Proton Mail, and even custom severs right out of the box. Best of all, it’s free; and open source. See https://github.com/caronc/apprise

Comments are closed.