Skip to content

Using the HubSpot API to Bulk Manage Email Content in Spreadsheets

We recently used HubSpot’s marketing email API to streamline how we checked dead links in emails and made updates to content in bulk. Here’s how it works.

While HubSpot’s built-in tools are great for creating emails and building workflows, they don’t always cut it when you need to bulk edit hundreds of emails. In this guide, we’re going to cover how you can use HubSpot API endpoints to export content from every email in your workspace to a spreadsheet. You don’t need to be a code wizard to do something like this, and even a marketer can figure it out pretty quickly. 

In this guide, we'll focus on the HubSpot marketing email API, which is currently in beta, meaning it's subject to change. But the steps you learn here can be used with any HubSpot API to manage data or content in bulk by exporting it to a CSV file.

Why manage HubSpot content in spreadsheets?

Being able to export all your HubSpot emails to a spreadsheet allows you to audit all your workflows at once, make bulk changes, and more. At Unito, we’ve used this process to scrape all our emails for broken links after a few people on the team started noticing them. Instead of having some poor marketers go through every email with a fine-tooth comb, we exported them all to a spreadsheet, scraped them for broken links, and fixed them from there.

That’s not the only thing you could use this guide for, however. Here are just a few other ways this could be useful:

  • Scrape emails for old product names ahead of a rebrand.
  • Replace copy across hundreds of emails programmatically.
  • Create reports in spreadsheets based on email content.
  • Find errors across all emails much faster.

The basics

To get the most out of this guide, there are a few concepts and tools you should already have a basic understanding of.

  • APIs and Endpoints: HubSpot’s Developer Docs have all the information you’ll need to follow this guide, specifically for the Marketing Email API. If you don’t know the difference between an API and ARPU (average revenue per user), here’s a great guide for beginners.
  • Basic programming: We’ll be using curl and basic node scripts (Javascript) in the examples below, so knowing it would help; but we won’t be going too deep so you should be able to translate the approach to the language of your choice. The important thing you need to understand are basic principles of storing and looping through data.
  • HubSpot: Since we’re exporting data from HubSpot, you should have at least a passing familiarity with how it handles marketing content (i.e. emails) to know what to pull and what to filter out.

Now that we’ve covered the basics, let’s get started.

How to export all HubSpot email content to a spreadsheet

You can get your email content out of HubSpot and into a spreadsheet by following these three steps:

  • Creating a private app in HubSpot
  • Accessing the API endpoint to fetch emails
  • Processing the emails

Step 1: Create a private app in HubSpot

In order to access pretty much any HubSpot API, you need a way to set up an authentication process. In HubSpot, that's done through private apps, which have specific permissions assigned via scopes.

HubSpot's documentation has a pretty detailed walkthrough for this, so I won't replicate it here.

