Mailchimp is one of the most widely used email service providers (or ESPs) in ecommerce and marketing. Since it is popular with those who only need its basic campaign features, it’s built-in functionality for creating marketing automation emails can sometimes be overlooked as being too basic, compared to more sophisticated platforms.
While Mailchimp’s marketing automation functionality isn’t currently up there with the best, it’s actually pretty good for basic needs, and can be quite powerful if you can make use of the Mailchimp marketing API. With the API, and some data science skills, you can actually create some quite impressive marketing automation email campaigns.
For example, let’s say you sell washing machines and find that customers who purchase them always get confused about how to install them, leading to lots of customer service tickets or emails. To resolve this, you could run a database query to identify new washing purchasers, add a tag called purchased_washing_machine
, and trigger an automation email in Mailchimp to give them some tips and advice.
Since these emails are timely and relevant, open rates and click through rates tend to be very strong. They’re also a great way to cross-sell other items that customers might want, and can be used to trigger replenishment emails later down the product lifecycle.
In this project, I’ll show how you can use the Mailchimp marketing API to create the tags you need to set marketing automation triggers in Mailchimp, so you can create your own marketing automation email campaigns.
While Mailchimp is good for creating basic marketing automation campaigns, it’s not as feature rich as some other systems, especially if you don’t have all your ecommerce data in the system, or you want to create marketing automation tags to trigger emails using data from models or external data sources.
However, if you’re happy to put in the extra work, it’s a great budget solution for those of a technical nature who can exploit the API to make it do more than the average marketer could achieve. The frustrating thing is that its internal rules engine for creating segments is somewhat limited, particularly in the standard version.
For example, you can’t create complex rules, and you’re limited to the number of fields you can use to create the triggers that cause emails to be sent automatically. You need to plan for this to get the best out of the platform.
Prior to developing your marketing automation application, I’d recommend manually creating some tags using the Mailchimp backend and then checking to ensure you can create an automation that does what you want. There are some annoying shortcomings in the rules engine that mean only certain scenarios are possible, but pre-development prototyping can find these and save wasted time.
Picture by Negative Space, Pexels.
To understand what data you can put into Mailchimp, we first need to go over Mailchimp’s underlying data structure. This comprises three simple features: merge fields, tags, and segments:
Merge fields were formerly known as merge vars and exist as columns of data within the Mailchimp audience designed to hold variable data. The merge field name is fixed, but the value could be different across customers. For example, you might have a merge field called TOTAL_ORDERS
which stores the total number of orders each customer has placed.
Merge fields are specific to a Mailchimp list. You can add these manually using the Mailchimp admin panel, or you can create them via the API using the client.lists.add_list_merge_field()
. They can be required for each user and you can control whether users can see them via your Mailchimp preferences centre, or whether they’re hidden from subscribers.
Merge fields can have a range of different “types” which alter the way the data are stored and how the field appears in the backend. These include: text
, number
, address
, phone
, date
, url
, imageurl
, radio
, dropdown
, birthday
, and zip
.
The API can be used to list all the merge fields, add merge fields, get a specific merge field’s settings, update a merge field, or delete a merge field. When adding list members, you can easily update the values you push to the API to populate these fields.
Unlike merge fields, or merge vars, tags are completely dynamic, data aren’t stored in columns, and are for internal use only. They’re quite scalable, because you can bulk tag contacts (or bulk remove tags), which is faster than individually updating merge field values.
You can assign and remove tags to individual email subscribers or batches of subscribers, and then use those tags to help create segments or use them as triggers for sending automations.
For example, you might tag a subscriber with “lapsed” to indicate they’re a lapsed customer, or “bulk_buyer” to indicate that they prefer to buy products in larger volumes. They’re really flexible and useful and let you do some innovative things inside Mailchimp campaigns.
Segments are custom-made groups of email subscribers whose merge field or tag data matches a defined set of criteria. The usual approach to using Mailchimp segments is to create and populate the appropriate merge fields and tags regularly, and then use segments to create specific subsets of subscribers to target.
For example, you might create a segment contain all subscribers with the merge field of customer
, and who have spent over 100
per order in the past year, and have been tagged with the purchased_washing_machine
tag.
You can create complex segments using up to five features using the basic version of Mailchimp, but this is more feature-rich in Mailchimp Pro. The downside is that the rules aren’t that flexible, and I find this is where problems usually arise.
Picture by Krsto Jevtic, Unsplash.
For this project we’ll be using the Mailchimp Marketing API. I’ve covered the basic usage of this API in my previous article, so you may want to check that out before you start. The package is available via PyPi, so install this first and then import the packages below.
!pip3 install mailchimp-marketing
import pandas as pd
import hashlib
import mailchimp_marketing as MailchimpMarketing
from mailchimp_marketing.api_client import ApiClientError
Next, we need to extract the API configuration parameters from your Mailchimp account so we can authenticate against Mailchimp and retrieve and update the data in your Mailchimp account.
To connect to the Mailchimp API, you will need three variables that have to be extracted from your Mailchimp account. These are the API key, the server prefix, and the list ID, or audience ID. They’re not particularly easy to find. Here’s how it’s done at present:
us2
To save re-entering these throughout the code, we’ll define some constants in which to store them for later use.
API_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-us2'
SERVER_PREFIX = 'us2'
LIST_ID = 'xxxxxxxxx'
Since we’re going to trigger our marketing automations by creating and assigning tags in Mailchimp, the first thing you’ll need to do is create the tags you want to use and assign them to a test email address, such as your own.
You can do this by finding your email address within the Mailchimp list, and then clicking the plus icon in the tags section to add an existing tag or create a new one and add it to the email subscriber. I’d recommend using a simple naming convention for you tags, such as lowercase letters and underscores only.
In order to assign an existing tag to your users you will need the tag_id
. However, annoyingly, this isn’t visible within the Mailchimp interface. However, there’s an API method called client.lists.tag_search()
that returns a list of all tags on a list.
We’ll create a function called tag_search()
to which we can pass the list_id
obtained above via a try except block. This will connection to the Mailchimp API using your API key and server prefix and return a dictionary containing all the tags found on that list_id
. You can then identify the tag_id
for the tag you wish to assign to other subscribers.
def tag_search(list_id):
"""Return all tags on a list"""
try:
client = MailchimpMarketing.Client()
client.set_config({
"api_key": API_KEY,
"server": SERVER_PREFIX
})
response = client.lists.tag_search(list_id)
return response
except ApiClientError as error:
print("Error: {}".format(error.text))
response = tag_search('xxxxxxxxx')
response
{'tags': [
{'id': 123, 'name': 'Installed Pied Piper'},
{'id': 135, 'name': 'Visited Hoolicon'},
{'id': 244, 'name': 'Shared app'},
{'id': 148, 'name': 'Heavy app user'}],
'total_items': 4}
Next, we’ll create another helper function called get_member_tags()
to use to check that any updates we make to a list subscriber have worked. We’ll pass in the list_id
and the email
, connect to the API and then fetch the tags assigned to that user using the get_list_member_tags()
function.
Importantly, this requires the email address you pass to be hashed using the MD5 hashing algorithm. We can ensure this is in the right format for Mailchimp by using this code: hashlib.md5(email.encode('utf-8')).hexdigest()
. After running that, we get a dictionary containing the tags for our given email address.
def get_member_tags(list_id, email):
"""Get member tags."""
try:
client = MailchimpMarketing.Client()
client.set_config({
"api_key": API_KEY,
"server": SERVER_PREFIX
})
subscriber_hash = hashlib.md5(email.encode('utf-8')).hexdigest()
response = client.lists.get_list_member_tags(list_id, subscriber_hash)
return response
except ApiClientError as error:
print("Error: {}".format(error.text))
response = get_member_tags('xxxxxxxxx', 'richard@example.com')
response
{'tags': [{'id': 123,
'name': 'installed_pied_piper',
'date_added': '2021-07-13T08:23:06+00:00'}],
'total_items': 1}
We’re going to trigger our marketing automation by adding a tag to specific email addresses. To do this, we need to find the tag_id
using the function we created a moment ago. We can use then use the client.lists.batch_segment_members()
API function to add or remove email subscribers to the tag.
Our function takes a list of emails. That list can contain a single email address or up to 500, allowing us to perform a bulk update or bulk remove on a given tag. The function below provides a neat wrapper to it, and lets you add or remove a tag from a list of emails by setting the add
parameter to True
or False
.
emails_list = ['richard@example.com']
def update_tags(list_id, tag_id, emails_list, add=True):
"""Assign or remove a tag from a list of emails within a Mailchimp list.
Args:
list_id (string): Mailchimp list ID
tag_id (int): Mailchimp tag ID
emails_list (list): List of email addresses in the Mailchimp list to update
add (boolean, optional, default=True): Set to True to add or update tags, or False to remove tags.
Returns:
response: API response or
"""
try:
client = MailchimpMarketing.Client()
client.set_config({
"api_key": API_KEY,
"server": SERVER_PREFIX
})
if add:
response = client.lists.batch_segment_members({"members_to_add": emails_list}, list_id, tag_id)
else:
response = client.lists.batch_segment_members({"members_to_remove": emails_list}, list_id, tag_id)
return response
except ApiClientError as error:
return format(error.text)
response = update_tags('xxxxxxxxx', '123', emails_list, add=True)
response
{'members_added': [],
'members_removed': [],
'errors': [{'email_addresses': ['richard@example.com'],
'error': 'Email addresses already exist in the static segment'}],
'total_added': 0,
'total_removed': 0,
'error_count': 1,
'_links': [{'rel': 'self',
'href': 'https://us2.api.mailchimp.com/3.0/lists/xxxxxxxxx/segments/123',
'method': 'GET',
'targetSchema': 'https://us2.api.mailchimp.com/schema/3.0/Definitions/Lists/Segments/Response.json'},
{'rel': 'parent',
'href': 'https://us2.api.mailchimp.com/3.0/lists/b5ed477df9/segments',
'method': 'GET',
'targetSchema': 'https://us2.api.mailchimp.com/schema/3.0/Definitions/Lists/Segments/CollectionResponse.json',
'schema': 'https://us2.api.mailchimp.com/schema/3.0/Paths/Lists/Segments/Collection.json'},
{'rel': 'delete',
'href': 'https://us2.api.mailchimp.com/3.0/lists/xxxxxxxxx/segments/123',
'method': 'DELETE'},
{'rel': 'update',
'href': 'https://us2.api.mailchimp.com/3.0/lists/xxxxxxxxx/segments/123',
'method': 'PATCH',
'targetSchema': 'https://us2.api.mailchimp.com/schema/3.0/Definitions/Lists/Segments/Response.json',
'schema': 'https://us2.api.mailchimp.com/schema/3.0/Definitions/Lists/Segments/PATCH.json'},
{'rel': 'members',
'href': 'https://us2.api.mailchimp.com/3.0/lists/xxxxxxxxx/segments/123/members',
'method': 'GET',
'targetSchema': 'https://us2.api.mailchimp.com/schema/3.0/Definitions/Lists/Segments/Members/Response.json'}]}
Since the client.lists.batch_segment_members()
function only accepts up to 500 email addresses, you will need to make multiple requests if the volume of emails you wish to tag exceeds this amount. Next we will modify the function above, so it can handle a longer list of emails, batch them up into groups of 500 or fewer, and then run each batch through the API.
First, we’ll create a helper function called create_batches()
that will take our list of emails, and our batch size, i.e. 500. It will break the original list up into batches of 500, and put the remainder in a final list. It will then put these lists into another list and return that. A simple for
loop can then be used to access each batch.
def create_batches(emails_list, batch_size=500):
"""Splits a list of emails into batches of size n and returns a list of batches.
Args:
emails_list: List of emails
batch_size (optional, int): Maximum batch size (default is 500)
Returns:
batches: List of lists of batches of 500 or fewer emails.
"""
batches = [emails[i:i + batch_size] for i in range(0, len(emails), batch_size)]
return batches
If we create a test list of emails and run it through create_batches()
with the batch_size
argument set to 1, we should get back a single list, containing a list for each batch of one email.
This works fine, so the next step is to incorporate the code into the update_tags()
function we created in the previous step, so it can support massive lists of emails and can run them through the Mailchimp API in batches of 500 at a time.
emails = ['richard@example.com', 'erlich@example.com', 'jared@example.com', 'gilfoyle@example.com']
batches = create_batches(emails, batch_size=1)
batches
[['richard@example.com'],
['erlich@example.com'],
['jared@example.com'],
['gilfoyle@example.com']]
Now, we’ll duplicate the update_tags()
function and make some modifications so it supports batch processing. We’ll add a function argument for batch_size
which we’ll set to the 500 email limit of Mailchimp, then we’ll use create_batches()
to take the original list of emails and split it into chunks of 500 or fewer, assigning the list of lists to batches
.
Then, rather than just running the batch_segment_members()
function on the entire emails_list
list, which might hit the 500 email limit, we’ll use a for
loop. For each batch of 500 or fewer emails we encounter, we’ll pass the list stored in batch
to the batch_segment_members()
function.
If we get back a response
from the API, we’ll add it to the responses
list, otherwise we’ll append()
an exception error, and then return the list of responses
as the output.
def batch_update_tags(list_id, tag_id, emails_list, add=True, batch_size=500):
"""Bulk tag a list of up to 500 emails. """
responses = []
try:
client = MailchimpMarketing.Client()
client.set_config({
"api_key": API_KEY,
"server": SERVER_PREFIX
})
batches = create_batches(emails_list, batch_size=batch_size)
if add:
for batch in batches:
response = client.lists.batch_segment_members({"members_to_add": batch}, list_id, tag_id)
if response:
responses.append(response)
else:
for batch in batches:
response = client.lists.batch_segment_members({"members_to_remove": batch}, list_id, tag_id)
if response:
responses.append(response)
except ApiClientError as error:
responses.append(format(error.text))
return responses
Running the function on our list will add the tag if the user exists in the list, or will return an error if they’re not present. If there are more than 500 emails, it will create batches and run each batch through the API, thus bypassing the 500 email limit.
responses = batch_update_tags('xxxxxxxxxxx', '123', emails_list, add=True, batch_size=1)
The final steps are to put it into production. I’m going to skip this step, as it depends on your specific environment. To get the list of emails, you simply need to write a database query to fetch emails that match your given criteria, i.e. “installed app” and then pass that to batch_update_tags()
.
You’ll probably want to run these using an orchestration system such as Airflow or Luigi. Check out my guide to creating pipelines with Apache Airflow for some general pointers on how to get this up and running.
Matt Clarke, Friday, July 16, 2021