MailChimp API, Netlify and Serverless Functions 
Tags: beginners, javascript, webdev, serverless
Published: 2019-09-27 23:48:35

---
title: MailChimp API, Netlify and Serverless Functions 
published: true
description: How to add your email subscribers to different groups using a serverless function
tags: beginners, javascript, webdev, serverless
---


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]([https://www.netlify.com/docs/functions/](https://www.netlify.com/docs/functions/)). 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: 

1. **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. 

2. **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`) 

3. **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/
```

4. **Audience/list ID**

    You will need the Audience ID to identify the audience/list where you'll be making updates to

5. **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" })
            })
        })
    };
```