Connecting Endpoints: Email a Contact List Using HubSpot’s API

Navigating the limitations of HubSpot’s API to create our own Email Contact list function, a key missing feature.

February 2, 2024

By Devin Dunn

HubSpot is a very powerful, and popular, CRM platform that is designed to improve overall customer experience and business performance through integrated marketing, sales and customer service tools. Customers seek out HubSpot’s tools to increase lead generation, retain customers for longer, and develop advanced insights into each individual contact throughout their customer journey.

This blog will focus on the Marketing platform offered by HubSpot. In this platform, business owners are able to segment customers into lists and also create email templates that can be sent using HubSpot’s API, or automation features. However, there is a missing link between customer lists and email sending. Currently, HubSpot’s API does not offer the ability to email a complete contact list at one time.

Let’s imagine a use case for when we want to email a complete list. Fishtank Events Services have placed a form on our website to track interest for an upcoming customer appreciation event. Every time a user fills out the form with their name and email, they are added to the Customer Event list in our HubSpot backend. As the event approaches, we’d like to send updates and reminders to keep contacts engaged and excited about our event. Since, HubSpot does not offer a single API endpoint for this, so we’ll have to come up with our own solution.

Working With What We Have

Since HubSpot’s API does not provide an endpoint to email a complete list, we’ll need to piece together what is there to create our own email list function. Let’s begin with a brief explanation of the steps, and some of the constraints we’ll need to keep in mind. Assuming you have already created a list and have an email template ready to go, let’s get into it.

Here's the basic logic we’ll follow:

  1. Make an API request to get contacts in a list, specifying parameters for count and property to return contact emails.
    • There is a maximum of 100 contacts returned per request. If the list contains more, we’ll need to include step 3.
  2. For each contact returned in the array from the request, make another API request to send an email.
  3. Check the 'has-more' and 'vid-offset' values to determine whether the process needs to be repeated. If has-more is true, run again using vid-offset value in next get contacts request.

Step 1: Get Contact Profiles from a HubSpot List

First, we need to make a GET request to “Get contacts in a list” from the Contact List API. This will return a JSON object that will contain a “contacts” array. The contacts array will be populated with objects that represent every individual HubSpot contact found in the list and all of the properties associated with that contact. View the example response to gain a better understanding of the contacts array.

To make life a little easier for ourselves, we can filter out the properties we don’t need by adding the parameter “&property=email” to our request URL so only the email property is returned for each contact. We will add another optional parameter of “&count=100” to retrieve 100 contacts per request, which is the maximum allowed. If your list contains more than 100 contacts, the API returns a has-more boolean value of true. To access those additional contacts we’ll need to use the vid-offset value that is also returned in the request, in the next Get request we make. This is covered in detail in step 3.

private Dictionary<string, object> GetContacts(int offset, ApiClient client)
{
    // Call API to get contacts
    var contactListEndpoint = String.Format("/contacts/v1/lists/{hubSpotListID}/contacts/all?&property=email&count=100");

    // Adjust the API call for pagination if offset is not zero. Explained further in step 3.
    if (offset != 0)
    {
        contactListEndpoint = String.Format("/contacts/v1/lists/{hubSpotListID}/contacts/all?&property=email&count=100&vidOffset={0}", offset);
    }

    // Make the API call to get contacts
    var response = client.Get(contactListEndpoint);

    if (response != null)
    {
        // Deserialize the response and return the result
        return Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(response);
    }
    else
    {
        // Throw an exception if the API call fails
        throw new Exception("Failed to retrieve contacts");
    }
}

Step 2: Loop Over Returned Contacts Array, Calling Single-Send API for Each Contact

Now we’re rolling, we’ve received a response from our first request. Time to inspect it for the values we’re interested in. First, we want to look for a contacts array so we can loop over the first batch of contacts found and send them each an email.

if (response!= null && response.ContainsKey("contacts"))
{
    var contactsArray = response["contacts"] as JArray;
}

Now that we have the contact array saved from the response, we need to loop over it and send an email to each individual contact. For this, we will need to use the Single-Send API. Make sure we have our email template ID handy too! We will need to include the email ID and the user’s email address in the body of a POST request. The body should be formatted as follows;

// Body for Hubspot Single-Send API POST request. 
{
    "emailId": {yourEmailTemplateID},
    "message": {
        "to": "{usersEmailAddress}"
    }
}

