How to Send Emails in Python with Gmail SMTP and API

On June 04, 2024
13min read
Denys Kontorskyy Technical Content Writer @Mailtrap
Veljko Ristić Content Manager @ Mailtrap
How to send emails in Python with Gmail

In this tutorial, using code examples, we’ll cover how to use different modules in Python to construct and send various types of email messages, review existing authentication methods, and more. As an example, we’ll be using both the Gmail SMTP server and API.

If you’d like to find out about other methods of sending emails in Python, check out our comprehensive guide.

Setting up Python smtplib and email libraries

To send an email with Python via Gmail SMTP, you must use the smtplib module and the email module. The smtplib essentially provides functions for connecting and sending emails using a Simple Mail Transfer Protocol (SMTP) server.

As for the email module, it provides classes for constructing and parsing email messages (To; From; Subject; etc.)  and allows encoding and decoding emails with the MIME (Multipurpose Internet Mail Extensions) standard.

Let’s break down the actual steps that need to be done. And, in the following section, you can check the actual code.

  1. Import the required libraries: smtplib and MIMEText, from the email.
  2. Create the email message using the MIMEText object, and set the desired values in the ‘Subject’, ‘Sender’, and ‘Recipients‘ fields.
  3. Establish the SMTP connection to Gmail’s server over a secure SSL connection using the SMTP_SSL function.
  4. Call the sendmail method of the SMTP object, and specify the sender’s and recipient’s email addresses and the email message as arguments.

With that out of the way, you’re ready to send emails via Gmail in Python. I’ll be covering the methodology, examples and other use cases in the following sections. In case you need to brush up on your skills, check out these Python courses online beforehand.

Send email in Python using Gmail SMTP

Based on what I covered in the previous section, here’s a simple script to send emails:

import smtplib
from email.mime.text import MIMEText

subject = "Email Subject"
body = "This is the body of 
the text message"
sender = "sender@gmail.com"
recipients = ["recipient1@gmail.com", "recipient2@gmail.com"]
password = "password"


def send_email(subject, body, sender, recipients, password):
    msg = MIMEText(body)
    msg['Subject'] = subject
    msg['From'] = sender
    msg['To'] = ', '.join(recipients)
    with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp_server:
       smtp_server.login(sender, password)
       smtp_server.sendmail(sender, recipients, msg.as_string())
    print("Message sent!")


send_email(subject, body, sender, recipients, password)

Important Notes:

Prior to May 30, 2022, it was possible to connect to Gmail’s SMTP server using your regular Gmail password if “2-step verification” was activated. For a higher security standard, Google now requires you to use an “App Password”.

This is a 16-digit passcode that is generated in your Google account and allows less secure apps or devices that don’t support 2-step verification to sign in to your Gmail Account.

Alternatively, there is OAuth 2.0 Gmail authentication mode, but in this case, you’ll need to use the Gmail API sending method in your Python script. 

Send email to multiple recipients

To send the email to multiple recipients, I’ll define a list of email addresses to allow sending to multiple recipients. Also, the ‘To’ field sets the recipients under the message headers. It’s converted from a list to a string separated by commas, which is a format accepted by most email headers.

Here’s the code:

import smtplib
from email.mime.text import MIMEText

# Define the subject and body of the email.
subject = "Email Subject"
body = "This is the body of the text message"
# Define the sender's email address.
sender = "sender@gmail.com"
# List of recipients to whom the email will be sent.
recipients = ["recipient1@gmail.com", "recipient2@gmail.com"]
# Password for the sender's email account.
password = "password"

def send_email(subject, body, sender, recipients, password):
    # Create a MIMEText object with the body of the email.
    msg = MIMEText(body)
    # Set the subject of the email.
    msg['Subject'] = subject
    # Set the sender's email.
    msg['From'] = sender
    # Join the list of recipients into a single string separated by commas.
    msg['To'] = ', '.join(recipients)
   
    # Connect to Gmail's SMTP server using SSL.
    with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp_server:
        # Login to the SMTP server using the sender's credentials.
        smtp_server.login(sender, password)
        # Send the email. The sendmail function requires the sender's email, the list of recipients, and the email message as a string.
        smtp_server.sendmail(sender, recipients, msg.as_string())
    # Print a message to console after successfully sending the email.
    print("Message sent!")

