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:
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.
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:
| Name | Last_Name | Account | Address | Gift | |
| Alvaro | Tejada Galindo | 12345 | 742 Evergreen Terrace | [email protected] | Deadpool Action Figure |
| Blag | Tejada Galindo | 56789 | 430 Spalding Way | [email protected] | AFI Poster |
| Alvaro aka “Blag” | Tejada Galindo | 46451 | 1024 Cherry Street | [email protected] | Tomb 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:

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.

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

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.

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



As we can see, all recipients received a personalized email, as all the fields were replaced accordingly.
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:
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 MailMerge.py in the MailMerge folder, and add the following code:
# Load your env variables
from dotenv import load_dotenv
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"
Session(app)
# Initialize your Nylas API client
nylas = APIClient(
os.environ.get("CLIENT_ID"),
os.environ.get("CLIENT_SECRET"),
os.environ.get("ACCESS_TOKEN")
)
# 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
else:
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'))
else:
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 re.search("{"+f"{header}"+"}", subject):
subject_replaced = re.sub("{"+f"{header}"+"}", row[row_header[f"{header}"]],
subject_replaced)
if re.search("{"+f"{header}"+"}", body):
body_replaced = re.sub("{"+f"{header}"+"}", row[row_header[f"{header}"]],
body_replaced)
#Try to get the Name and Last_Name
try:
full_name = row[row_header["Name"]] + row[row_header["Last_Name"]]
except:
full_name = row[row_header["Name"]]
# Try to send an email
try:
# 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
draft.to = [{"name":full_name,"email":row[row_header["Email"]]}]
# Send the email
draft.send()
# It was successful, added to the emails array
email = row[row_header["Email"]]
emails.append(email)
except:
# 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__":
app.run()
Inside the templates folder, we need to create 3 different files, let’s start with base.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdn.tailwindcss.com"></script>
<title>Nylas' Mail Merge</title>
</head>
<body>
<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 }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}
{% endblock %}
</div>
</body>
</html>
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>
<br>
<form method="post">
<label for="subject" class="font-bold">Subject: </label>
<input type="text" name="subject" value="{% if session.subject != None %} {{session.subject}} {% endif %}" size="50"></input>
<br><br>
<label for="body" class="font-bold">Body: </label>
<textarea name="body" rows=5 cols=49>{% if session.body != None %} {{session.body}} {% endif %}</textarea>
<br><br>
<label for="mergefile" class="font-bold">Merge File: </label>
<input type="file" name="mergefile">
<br><br>
<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>
</form>
</div>
{% 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>
<br>
{% for email in emails: %}
<p class="font-semibold">{{ email }}</p>
{% endfor %}
</div>
<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>
</div>
{% endblock %}
And that’s it. We’re ready to roll.
In order to run our application, we just need to type the following on the terminal window:
$ python MailMerge.py

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:
http://localhost:5000
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!