How to create a mail merge template with Python and Gmail

How to create a mail merge template with Python and Gmail

8 min read

Mail Merge uses a template to send similar emails to multiple recipients while keeping the content personalized.

Why is this important? Well, you can simply copy and paste and replace the content and then send the emails, but what happens when you have 300 recipients? Copy and paste don’t seem so fun anymore. Using a mail merge template with Python is the way to go for high-volume sends.

If you want to learn more, follow our post What is Mail Merge?.

Looking for another SDK? Here are the Ruby, Java and Node versions.

We will use Gmail as our email service provider as setting up is fast and easy.

With the Nylas Email API, we can easily support mail merge. Here’s how:

Is your system ready?

If you already have the Nylas Python SDK installed and your Python environment configured, continue with the blog.

Otherwise, I recommend reading the post How to Send Emails with the Nylas Python SDK where the basic setup is clearly explained.

What are we going to talk about?

What our application will look like

Before we jump into the code, let’s see how our application works. However, for that, we need an essential step, which is creating a .csv file containing all the emails and information we will use.

The file will look like this, and you can choose how you name it:

AlvaroTejada Galindo12345742 Evergreen Terracexxx@gmail.comDeadpool Action Figure
BlagTejada Galindo56789430 Spalding Wayxxx@nylas.comAFI Poster
Alvaro aka “Blag”Tejada Galindo464511024 Cherry Streetxxx@gmail.comTomb Raider Game

Keep in mind that you can add whatever you like here or you can even delete columns. The only ones that are “enforced” are Name and Email.

This is how our application will look:

Mail Merge Template application

Here, we can fill in the subject, and the body and select the .csv file that we had created.

If we don’t specify all the fields, we will get an error message.

You must specify all fields

The interesting part here is how we define the subject and the body.

Filling out our Mail Merge Template

Here, {Name}, {Account}, {Address} and {Gift} will be replaced by the values stored on the .csv file. So each recipient will get a personalized email. Remember that the values used between brackets need to match the names in the .csv file.

Let’s click Submit and see what happens.

Emails sent confirmation

We have successfully sent emails to all recipients. Let’s check their inbox.

User 1 receive the email
User 2 receive the email
User 3 receive the email

As we can see, all recipients received a personalized email, as all the fields were replaced accordingly.

Installing the Flask package

As we want to create a Flask web application, our best option is to use Flask, one of the most popular Micro Frameworks in the Python world:

$ pip3 install Flask
$ pip3 install Flask-session

Once installed, we’re ready to go:

Creating the Mail Merge with Python project

First, we’re going to create a folder called MailMerge, and inside we’re going to create another folder called templates.

Let’s create a file called in the MailMerge folder, and add the following code:

# Load your env variables
from dotenv import load_dotenv

# Import your dependencies
from flask import Flask,render_template,json,request,flash,redirect,url_for,session
from flask_session.__init__ import Session
import os
import csv
import re
from nylas import APIClient

# Create the app
app = Flask(__name__)
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"