# Call the function to send the email.
send_email(subject, body, sender, recipients, password)

Send email with attachments

You need to import the necessary libraries. In this case, you’ll also need the MIMEBase that allows you to represent an attachment file and encoders, which is used to encode the attachment in base64 format.

Here’s the complete script including comments that will help you understand how to send an email with attachments: 

import smtplib
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

sender_email = "sender@gmail.com"
sender_password = "password"
recipient_email = "recipient@gmail.com"
subject = "Hello from Python"
body = "with attachment"


with open("attachment.txt", "rb") as attachment:
    # Add the attachment to the message
    part = MIMEBase("application", "octet-stream")
    part.set_payload(attachment.read())
encoders.encode_base64(part)
part.add_header(
    "Content-Disposition",
    f"attachment; filename= 'attachment.txt'",
)

message = MIMEMultipart()
message['Subject'] = subject
message['From'] = sender_email
message['To'] = recipient_email
html_part = MIMEText(body)
message.attach(html_part)
message.attach(part)

with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
   server.login(sender_email, sender_password)
   server.sendmail(sender_email, recipient_email, message.as_string())

Send HTML email

Just like you would with a regular email message, to send an HTML email using the Gmail SMTP server in Python, use the MIMEText class from the email package. After that, use html_message object instead of the plain text msg object and paste the HTML content into the body of the email.

Here is a full code example: 

import smtplib
from email.mime.text import MIMEText

sender_email = "sender@gmail.com"
sender_password = "password"
recipient_email = "recipient@gmail.com"
subject = "Hello from Python"
body = """
<html>
  <body>
    <p>This is an <b>HTML</b> email sent from Python using the Gmail SMTP server.</p>
  </body>
</html>
"""
html_message = MIMEText(body, 'html')
html_message['Subject'] = subject
html_message['From'] = sender_email
html_message['To'] = recipient_email
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
   server.login(sender_email, sender_password)
   server.sendmail(sender_email, recipient_email, html_message.as_string())

If you would like to send an email using an HTML template instead of having the HTML in the body, you can use the Template class from the jinja2 package, a fast, expressive, and extensible templating engine.

pip install jinja2

Here is the script for all of the steps needed: 

import smtplib
from email.mime.text import MIMEText
from jinja2 import Template

sender_email = "sender@gmail.com"
sender_password = "password"
recipient_email = "recipient@gmail.com"
with open('template.html', 'r') as f:
    template = Template(f.read())
context = {
    'subject': 'Hello from Python',
    'body': 'This is an email sent from Python using an HTML template and the Gmail SMTP server.'
}
html = template.render(context)
html_message = MIMEText(context['body'], 'html')
html_message['Subject'] = context['subject']
html_message['From'] = sender_email
html_message['To'] = recipient_email
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
   server.login(sender_email, sender_password)
   server.sendmail(sender_email, recipient_email, html_message.as_string())

If you’d like to learn more about sending dynamic content from your Python application, we recommend checking out our article about Mail Merge

Send bulk email

I’ll show you how to send bulk email via Gmail, but keep in mind that this is just for shows, not a method you should run on production. 

Gmail has a pretty stringent policies about sending bulk emails and campaigns, I talk about it in a later section (jump by clicking the link). Also, your deliverability might suffer, particularly with a new domain or a relatively low domain rating. Or if you just try to send from an @gmail.com address. 

With that in mind, the script below is tailored for scenarios where each recipient should receive a separate, individual email, which is common in personalized marketing campaigns, notifications, etc. 

Simply, the same message gets sent to multiple people without grouping them together in a single email. This method also avoids disclosing the list of recipients to each other, preserving privacy.

Check the script below. 

import smtplib
from email.mime.text import MIMEText

# Email content
subject = "Email Subject"
body = "This is the body of the text message"
sender = "sender@gmail.com"
recipients = ["recipient1@gmail.com", "recipient2@gmail.com", "recipient3@gmail.com"]  # List of recipients
password = "password"

def send_bulk_emails(subject, body, sender, recipients, password):
    # Connect to Gmail's SMTP server using SSL
    with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp_server:
        smtp_server.login(sender, password)  # Log in to the server

        # Loop over each recipient and send them an individual email
        for recipient in recipients:
            msg = MIMEText(body)  # Create a MIMEText object with the email body
            msg['Subject'] = subject  # Set the email's subject
            msg['From'] = sender  # Set the sender
            msg['To'] = recipient  # Set the current recipient

            smtp_server.sendmail(sender, recipient, msg.as_string())  # Send the email
            print(f"Message sent to {recipient}!")