Before you create your app, you'll want to check the endpoint you're using (in our case it's the marketing emails endpoint) to assign the appropriate scopes to your app. Scopes determine how much data you can pull from a HubSpot account, meaning your app has only as much access as it needs to run.


Once your app is created, you'll get a private app token you'll need to perform the API calls in the following steps.


Step 2: Access the API endpoint and fetch emails

I like to fiddle around and test access to APIs before writing any code. This is easy enough to do by copying and pasting the Request code from HubSpot’s docs.

Notice the header ‘authorization: Bearer YOUR_ACCESS_TOKEN’ line below. That’s where you’ll add the private app token you created above.

curl --request GET \ --url \ --header 'authorization: Bearer YOUR_ACCESS_TOKEN'

If you set up your private app properly, you'll get a lengthy JSON response with all kinds of goodies. You can customize this response by passing more properties along with the request — like createdAt or createdAfter.

Note that you're limited to 100 pages of results for a given request. If you want to get *all* emails from your HubSpot account, you'll need to run multiple requests. The API generates a URL for the next batch as part of each response as the properties and I write a recursive async function in Javascript to get an array of every marketing email.

async function fetchHubspotMarketingEmails( output = [], after = null, headers = { 'Authorization' : `Bearer ${process.env.HS_PRIVATE_APP_ACCESS_TOKEN}` } ) { try { const QUERY_PARAMS=`?${after ? `&after=${after}` : ''}`; const response = await fetch(`${QUERY_PARAMS}`, { headers } ); const emails = await response.json(); if ( 0 !== Object.keys(emails.results).length ) { const emailsToMerge = Object.values(emails.results); output.push(...emailsToMerge); const pagingKey = getNestedObjectProperty( emails, 'paging', 'next', 'after' ); if ( pagingKey ) { await new Promise((resolve) => setTimeout(resolve, 25)); // setup a sleep depend your api request/second requirement. return await fetchHSEmails( output, pagingKey ); } } return output; } catch (err) { console.error(err); } }

Step 3: Process the emails

When we originally did this at Unito, we needed to catalog all HubSpot emails in a CSV file so the support team could flag any that had email addresses for ex-employees. As a growing startup, this was bound to happen, and we wanted something we could use again in the future.

After running this request, we get a LOT of data, but we only need a bit of it for this initiative. Specifically, the properties we want are email IDs (to easily find and edit emails manually if needed), state (eg. "SENT"), the campaign name, the subject, type, whether it’s archived, the sender’s name, and the reply-to email address.

It takes some trial and error to figure out exactly which properties you need, especially since not all properties are set if blank, and loop through the array of emails until you only get the info you need. I'm using CSV stringify/sync to get a CSV from this raw data, but any CSV utility library will work.

Here's what our output looks like after we've figured out the properties we need.

import fetchHubspotMarketingEmails from './modules/fetch-hubspot-marketing-emails.js'; // our first function import { stringify } from 'csv-stringify/sync'; import fs from 'fs/promises' import path from 'path'; import { fileURLToPath } from 'url'; // Utility function to get the current folder function getCurrentFolder(metaURL = null) { try { const __filename = fileURLToPath(metaURL); const __dirname = path.dirname(__filename); return __dirname; } catch (err) { console.log(err); return false; } } // Utility function to write to a local file async function writeFile(string, fileName = 'data.json') { try { await fs.writeFile(fileName, string, "utf8") //options can use the shorthand version here, just a string automatically assigns file encoding return true; } catch (err) { console.error('Error occurred while writing file:', err) return false; } } // The fun starts here const emails = await fetchHubspotMarketingEmails(); let data = []; let columns = { id: 'id', state: 'state', campaignName: 'campaignName', name: 'name', subject: 'subject', type: 'type', archived: 'archived', fromName: [ 'from', 'fromName' ], replyTo: [ 'from', 'replyTo' ], }; emails.forEach((email) => { const row = []; Object.entries(columns).forEach(([key,val]) => { if ( 'string' == typeof val ) { row.push( email[val] ); } else { row.push( email[val[0]][val[1]] ) } }); row.push( `${}/performance` ); data.push(row); }); const moreCols = { ...columns, url: 'url' } const thisFolder = getCurrentFolder(import.meta.url); const csvFile = `${thisFolder}/output/hubspot-marketing-emails.csv`; const output = stringify( data, { header: true, columns: moreCols } ); const file = await writeFile( output, csvFile ); if (!file) { console.log('Could not create file.'); } else { console.log(`File created successfully: ${csvFile}`); }

And that's it. Once we have our CSV in hand, we pass it off to the support team for their part of the work.

Now that we've built and run this, we've gotten a lot better at setting it up. We've already used the HubSpot API to:

  • Sort through all email content to find broken links.
  • Sort through our knowledge base to find broken links.
  • Flag emails not in our current top 145 workflows.

And we’re planning to do more.

Build it in a day, save time every day

The first time we built this app, it only took us a few hours to go from “hmm, we need to export all our marketing emails” to a spreadsheet with all the data we needed. Your mileage might vary—especially if you have to troubleshoot—but it shouldn’t take you more than a day to get this done.

You could easily use this as a basis for similar apps that fulfill all of your marketing team’s data and ops needs, automating away tasks that snap you out of your daily work.