Search by Algolia

Sorry, there is no results for this query

How to send engaging customer emails with Twilio SendGrid and Algolia Recommend

Oct 19th 2021 engineering

How to send engaging customer emails with Twilio SendGrid and Algolia Recommend

If you’re going to send an email to your customer, it better be relevant. Product recommendations are a great way to create engaging emails for your customers to invite them back to your site. Algolia Recommend builds personalized product recommendations by training AI models on customer site analytics. In this blog you’ll see how to build a back-end service that crafts tailored recommendation emails and sends them to your customers using Twilio SendGrid.

What we’re building with Twilio and Algolia

For this demo, we’ll be building an engagement email service for an e-commerce site. We’ll explore recommendation strategies that become more tailored as we layer in more features and analytics based on customer interactions with our site.

The four customer scenarios are as follows:

  1. They browse a category on our site but do not make a purchase
  2. They buy a product and we recommend other products that are often bought with it
  3. They buy a product and we recommend other products that are similar or related
  4. They have an existing user profile on our site

We’ll rely on the following technologies:

We’ll build the service in four steps:

  1. Set up Algolia Search/Recommend and configure the API
  2. Examine the back-end node application used to generate recommendation emails
  3. Set up Twilio SendGrid and configure their API
  4. Run the service to send emails

The setup

We’ll build our service as an express app using node. You can clone the solution repository to follow along.

git clone

Our service follows the typical structure of an express application written in Node.js. The server code lives in server/node:

├── emails
│   ├── 1
│   │   ├──
│   │   └── email.js
│   ├── 2
│   │   ├──
│   │   └── email.js
│   ├── 3
│   │   ├──
│   │   └── email.js
│   └── 4
│       ├──
│       └── email.js
├── package-lock.json
├── package.json
├── sendgrid.js
├── server.js
└── templates
    ├── base.html
    ├── partials
    │   ├── products_3_columns.html
    │   └── products_full_width.html
    ├── post_order.html
    ├── pre_order.html
    └── re-engagement.html

The server.js file has our routing, API initialization, and page rendering. The templates/ directory is for our Cerberus email templates. The actual contents of our four engagement emails live in the emails/ directory. We manage sending emails in sendgrid.js.

Before we can continue, we need to install our dependencies from our package.json.

cd server/node
npm install

Now we’re ready to start configuring the various APIs our service relies on. We copy the file .env.example from the root of this project to the directory of the server we want to use and rename it to .env. For example, to use the Node implementation:

cp .env.example server/node/.env

Our .env file has some keys that need we need to assign:

# Algolia credentials

# SendGrid credentials

# Envs

Let’s start with our data and Algolia credentials.

Setting up Algolia

To follow along, you’ll need an index under your existing Algolia account or you can sign up for a free account and set up an application. For the examples in this blog, we’ll be using an existing demo site.

Index and credentials

This demo assumes you have a pre-existing index with enough event data to use Algolia Recommend. If you don’t have an index yet or don’t have enough events for recommendations, you can still use the strategies that don’t require trained machine learning models. Just follow these steps to create and populate an index.

Once we have our index we can set the ALGOLIA_INDEX_NAME and ALGOLIA_APP_ID environment variables in our .env file. We’ll also set ALGOLIA_API_KEY to our search-only API key. We can find this info on API Keys page of our Algolia dashboard.


While we’re on the API Key page, we should create another key to use for recommendations. From the API Keys page, we navigate over to the All API Keys tab and click New API Key, choose our index, and add recommendation to our ACL. Now we can add this key to our .env file.


Verifying our models

Once all the Algolia pieces are in place, let’s make sure our Recommend models are trained and ready. We click on the Recommend light bulb icon on the Algolia dashboard and look at the “Where is Recommend active?” section. If the models are not already trained, we’ll need to train the models.

It’s ok if we don’t have enough events to train our models. We can still use the templates that don’t need Algolia Recommend.

Setting up Twilio SendGrid