# Call the function to send bulk emails
send_bulk_emails(subject, body, sender, recipients, password)

Now, I’d like to give you a quick breakdown to explore how it all works:

  1. On initialization, the script defines the subject, body, sender’s email address, recipients list, and password.
  2. With send_bulk_emails, the script makes a secure connection to Gmail’s SMTP server on port 465 via smtplib.SMTP_SSL. After establishing a connection, the script logs in to the SMTP server using the sender’s Gmail credentials.
  3. Then, the script loops through the recipients, doing the following for each one.
  • A MIMEText object is created for the email body. 
  • The email headers (Subject, From, To) are set. To is set individually for each recipient to ensure each email is personalized.
  • The email is sent to the current recipient. The sendmail method takes the sender, the current recipient, and the email content as arguments.

The upside of this method is that each message is logged or handled separately. This helps track success of each sent email more efficiently, which is critical for bulk operations.  

Send email in Python using Gmail API

The Gmail API is a RESTful API that allows your application to send and receive emails using Google’s mail servers. On top of that, it gives you the ability to retrieve and manage messages and use Gmail features such as labels, threads, etc. 

Let’s analyze each step of sending an email with Python via Gmail API using OAuth2 authentication. 

  1. Set up a Google Cloud Platform project, click on the hamburger menu, and select view all products. Under the management section, select APIs and services
  1. Next, select Library and type “Gmail API” in the search bar, and click on the Gmail API card.
  1. Finally, select the enable the Gmail API button. 
  1. Now, you’ll need to download the client secrets file for your project. Start by selecting “Create Credentials”. In this section, select the Gmail API as your preferred API and user data as the type of data you will be accessing.
  1. To get the OAuth client ID, select your application type as a Desktop App, set the application name, and select create. Next, download and store the credentials in your local file system.

After downloading the secret file, you should have the file in this format:

{
    "installed": {
        "client_id": "463220703866-un8ijck75igunsbh4nhclm74edprhj5p.apps.googleusercontent.com",
        "project_id": "geocaching-366015",
        "auth_uri": "https://accounts.google.com/o/oauth2/auth",
        "token_uri": "https://oauth2.googleapis.com/token",
        "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
        "client_secret": "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",
        "redirect_uris": [
            "http://localhost"
        ]
    }
}

Before beginning with the code, you will need to install the google-auth, google-auth-oauthlib, google-auth-httplib2, and google-api-python-client libraries by using the command:

pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client

After successfully installling the libraries, you will create a variable called SCOPES which will hold a list of strings that specifies the permissions that an app has when it accesses a user’s data.

At this point, create another variable called flow which will use the InstalledAppFlow class to create an OAuth 2.0 flow for an installed app and specifies the OAuth 2.0 scopes that the app needs in the SCOPES variable.

The code then starts the OAuth 2.0 flow by calling the run_local_server method and specifying a port number, which starts a local web server to handle the authorization flow and return the OAuth 2.0 credentials when the flow is complete.

Next, build the Gmail service by calling the build function from the googleapiclient.discovery module, passing ‘gmail’ in the service name and ‘v1’ version as arguments, and taking the credentials you called earlier.

Now construct the email message by creating a MIMEText object, and setting the ‘to’ and ‘subject’ fields to the desired values. Additionally, encode the email message as a base64-encoded string. 

Finally, you can send the email by calling the send method of the messages resource and passing it in the create_message dictionary as the request body.

This is what the Python code looks like when all these steps are put together:

import base64
from email.mime.text import MIMEText
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from requests import HTTPError
SCOPES = [
        "https://www.googleapis.com/auth/gmail.send"
    ]
