Calm’s dynamic emails leverage the Iterable Catalog feature extensively to keep campaigns evergreen and easy to maintain. I use it for a variety of use cases:
- Email localization strings. Calm serves content in English, French, Spanish, Japanese, Korean, Portuguese, and German.
- Content calendars. Calm has a variety of content that releases on a regular cadence including the Daily Calm and the Daily Trip.
- Follow-up lessons. For special programs we send messages that remind listeners of key takeaways, lessons, or review material.
- Top N lists. The most recent Catalog I added allows others to curate lists of top/new/hot/trending content.
I have been most tickled with my most recent effort to store Top-N lists (Top 10 newest music, Top 10 most popular, Top 5 fill-in-the-blank) in our Catalog because it streamlines our process for creating ad-hoc special announcement emails. Creating this did not require a lot of code and makes me wish I had thought of doing this earlier.
Google Sheet as Content Management System (CMS)
Using a Google Sheet shared with my colleagues allows us to have a mix of curated and automated feeds controlled by different:
Automated feeds:
- the newest Sleep Stories for each language
- the newest Music releases for each language
- the newest Meditations for each language
- 3 content types * 7 languages = 21 individual feeds
Curated feeds
- featured English new releases
- English-language blog content
- special blocks for other departments to provide content (b2b, marketing, product, engineering)
The Catalog
Retrieve and Render Iterable Catalog Data
Using a known key, such as ennewSleep
I am able to retrieve the appropriate content for Korean language new Sleep Stories.
{{!-- look up new Sleep Stories in English (en) --}}
{{#catalog "Datafeeds" "ennewSleep" as |datafeed|}}
{{#lookup datafeed "programs" as |prog|}}
<h1>{{{ prog.title }}}</h1>
<p>{{{ prog.description }}}</p>
<hr />
{{/lookup}}
{{/catalog}}
Basic HTML result would be
<h1>Sleep story title 1</h1>
<p>Description of story 1</p>
<hr />
<h1>Sleep story title 2</h1>
<p>Description of story 2</p>
<hr />
Addendum: Roadblocks along the way
My initial intent for this project was to lean on Iterable Datafeeds and Google App Scripts to provide my dynamic data. I created a Google Apps Script to create a web app that would grab some API data, package it up into a more email-friendly rendering payload, and return JSON. I ran into 2 major problems while trying to set this up which eventually led to my Catalog solution.
First, I found out that I cannot change the user-agent
HTTP header when using the UrlFetchApp
library. UrlFetchApp
sends something like Google/app-script-client
when doing its HTTP fetch. I needed control of the user-agent
header to pull the different language content from the API. My solution to this was to set up a free Netlify account that would proxy a request Google -> Netlify -> Calm. I created the proxy as a serverless function. This only took a couple of hours thanks to Netlify for its very developer-friendly deployment tools.
My second problem is that Google App Script only serves a few requests per second (expected). I was expecting to lean on the caching option in the Iterable datafeed fetcher. My theory was Iterable would hit my endpoint once and cache the response for 1 hour as described in their documentation. Despite several attempts at configuring the templates and datafeeds I could not get Iterable to cache my feeds properly. During campaign send time Iterable would hit my Google App Script hundreds of times per second and fail out due to rate limiting problems.
Figuring that the caching roadblock would be unsolvable on my own, I decided to use the Iterable Catalog solution instead. The Catalogs are useful because I don’t have to worry about maintaining uptime on the services but not as nimble as a API solution because I have to constantly sync my Google Sheet to the Catalog(s).