We’ve setup an empty JSON that matches that format as our emailTemplate, we’ll populate it with the user’s email address while looping through the contacts array below.

private void SendEmailsToContactList(JArray contactsArray, EmailTemplate emailTemplate, ApiClient client)
{
    // Loop through contacts and send alerts
    foreach (var contact in contactsArray)
    {
        // Extract email value from contact properties
        string emailValue = contact["properties"]?["email"]?["value"]?.ToString();

        if (emailValue != null)
        {
            // Set email address in the email template
            emailTemplate.message.to = emailValue;
            emailTemplate.emailId = {yourEmailTemplateID};

            // Make the API call to send email
            var sendAlertEndpoint = "/marketing/v3/transactional/single-email/send";
            string emailData = Newtonsoft.Json.JsonConvert.SerializeObject(emailTemplate);

            // Uncomment the following lines when ready to send alerts
            var result = client.Post(sendAlertEndpoint, emailData);

            if (result == null)
            {
                Log.SingleError("Failed to send alert", result);
            }
        }
    }
}

Step 3: Check ‘Has-More’ and ‘Vid-Offset’ Values to See if We Need to Repeat Steps.

We’re at the final stage, we’ve collected our contacts from the list, and successfully sent the first group an email. But, since HubSpot limits our get contact call to 100 at a time, we need to check the has-more value from our first Get Contacts API call to see if we need to repeat any steps.

In the code below, we’ve established a do while loop around both our GetContacts and SendEmailToContactList functions from step 1 and 2. The do while loop will continue to re-run these two steps until ‘has-more’ is false. In our first step, we wrote an if statement that directs the GetContacts call to use the vid-offset value in the next API request, as long as it has been reassigned from its original value of 0.

Let’s look at the complete process, with all the steps combined.

public bool SendEmailToContacts(string apiBaseUrl, EmailTemplate emailTemplate)
{
    try
    {
        // Initialize API client
        var client = new ApiClient(apiBaseUrl);

        // Initialize variables for pagination. 
                // offset is set to 0, so if it is reset later on with the value from response data
                // when we call GetContacts, it knows there is a vid-offset to use in the API call
        int offset = 0;
        Dictionary<string, object> contactListResult = null;

        // Loop through contacts and send alerts
        do
        {
            // Get contacts with pagination. This is where we call the function from step 1.
            contactListResult = GetContacts(offset, client);

            if (contactListResult != null && contactListResult.ContainsKey("contacts"))
            {
                // Extract contacts array from the result
                var contactsArray = contactListResult["contacts"] as JArray;

                // Send alerts for each contact. This is the function from step 2.
                SendEmailsToContactList(contactsArray, emailTemplate, client);
            }

            // If the response has a vid-offset, we will set its value to offset to be
                        // reused in the next getContacts call to get the next 100 contacts.
            offset = Convert.ToInt32(contactListResult["vid-offset"]);

                // Finally, we have our conditions to rerun the entire loop. If hasMore is true,
                // rerun all the steps we just did, but now with a new vid-offset value.
        } while (contactListResult != null && contactListResult.ContainsKey("has-more") &&
                 contactListResult["has-more"] is bool hasMore && hasMore);

    }
    catch (Exception ex)
    {
        // Handle exceptions and log errors
        Log.Error("Error: " + Newtonsoft.Json.JsonConvert.SerializeObject(ex), this);
        HandleError();
    }

    return true;
}

HubSpot's API: Your Key to Effective Email Marketing

And there we have it, a complete solution that will individually email every member of a list using HubSpot’s API. I hope if this process seemed confusing at the start, reviewing the logic and steps involved in the process have cleared things up.

Our approach creatively navigates HubSpot’s API limitations to address the absence of a direct API endpoint to email a complete contact list. The provided solution is ready to scale with your business, and will process lists of any size. Through a multi-step process, we equipped HubSpot users with the ability to efficiently reach out to complete contact lists, enhancing the marketing toolkit offered by HubSpot.



Devin Dunn

Devin Dunn

Junior Front-End Developer

Devin Dunn is a Front-end Developer and a University of Guelph and Juno College of Technology Alumni. Blending both marketing and web development, Devin brings a unique skillset to Fishtank. When not at his workstation you can find Devin on golf courses across Toronto, hiking with his dog, or digging through crates at your local record store.