# Initialize your Nylas API client
nylas = APIClient(

# This the landing page
@app.route("/", methods=['GET','POST'])
def index():
# We're using a GET, displat landing page
    if request.method == 'GET':
        return render_template('main.html')
# # Get parameters from form
        subject = request.form["subject"]
        body = request.form["body"]
        mergefile = request.form["mergefile"]
# Session variables		
        session["subject"] = subject
        session["body"] = body
# Make sure all fields are filled		
        if not subject or not body or not mergefile:
            flash('You must specify all fields')
            return redirect(url_for('index'))
            session["subject"] = None
            session["body"] = None	
# Auxiliary variables
            email = ""
            emails = []
            row_header = {}
            i = 0
            subject_replaced = subject
            body_replaced = body
# Open the CSV file			
            file = open(mergefile)
# Read the CSV contents			
            mergemail_file = csv.reader(file)
# Read and save the headers			
            headers = []
            headers = next(mergemail_file)
            for header in headers:
                row_header[header] = i
                i += 1
# Read all rows of the CSV file				
            for row in mergemail_file:
# Assign parameters to auxiliary variables				
                subject_replaced = subject
                body_replaced = body
# Read all headers				
                for header in headers:
# Search for header and replace them with
# the content on the CSV file					
                    if"{"+f"{header}"+"}", subject):
                        subject_replaced = re.sub("{"+f"{header}"+"}", row[row_header[f"{header}"]], 
                    if"{"+f"{header}"+"}", body):
		        body_replaced = re.sub("{"+f"{header}"+"}", row[row_header[f"{header}"]], 
#Try to get the Name and Last_Name
                        full_name = row[row_header["Name"]] + row[row_header["Last_Name"]]
                        full_name = row[row_header["Name"]]
# Try to send an email
# Create the draft					
                    draft = nylas.drafts.create()
# Add the subject					
                    draft.subject = subject_replaced
# Add the body					
                    draft.body = body_replaced
# Add the recipient and email					
           = [{"name":full_name,"email":row[row_header["Email"]]}]
# Send the email					
# It was successful, added to the emails array					
                    email = row[row_header["Email"]]
# There's a problem					
                    print("Something went wrong")
# Call the results page					
    return redirect(url_for('results', emails = emails))	
# Show recipients emails
@app.route("/results", methods=['GET'])
def results():
# Get the list of emails	
    emails = request.args.getlist('emails')
# Call the results page passing emails as parameters	
    return render_template('results.html', emails = emails)
# Run our application  
if __name__ == "__main__":

Inside the templates folder, we need to create 3 different files, let’s start with base.html:

<!DOCTYPE html>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<script src=""></script>
	<title>Nylas' Mail Merge</title>
<div id="container">		
  {% with messages = get_flashed_messages() %}
    {% if messages %}
      {% for message in messages %}
      <div class="flash bg-green-300 border-green-600 border-b p-4 m-4 rounded w-2/5 grid place-items-center text-red-600 font-bold">
        {{ message }}
      {% endfor %}
    {% endif %}
  {% endwith %}	
  {% block content %}  
  {% endblock %}

Then, main.html:

{% extends 'base.html' %}

{% block content %}
<div class="bg-green-300 border-green-600 border-b p-4 m-4 rounded w-2/5 grid place-items-center">
<p class="text-6xl text-center">Mail Merge</p>
<form method="post">
<label for="subject" class="font-bold">Subject: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</label>
<input type="text" name="subject" value="{% if session.subject != None %} {{session.subject}} {% endif %}" size="50"></input>
<label for="body" class="font-bold">Body:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</label>
<textarea name="body" rows=5 cols=49>{% if session.body != None %} {{session.body}} {% endif %}</textarea>
<label for="mergefile" class="font-bold">Merge File: &nbsp;</label>
<input type="file" name="mergefile">
<button type="submit" class="block bg-blue-500 hover:bg-blue-700 text-white text-lg mx-auto py-2 px-4 rounded-full">Submit</button>
{% endblock %}

And finally results.html:

{% extends 'base.html' %}

{% block content %}
<div class="bg-green-300 border-green-600 border-b p-4 m-4 rounded w-2/5 grid place-items-center">
	<h1 class="text-3xl"> The email was sent to the following addresses</h1>
	{% for email in emails: %}
		<p class="font-semibold">{{ email }}</p>
	{% endfor %}

<div class="bg-green-300 border-green-600 border-b p-4 m-4 rounded w-2/5 grid place-items-center">
<a href="/" class="text-blue-600">Go back</a>
{% endblock %}

And that’s it. We’re ready to roll.

Running our Mail Merge application

In order to run our application, we just need to type the following on the terminal window:

$ python
Running our Mail Merge Template application

Our Mail Merge Template with Python and Flask application will be running on port 5000 of localhost, so we just need to open our favourite browser and go to the following address:


If you want to learn more about our Email APIs, please go to our documentation Email API Overview.

You can sign up for Nylas for free and start building!

Related resources

How to create and read Google Webhooks using Ruby

Create and read your Google webhooks using Ruby and Sinatra, and publish them using Koyeb. Here’s the full guide.

Build mail merge and email templates using TinyMCE Rich Text Editor

Learn how to build Mail Merge and Email Template functionality in your email workflow using TinyMCE Rich Text Editor.

Send emails using TinyMCE Rich Text Editor

Learn how to improve your email workflow using TinyMCE Rich Text Editor.