MailChimp API, Netlify and Serverless Functions
Peter Jausovec
Posted on September 27, 2019
I've discovered multiple times now that Mailchimp does not allow you to sign up the same person (email that is) twice. Let me explain the scenario I was trying to implement. On my website, I want to allow users to provide their email address to get notified when certain workshop is released. There are multiple workshops and people might be interested in more than one workshop.
The first time they sign up, everything works fine - they are not in my Mailchimp list (or the audience as they call it today) and the sign up goes through fine. I am using the Groups feature in Mailchimp to put those emails in groups based on their interests. For example, ABC (abc@example.com) is interested in Workshop A, they sign up and they get put into the "Workshop A" group. Since they are interested in more workshops, they want to sign up to get notified when Workshop B is ready as well. That's where the problems start.
Since they are using the same email address (abc@example.com), Mailchimp returns an error saying that this user is already in the list. If you think about that, it is a valid error - they don't allow duplicate emails. To be honest, I would hate having duplicate emails in the list as well. Instead of signing the person up for the second time, what I really had to do is to update their group/interest preferences.
I am using Gatsby and the gatsby-plugin-mailchimp
to sign up users, but I needed the functionality to update the groups of existing and subscribed emails. Here's the workflow I had in mind:
if subscribeUser("abc@example.com") succeeds {
// nothing more to do
return;
}
if subscribeUser("abc@example.com") fails_due_to_duplicate_email {
// update the existing subscription to add "Interest 2"
updateSubscription("abc@example.com", "Interest 2")
}
Originally, I thought I could update the gatsby-plugin-mailchimp
and add that functionality, however subscribing users does not require authentication and it uses a simple POST
request. Updating the subscribers and adding interests, however, does require authentication and the use of Mailchimp API key.
Ideally, I would love to use an existing solution to solve this, but I didn't necessarily want to run a server just for this. Since my sites are hosted on Netlify, I noticed they offer a Functions feature. This feature allows you to run and deploy serverless (Lambda) functions without an AWS account. To make it even more appealing - there's also a free tier, up to a certain amount of invocations of course. I decided to go with it.
Using the Mailchimp API
As a next step, I spent some time looking at the Mailchimp API and trying to figure out which endpoints I need to use, how to authenticate and what values I need to send in order to update interests on subscribers. Here's the list of things I found out from different docs:
-
API Key
You will need an API key. To create one, log in to your Mailchimp account, open the Profile page and click the Extras → API Keys option. From there, you can click the Create A Key button to create an API key you can use when making calls to the API endpoint. There are also links to the API documentation and an API playground where you can test different stuff out.
-
API Endpoint
The format for the API endpoint where you can make calls to looks like this:
https://<dc>.api.mailchimp.com/3.0
. You will need to replace the<dc>
portion (dc stands for data center) with the last part of your API key (e.g.us7
) -
Authorization header
With each request you make, you will need to supply an API key with it. For example:
curl -H "Authorization: apikey [YOUR_API_KEY_HERE]" https://<dc>.api.mailchimp.com/3.0/
-
Audience/list ID
You will need the Audience ID to identify the audience/list where you'll be making updates to
-
MD5 hashes instead of emails/IDs
An MD5 hash of a lowercase email address is used to identify a contact in your audience. No need to send full emails, but MD5 hashes instead. For example:
curl -H "Authorization: apikey 123" https://<dc>.api.mailchimp.com/3.0/lists/[LIST_ID]/members/[MD5_EMAIL_HASH]
Originally I planned to do a check if a user is already subscribed or not, but it wasn't necessary as the POST
request to subscribe the user would tell me if the user is already subscribed or not. Maybe at some point, I could create a separate function that would check if the user is subscribed or not and then do a POST
request or update their subscription.
Digging through the API documentation I found the endpoint I needed to update the users' interests. You need to make a PATCH
request to the API endpoint (same as the one above that has the list id and the member hash) and provide an object with the interest IDs and a boolean value, indicating if you are adding or removing a user from the interest group:
{
"interests": {
"INTEREST_ID": true
}
}
I think the most complicated part was actually figuring out what the interest IDs are. To do that, I had to first figure out what the category ID was (each category has one or more interests in there). A GET
request to the /lists/[LIST_ID]/interest-categories
endpoint will give you something like this:
{
"list_id": "list_id",
"categories": [
{
"list_id": "list_id",
"id": "CATEGORY_ID",
"title": "My Category",
"display_order": 0,
"type": "checkboxes",
....
With the CATEGORY_ID
I made another request to the interests endpoint:
curl -H "Auth..." https://.../lists/[LIST_ID]/interest-categories/[CATEGORY_ID]/interests
Bingo! This returned an array of all interests within the category with their names and IDs:
"interests": [
{
"category_id": "CATEGORY_ID",
"list_id": "LIST_ID",
"id": "INTEREST_ID",
"name": "Interest One",
"subscriber_count": "2",
"display_order": 1,
....
},
{
"category_id": "CATEGORY_ID",
"list_id": "LIST_ID",
"id": "INTEREST_ID",
"name": "Interest Two",
"subscriber_count": "0",
"display_order": 2,
I am not in the business of dynamically getting the categories, interests and their IDs, so I just got the interest IDs to use in my frontend app. Depending on the signup page user were on, I would call the serverless function and pass in the INTEREST_ID, email hash, and the list ID.
Implementation
Adding a function to your Netlify site, you create a folder (e.g. functions
) in the root and put your function.js
file there. The implementation of this particular functionality was straight forward. I have set the MAILCHIMP_LIST_ID
and MAILCHIMP_API_KEY
as environment variables on the site setting page on Netlify, the other values are coming from the parameters.
const fetch = require("node-fetch");
exports.handler = function (event, context, callback) {
const listId = process.env.MAILCHIMP_LIST_ID;
if (listId === undefined) {
callback(null, {
statusCode: 400,
body: JSON.stringify({ error: "missing listId" }),
})
return;
}
const authHeader = `apikey ${process.env.MAILCHIMP_API_KEY}`;
if (process.env.MAILCHIMP_API_KEY === undefined) {
callback(null, {
statusCode: 400,
body: JSON.stringify({ error: "missing api key" }),
})
return;
}
const interestId = event.queryStringParameters.interestId;
const memberHash = event.queryStringParameters.memberHash;
if (interestId === undefined) {
console.log(`Missing interestId: ${JSON.stringify(event)}`)
callback(null, {
statusCode: 400,
body: JSON.stringify({ error: "missing interestId" }),
})
return;
}
if (memberHash === undefined) {
console.log(`Missing member hash: ${JSON.stringify(event)}`)
callback(null, {
statusCode: 400,
body: JSON.stringify({ error: "missing memberHash" }),
})
return;
}
const payload = {
"interests": {
[interestId]: true
}
};
console.log(`Payload: ${JSON.stringify(payload)}`);
console.log(`Adding interest ${interestId} to user ${memberHash}`);
const url = `https://us7.api.mailchimp.com/3.0/lists/${listId}/members/${memberHash}`
console.log(`Invoking URL: ${url}`);
fetch(url, {
method: 'PATCH',
headers: {
'Authorization': authHeader,
},
body: JSON.stringify(payload),
}).then(x => x.json()).then(data => {
console.log(`Request successful: ${JSON.stringify(data)}`);
callback(null, {
statusCode: 200,
body: JSON.stringify({ msg: "Subscription updated" })
})
})
};
Posted on September 27, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.