We use Twilio SendGrid as the email provider to send our rendered previews. You’ll need to sign up with SendGrid if you don’t have an account already, then create and store a SendGrid API key with permissions to send emails. You’ll also need to validate you email domain or single sender email before you’ll be able to send outbound mail.

Add your SENDGRID_API_KEY and validated SENDGRID_FROM_EMAIL in the .env file:


Generating engagement emails

With all our APIs configured, we’re ready to dig into the server code starting with server.js. Here we set up our express server and initialize all our API clients:

// Setup Express, markdown renderer and Algolia clients.
const app = express();
const converter = new showdown.Converter();
const algoliaClient = algoliaearch(process.env.ALGOLIA_APP_ID, process.env.ALGOLIA_API_KEY)
const recommendClient = algoliarecommend(process.env.ALGOLIA_APP_ID, process.env.ALGOLIA_API_KEY);
const algoliaIndex = algoliaClient.initIndex(process.env.ALGOLIA_INDEX_NAME);

// Setup email delivery

In this demo we generate and send emails interactively, so we want to serve up our client application.

// Home page
app.get("/", (req, res) => {
    const indexPath = path.resolve(process.env.STATIC_DIR + "/index.html");

Each of our engagement scenarios has an email template and a describing the scenario. The client loads the four templates out of the emails/ directory and puts them in a list to display in the client application.


const loadEmails = async () => {
    const EmailBaseDir = "./emails";
    const emailDirs = await fs.promises.readdir(EmailBaseDir);
    const emails = [];
    for(const emailDir of emailDirs) {
        const emailDirPath = path.join(EmailBaseDir, emailDir);
        const explanationFile = await fs.promises.readFile(path.join(emailDirPath, ""), "utf8");
        const explanation = converter.makeHtml(explanationFile);
        const { email } = await import(`./${path.join(emailDirPath, "email.js")}`);
    return emails;

After selecting a scenario from the drop down, the client renders the selected email using nunjucks, the appropriate Cerberus email template from the templates/ directory, and the underlying API calls needed to generate the recommendations for that scenario.

If we like the look of the rendered email, we can send it to ourselves using SendGrid. We type our email address into the Send this email field and click Send. Our service will pass the rendered email to the SendGrid API and mail it to us!

Now that we understand the general pattern, let’s dive into how we construct the recommendations for each of our four scenarios.

Scenario 1: A customer browses a category

In this scenario, a customer browses a category on your site but doesn’t buy anything. We don’t know much about the customer or the specific item they are looking for. We use Algolia’s Faceting to recommend the best rated products from that category.

If the customer browses for men’s t-shirts, we call the Algolia Search API and request the first six items for this category using the index’s ranking criteria.

    async (algoliaIndex, _) => {
      // The customer browsed the Men / Tee-shirt category
      const facetFilters = ["category:Men - T-Shirts"];
      // Get the top-rated products from the category
      const { hits } = await"", { facetFilters, hitsPerPage: 6 });

We use SendGrind to email these suggestions to the customer with links back to our site to continue browsing.

This is a great fallback strategy when we do not have enough information about the customer or the particular product they are looking for. It doesn’t require event data and relies only on category information within the index itself.

Scenario 2: Recommend products “frequently bought together” with the one our customer just purchased

In this scenario, the customer has purchased a product from our site. We’ll suggest products other customers have often bought along with it. We’ll use Algolia Recommend to train a machine-learning algorithm based on event data of customer purchases.

With our models already trained, we’ll only need a few lines of code to get from the current product to a list of recommended products based on the purchase history of other customers on our site.

      const orderContent = ["D07940-6931-990"];
      // Get the similar items for this item.
      const { results: [ { hits }, ]} = await recommendClient.getRecommendations([{
        indexName: process.env.ALGOLIA_INDEX_NAME,
        objectID: orderContent[0],
        model: "bought-together",
        maxRecommendations: 3,

Again, we use SendGrind to email these suggestions to the customer with links back to our site to continue browsing.

Scenario 3: Recommend products related to the one our customer just purchased

This is similar to scenario two. The customer has just purchased a product from our site. This time we’ll use this information to suggest products related to the one they purchased. We’ll use the Algolia Recommend related-products model this time.

As with the previous scenario, since our models are already trained, we’ll only need a few lines of code to get from the current product to a list of recommended products based on the purchase history of other customers on our site.

      const orderContent = ["D07940-6931-990"];
      // Get the related product for the bought product.
      const { results: [ { hits }, ]} = await recommendClient.getRecommendations([{
        indexName: process.env.ALGOLIA_INDEX_NAME,
        objectID: orderContent[0],
        model: "related-products",
        maxRecommendations: 3,
        fallbackParameters: {facetFilters:["category:Women - Jeans"]}

Notice here we include a fallback parameter. We can fill in our recommendation list with top items from this category if there aren’t enough recommendations from the model.

Now we can email the recommendations to the customer using SendGrind.

Scenario 4: Personalized recommendations

In this final scenario, a customer has visited the site before. As they navigated our site, we captured their click and conversion actions using Algolia Insights. Algolia takes these events and builds a Personalization profile for the customer. We will use this profile along with the Recommend API to find related products that match the customer’s affinities.

First, we use the unique token our site uses for this customer to retrieve their personalization profile from the Algolia Personalization endpoint. (Note that we use the Recommendation API credentials to retrieve this profile.)

    async (algoliaIndex, recommendClient) => {
      // Get the customer's affinities profile
      const userToken = 'user-1';
      const res = await fetch(
          headers: {
            "X-Algolia-API-Key": process.env.ALGOLIA_RECOMMENDATION_API_KEY,
            "X-Algolia-Application-Id": process.env.ALGOLIA_APP_ID
      const { scores } = await res.json();

The response is the customer’s profile in JSON format. It will look something like this:

  "userToken": "user_1",
  "lastEventAt": "2019-07-12T10:03:37Z",
  "scores": {
    "category": {
      "Women - Jeans": 1,
      "Men - T-Shirts": 10
    "location": {
      "US": 6

This profile includes a list of categories from our site ranked by affinity score. We’ll take the top-ranked categories and turn them into facets that the user can use to search for products.

      // Transform the top customer affinities into a list of `facetFilters`
      let facetFilters = [];
      for (const key in scores) {
        const score = scores[key];
        let values = [];
        for (const valueKey in score) {
          values.push([ valueKey, score[valueKey] ]);
        values.sort((kv1, kv2) => kv2[1] - kv1[1]);

Algolia Recommend provides recommendations based on individual products, not categories. We’ll retrieve the top products from each of the facets we know our customer likes, then send them to Algolia Recommend to find similar products.

      // Get the top-rated products matching the top-scoring customer profile facets
      const { hits: [topHit,] } = await"", { facetFilters, hitsPerPage: 6 });
      // Get the related items matching the top-rated product
      const { results: [ { hits }, ]} = await recommendClient.getRecommendations([{
        indexName: process.env.ALGOLIA_INDEX_NAME,
        objectID: topHit.objectID,
        model: "related-products",
        maxRecommendations: 3

Now we have engaging results by combining our knowledge of the customer’s habits with the buying behaviors of other customers on our site.

Once again, we add links back to our site and send the email out using SendGrid.


You can generate engaging customer recommendation emails based on knowledge of your products and your users’ behavior. Machine learning models like those in Algolia Recommend can bring even more relevance into your emails. Now you have the tools to go build a recommendation email service of your own!

Want to do more with recommendations? Take a look at our code blocks using Twilio or, more broadly, our code blocks using Algolia Recommend. Check out related solutions on our open source code exchange platform.

About the author
Chuck Meyer

Sr. Developer Relations Engineer


Recommended Articles

Powered byAlgolia Algolia Recommend

Introducing Algolia Recommend: The next best way for developers to increase revenue

Matthieu Blandineau

Sr. Product Marketing Manager

Building a Store Locator in React using Algolia, Mapbox, and Twilio – Part 3

Clément Sauvage

Software Engineer, Freelance

How to build a simulator for any feature — Part 2: Browser emulation

Jaden Baptista

Technical Writer