flow = InstalledAppFlow.from_client_secrets_file(
            'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
service = build('gmail', 'v1', credentials=creds)
message = MIMEText('This is the body of the email')
message['to'] = 'recipient@gmail.com'
message['subject'] = 'Email Subject'
create_message = {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode()}
try:
    message = (service.users().messages().send(userId="me", body=create_message).execute())
    print(F'sent message to {message} Message Id: {message["id"]}')
except HTTPError as error:
    print(F'An error occurred: {error}')
    message = None

That’s it! Just like that, with a few lines of code, you can add a quick email-sending functionality to the app you’re building, and of course, don’t forget to validate email addresses gathered from your users.

Gmail SMTP and API limitations

Using the Gmail API or SMTP is a convenient and reliable option for the automation of your email sending, but, as mentioned, it has some limitations.

  • Daily send limit and maximum email size limit 

To ensure a single user or application does not consume too many resources, the API limits the number of emails you can send daily and has a maximum email size limit. For accurate numbers regarding these limitations, check out Google’s documentation.

  • Limited support for features

The API may not support all features available through the Gmail web interface, such as the ability to schedule emails to be sent at a later time or to use certain types of formatting or attachments.

  • OAuth scopes control access to user data 

When you authenticate your application, you must request the appropriate OAuth scopes to access the data you need. The user must then grant your application access to their data, adhering to the principle of least privilege and potentially utilizing just in time permissions for enhanced security. Without the appropriate OAuth scopes, your requests to access user data will be denied and result in an error.

  • Quota and rate limits

The Gmail API is subject to a daily usage limit, which applies to all requests made from your application, and per-user rate limits. Each limit is identified in terms of quota units, or an abstract unit of measurement representing Gmail resource usage. 

For instance, sending a message consumes 100 quota units, while most other actions like fetching a message or creating a draft consume far fewer quota units.

  • Sending limits

Gmail’s SMTP access has stringent limits to prevent abuse and ensure service availability. Typically, these limits are lower than those of the Gmail API, often restricting users to sending fewer emails per day.

  • Attachment size

Like the API, the SMTP service limits the size of email attachments. The combined email size (body and total attachments) must not exceed 25 MB.

  • Connection throttling

If too many connections or authentication attempts are made in a short period, Gmail may throttle your connection, temporarily limiting your ability to send more emails. This is a part of Gmail’s effort to identify and prevent potential spam or abusive behavior through SMTP.

Is there an alternative to Gmail email infrastructure?

The shorter answer is yes. 

There are plenty of alternatives to using Gmail’s SMTP server to send emails. You can use any other email provider of your choice. It’s important to ensure that their server infrastructure meets your requirements and needs. 

Another option is to use a third-party email service provider with an API that allows sending emails from your own application using their servers. Additionally, before implementing email sending functionality in your app, it’s important to test it. 

This is where the Mailtrap Email Delivery Platform comes in handy.

The platform offers Email API/SMTP Service, which is used to deliver emails while giving you more control over your email infrastructure, and Email Sandbox, a tool that allows testing and debugging those emails before they are sent.

If you’d like to use Mailtrap Email API with your Python app, after verifying your domain name, go to the Sending Domains section, and select the API and SMTP tab. From the drop-down menu, select Python and copy and paste the generated code with your credentials into your script.

Tip: Check the official GitHub docs on Mailtrap Python SDK.

import requests

url = "https://send.api.mailtrap.io/api/send"

payload = "{\"from\":{\"email\":\"mailtrap@mailtrap.club\",\"name\":\"Mailtrap Test\"},\"to\":[{\"email\":\"example@railsware.com\"}],\"subject\":\"You are awesome!\",\"text\":\"Congrats for sending test email with Mailtrap!\",\"category\":\"Integration Test\"}"
headers = {
  "Authorization": "Bearer 48d3783bde8152*******************",
  "Content-Type": "application/json"
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)

You can also select the SMTP method and copy the relevant configurations from the app. Just make sure you choose the correct sending stream – Transactional or Bulk, depending on the type of emails you’d like to send.  

Here’s an example:

import smtplib
from email.mime.text import MIMEText

# Define your email content and credentials
subject = "Email Subject"
body = "This is the body of the email."
sender_email = "sender@gmail.com"
receiver_email = "receiver@gmail.com"
password = "your_password"  # Be cautious with passwords in code!

# Create MIMEText object
message = MIMEText(body)
message['Subject'] = subject
message['From'] = sender_email
message['To'] = receiver_email

# Connect to Gmail's SMTP server
with smtplib.SMTP('live.smtp.mailtrap.io', 587) as server:  # Note the use of port 587 for STARTTLS
    server.ehlo()  # Can be called optionally (It is called automatically after a connect)
    server.starttls()  # Secure the connection
    server.ehlo()  # Can be called optionally (It reidentifies us to the server post-STARTTLS)
    server.login(sender_email, password)
    server.send_message(message)  # Send the email

    print("Email sent successfully!")

Notes: 

  • The starttls() method is called to initiate the TLS upgrade. After this call, the connection is encrypted.
  • The example features a simple message body, you can upgrade it with HTML or CSS styles as you deem fit. 

Test emails and email sending on staging

Testing the effectiveness of your emails is crucial to ensure they are delivered successfully and adequately formatted. 

An email sandbox offers a range of features to help you do just that. By capturing SMTP traffic, it allows you to analyze email content for a spam score, validate HTML/CSS, review raw data, and much more, thus ensuring that your emails are properly formatted and delivered successfully.

Here I’ll use Mailtrap Email Testing to show you how to leverage a sandbox via SMTP or API. 

Mailtrap Email Testing HTML Check dashboard

SMTP

Mailtrap Email Testing can easily be integrated with your Python app in just a few clicks. All there is to do is to copy the SMTP credentials generated by the app and paste them into your code.

Mailtrap Email Testing SMTP Settings

Then choose one available Python code samples under Integrations and run it to see the test in action. Of course, our default code example can be adapted to your needs. 

Mailtrap Email Testing Integration example

API

If you’d like to automate QA testing flows and have more control over testing, Maitrap Email Testing API is the way to go. 

Here, I’ll show you how to set it all up, of course, assuming you’re already user. And you can check all the details at our official API docs

import http.client
import json

def test_send_email():
    conn = http.client.HTTPSConnection("sandbox.api.mailtrap.io")
   
    payload = {
        "to": [{"email": "john_doe@example.com", "name": "John Doe"}],
        "cc": [{"email": "jane_doe@example.com", "name": "Jane Doe"}],
        "bcc": [{"email": "james_doe@example.com", "name": "Jim Doe"}],
        "from": {"email": "sales@example.com", "name": "Example Sales Team"},
        "attachments": [
            {
                "content": "base64_encoded_content_here",
                "filename": "index.html",
                "type": "text/html",
                "disposition": "attachment"
            }
        ],
        "custom_variables": {"user_id": "45982", "batch_id": "PSJ-12"},
        "headers": {"X-Message-Source": "dev.mydomain.com"},
        "subject": "Your Example Order Confirmation",
        "text": "Congratulations on your order no. 1234",
        "category": "API Test"
    }

    headers = {
        'Content-Type': "application/json",
        'Accept': "application/json",
        'Api-Token': "your_api_token_here"  # Replace with your real API token
    }

    # Convert the payload to a JSON string
    json_payload = json.dumps(payload)

    # Make the POST request
    conn.request("POST", "/api/send/inbox_id", json_payload, headers)  # Replace 'inbox_id' with your real inbox ID

    # Get the response
    response = conn.getresponse()
    data = response.read()

    print(data.decode("utf-8"))

if __name__ == "__main__":
    test_send_email()

The script above connects to the Mailtrap Email TestingAPI via http.client.HTTPSConnection. The email content including subject, text content, recipients, etc is defined as JSON payload. And the POST request is made with the payload you set. 

Pro Tips:

  • If you want to add attachments, encode the content in Base 64 and include it in the content field. 
  • Under the Api-Token header, include your actual Mailtrap API token, which can be found under Settings > API Tokens
  • The post request is made to the /api/send/inbox_id. Make sure to replace the inbox_id with your actual Inbox ID, which can be found in the inbox URL. 

Wrapping up

We hope this helps you in developing email sending functionality in your app! Just remember, before you start building it, make sure to research what Python framework to go with, as each one has its own set of features and benefits:

Article by Denys Kontorskyy Technical Content Writer @Mailtrap

I am an experienced Technical Content Writer offering insights on email infrastructure, sending, testing, and optimizing emails. With a strong passion for product marketing, I love creating content that educates audiences and provides practical, applicable knowledge.

Article by Veljko Ristić Content Manager @ Mailtrap

Linguist by trade, digital marketer at heart, I’m a Content Manager who’s been in the online space for 10+ years. From ads to e-books, I’ve covered it all as a writer, editor, project manager, and everything in between. Now, my passion is with email infrastructure with a strong focus on technical content and the cutting-edge in programming logic and flows. But I still like spreading my gospels while blogging purely about marketing.