6 Steps to Personalizing Your Newsletter with the Parse.ly API

The inbox is a crowded place. It’s also a very personal one. Sites like the New Yorker, Apartment Therapy, and the NBA have found some incredibly innovative ways to create personalized content experiences on their sites. Parse.ly data shows  that most visits are single page view visits, but that visitors coming to your site directly or via email are the most likely to return more frequently: on average, nearly five sessions a month! To build higher engagement on site and more loyalty, you need to offer your visitors experiences around content that are relevant, tailored, and data-driven. Parse.ly has indexed 100 percent of your site’s content, including title, authors, section, tags, publication dates, image, and even the full text content. Using the /related endpoint, you can curate highly relevant article-to-article recommendations. Our CTO and co-founder Andrew Montalenti explains how that works:

Stories are related to one another based on a semantic relevance algorithm, that stems out of the post metadata and the keywords/topics discussed in the piece. For example, if the current URL discusses Apple and iPads, the /related endpoint will likely surface other content discussing the same topics from your archive.

So what if you wanted to personalize your newsletter? For this, you can build a layer on top of the /related endpoint using the /profile and /history endpoints. Where the /related endpoint asks Parse.ly’s system for all content related to a URL, the /profile endpoint will tell Parse.ly’s system to train personalized content recommendations for a user. Finally, the /history endpoint returns all articles previously viewed by the user—and based on the articles they visited, recommend related stories. Parse.ly’s personalized content recommendations are pretty powerful, so you want to make sure you make the most of them! Here’s a step-by-step guide to create personalized newsletters with the Parse.ly API.

# Step 0: Information Swap: the unique user identifier

The email service provider (ESP) will need a list of your tracked domains. You and the ESP need to agree on a user identifier that both parties have access to, such as a hashed email address.

# Step 1: Build personalized profiles

Once you have a user identifier, you’re ready to build a profile around your users. This step will identify the user profile we’ll train the content recommendations for and for the sake of this post, we’ll call this user KA12345. When a visitor reads an article on your site, your server should make a call to our /profile endpoint using a unique user identifier. Any unique string will suffice, but remember: don’t send e-mail addresses unless they are hashed.

await fetch(`https://api.parsely.com/v2/profile?apikey=${YOUR_APIKEY}&uuid=${USER_IDENTIFIER}&url=${URL_BEING_VIEWED}`);

# Step 2: Fetch recommendations and build newsletter

Now we need to align that profile with the rest of the recommendations engine. When building a newsletter for user KA12345, we’ll fetch personalized recommendations using the /related endpoint for that user. Note: if no results are found, by default, we return the most recently published articles. This is so you never have a blank recommendations widget on your site or in your newsletters (unless you’re filtering out the returned articles). Nice, huh?

Respect empty results

If you’d like to change that, perhaps while you’re in the process of testing your personalized recommendations, try using the respect_empty_results parameter. This way, if the API doesn’t find any recommended articles, the endpoint will return an empty array:

let articles = await fetch(`https://api.parsely.com/v2/related?apikey=${YOUR_APIKEY}&uuid=KA12345&respect_empty_results=1`)
  .then(resp => resp.json())
  .then(resp => resp.data);

Filtering results with metadata

You can optionally filter the list of articles to include or exclude certain authors, sections, or tags. If, for instance, you wanted to ignore any content tagged as “wire”, we could use an exclude parameter (see the API docs for the format of the exclude parameter):

let articles = await fetch(`https://api.parsely.com/v2/related?apikey=${YOUR_APIKEY}&uuid=KA12345&respect_empty_results=1&exclude=tags%3A%22wire%22`)
  .then(resp => resp.json())
  .then(resp => resp.data);

Filtering results for recency

If there are no recommended articles returned, you could fall back to the most popular articles in the last two weeks:

if (articles.length === 0) {
  articles = await fetch(`https://api.parsely.com/v2/analytics?apikey=${YOUR_APIKEY}&secret=${YOUR_APIKEY_SECRET}&pub_date_start=2w&period_start=2w`)
    .then(resp => resp.json())
    .then(resp => resp.data)

The /analytics endpoint does not support the exclude parameter. If you need to filter articles, consider increasing the limit parameter to something like 500 posts, and manually filter each post based on your criteria.

# Step 3: Filter articles recently read (optional)

If you want to ensure that newsletters don’t contain previously delivered articles, we need to filter articles that were previously sent. Parse.ly offers a “hack” to make this work with the limitation that we can only remember articles viewed for a two-week period. If you need a longer timeframe than that, you’ll have to use your own state store to keep track of articles sent to each user. To filter out recently read articles for user ‘KA12345’ using the Parse.ly method, use the following:

const previouslyReadArticles = await fetch(`https://api.parsely.com/v2/history/apikey=${YOUR_APIKEY}&uuid=KA12345+newsletter`)
  .then(resp => resp.json())
  .then(resp => resp.data.urls);
articles = articles.filter(article => !previouslyReadArticles.includes(article.url))

Note: the unique user identifier we used was KA12345+newsletter. This works because the “hack” we’re using effectively trains a separate profile that records articles sent in newsletters. At first, this call will be empty, but we’ll train it in the next step.

# Step 4: “Train” newsletter profile

In Step 3, we asked for the history of user ID KA12345+newsletter, but that profile doesn’t exist yet. That’s because we’re creating it here. It’s a special profile we use just to maintain a list of URLs sent in newsletters over the past two weeks (which is the limit of the /history endpoint). Just before sending the newsletter, ensure this will “train” this special profile:

await Promise.all(
    article => fetch(`https://api.parsely.com/v2/profile?apikey=${YOUR_APIKEY}&uuid=KA12345+newsletter&url=${article.url}`)

# Step 5: Send newsletter

You now have a list of personalized articles you can send to the user!We’d love to hear about what you build! Tweet at us @parsely, or send us a note at hello@parsely.com.