How to make a serverless webhook for email alerts, using Gatsby Functions
Tags: gatsby, webdev, serverless, contentful
Published: 2021-10-14 06:50:56

**Using Gatsby Functions, it's easy to make a serverless function that automatically sends out email alerts triggered by an event - in our case when new content is published in Contentful (a headless CMS).**

In a series of articles, I have shown how I created a new website for the condominium where I live using React/Gatsby, Chakra UI and Auth0. Read part 1 of the series here: [How I built our condos's new web pages with Gatsby and Chakra UI](https://dev.to/klekanger/part-1-how-i-built-our-condos-s-new-web-pages-with-gatsby-and-chakra-ui-4jd5)

The main reason why we wanted a website in the first place, was the need for a place to publish new or important information to our residents. However, I quickly realized that it was cumbersome to manually send email to notify residents every time we had published new information in our headless CMS system.

Using [Gatsby Functions](https://www.gatsbyjs.com/docs/reference/functions/) it's quite easy to make an API that sends an email alert to subscribers. In this article I'll walk you through how I have set up a web hook where Contentful sends a POST request to my email alert API. The API checks if the request is allowed by verifying a secret key, and then fetches a list of all registered users and sends out an email alert to those users that have chosen to receive alerts.

For email I chose to use [Sendgrid](https://sendgrid.com/). I also considered [Nodemailer](https://nodemailer.com/about/), but Sendgrid has a nice email template feature where my API can fetch URL, article title and excerpt from Contentful, and pass this information along to the email template in Sendgrid. Then the user receiving the email can click on a link in the email to open the article that was just published on the condo's web site.

To make everything work, I had to set up the following:

* Create a webhook in Contentful
* Make a serverless function (our API) with Gatsby Functions 
* Create an email template in Sendgrid

What I have made could easily be adapted to other needs or purposes. If you're not using Gatsby, you don't need to make any big changes to make it work with [Netlify Functions](https://www.netlify.com/products/functions/) or [Next.js API routes](https://nextjs.org/docs/api-routes/introduction). The code is pretty similar to how you would create this using the Node.js [Express framework](https://expressjs.com/).

## Setting up a webhook in Contentful
All content on the condominium's website is [published in the headless CMS solution Contentful](https://dev.to/klekanger/part-2-a-new-web-page-for-our-condo-setting-up-gatsby-and-contentful-2k0j). Under the *Settings* menu in Contentful, you have an option called *Webhooks*. Here I created a webhook that will be triggered only when new content is published, and then send a POST request to the email API I intend to create.

After clicking *Add Webhook* and giving the webhook a name, I had to specify that it should be a POST request that will go to the URL of my email API.

![screenshot, set up webhook in Contentful.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0qq0zbeh8b1ejrefii9x.png)

Under *Content Events* I specify that the webhook should be called only when new content has been published.

![screenshot, configure triggers for webhook in Contentful.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jpkmcn9krstdtjtduzoq.png)

I don't want everyone to be able to make requests to my API and spam my users with email. So I created a *secret header* which is a key/value pair that is sent in the header of the API request. A secret key the API will verify before allowing email to be sent.

I called my secret key `CONTENTFUL_WEBHOOK_SECRET` and used a password generator to make a long password with numbers, letters and special characters. 

![screenshot, add secret header in Contentful.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0dg18l472rcjoyduha2l.png)

## Create the email API using Gatsby Functions

[Gatsby Functions](https://www.gatsbyjs.com/docs/reference/functions/) is an Express-like way of building APIs for your Gatsby projects. It's *serverless functions*, which of course does not mean that you don't need servers, but you don't have to *think* about the servers. Just focus on writing code - just the way I like it.

To make a new Gatsby Function you create a JavaScript or TypeScript file in the folder `/src/api` and export a `handler` function which takes two parameters - `req` (request) and `res` (response).

I created a file called `email-alert-users.ts` in the `api/admin-users` folder (use .js if you do not use TypeScript). This means that my API endpoint will be `https://url-to-my-site-admin/admin-users/email-alert-users`.

In the first part of the code, I first check that the HTTP method is POST. Then I check that the header contains `CONTENTFUL_WEBHOOK_SECRET` and that it matches the secret key I have stored as an environment variable (locally in a .env file, and at Netlify under *Deploy Settings* and *Environment*. Make sure you do not submit this to Github - check that you have .env* in your .gitignore file.

Here is the first part of the code, with the necessary checks:

```TypeScript
// src/api/admin-users/email-alert-users.ts

import { GatsbyFunctionRequest, GatsbyFunctionResponse } from 'gatsby';
const ManagementClient = require('auth0').ManagementClient;
const sgMail = require('@sendgrid/mail');

export default async function handler(
  req: GatsbyFunctionRequest,
  res: GatsbyFunctionResponse
) {
  if (req.method !== `POST`) {
    return res.status(405).json({
      error: 'method not allowed',
      error_description: 'You should do a POST request to access this',
    });
  }

  // Check if secret key received from Contentful web hook matches the one in the .env file
  if (
    req.headers.contentful_webhook_secret !==
    process.env.CONTENTFUL_WEBHOOK_SECRET
  ) {
    return res.status(401).json({
      error: 'unauthorized',
      error_description: 'The Contentful web hook secret key is not correct',
    });
  }

// ...code continues
```

If you're not using TypeScript, remove `GatsbyFunctionRequest` and `GatsbyFunctionResponse`, this is just for type checking.

At the top I import `ManagementClient` to be able to connect to Auth0's Management API, so that I can fetch a list of all users who should receive email. You can of course get this user list from other places as well, such as a database.

## Set up the email contents
The emails should contain the following, in addition to a standard text stating that there is new content (I set this up at Sendgrid):

* Title and introduction
* URL of the article

Some of the content on the condominiums website is only available to logged in users, using [client-only routes](https://dev.to/klekanger/part-3-authentication-and-private-routes-in-gatsby-46jc) in my Gatsby project, more specifically under the route `/information`. All open articles are under `/blog`. This means that I must first check if it is a private article or not, and then build the correct URL based on this. When the Contentful webhook makes a POST request to my API, it passes on lots of info about what is published in the `body` of the request. I have created a field in Contentful called `privatePost` that can be set to true or false (this is a checkbox you tick when you write an article in the Contentful editor). This makes it easy to create the correct URL by letting the API check this field first.

I also retrieve the article title and excerpt (short intro about the article) in this way:

```TypeScript
// src/api/admin-users/email-alert-users.ts

  let articleURL;
  if (req.body.fields.privatePost.nb === true) {
    articleURL = `https://gartnerihagen-askim.no/informasjon/post/${req.body.fields.slug.nb}/${req.body.sys.id}`;
  } else {
    articleURL = `https://gartnerihagen-askim.no/blog/${req.body.fields.slug}/${req.body.sys.id}`;
  }

  const articleTitle = req.body.fields.title.nb;
  const articleExcerpt = req.body.fields.excerpt.nb;

// ...code continues
```

## Get all email subscribers
Now that the email content has been retrieved, I connect to Auth0's Management API and request the permission ("scope") `read:users`. Then I use the `getUsers()` method to fetch all the users. Then I use a combination of `.filter` and `.map` to create a list of only users who have subscribed to email. I have stored this information in the `user_metadata` field for each user in Auth0.

I now have a list of all users who want email. I use `sgMail.setApiKey(process.env.SENDGRID_API_KEY)` to send an API key to access my Sendgrid account. Then I set up the email content and store it in the `msg` constant. `msg` contains an array of email recipients, the sender's email address, and a `templateId`. The latter is the ID of an email template I have set up at Sendgrid. Under `dynamic_template_data` I can pass along information about the article URL, title and excerpt of the article, which will then be captured by Sendgrid and filled out in the email template.

Finally, the email is sent with `sgMail.sendMultiple (msg)`.

```TypeScript
// src/api/admin-users/email-alert-users.ts

// Connect to the Auth0 management API
  const auth0 = new ManagementClient({
    domain: `${process.env.GATSBY_AUTH0_DOMAIN}`,
    clientId: `${process.env.AUTH0_BACKEND_CLIENT_ID}`,
    clientSecret: `${process.env.AUTH0_BACKEND_CLIENT_SECRET}`,
    scope: 'read:users',
  });

  try {
    const users = await auth0.getUsers();

    // Filter out only those users that have subscribed to email alerts
    // This is defined in the user_metadata field on Auth0
    const userEmails = users
      .filter((user) => {
        return (
          user &&
          user.user_metadata &&
          user.user_metadata.subscribeToEmails === true
        );
      })
      .map((user) => user.email);

    // using Twilio SendGrid's v3 Node.js Library
    // https://github.com/sendgrid/sendgrid-nodejs
    sgMail.setApiKey(process.env.SENDGRID_API_KEY);

    const msg = {
      to: userEmails,
      from: 'Boligsameiet Gartnerihagen <post@gartnerihagen-askim.no>',
      templateId: 'd-123456789',  // The ID of the dynamic Sendgrid template
      dynamic_template_data: {
        articleURL,
        articleTitle,
        articleExcerpt,
      },
    };

    await sgMail.sendMultiple(msg);

// ...code continues
```

If everything has worked as expected, I return status 200 and some info. I do everything inside a try/catch block, and in case of an error I return an error code.

```TypeScript
res.status(200).json({
      body: {
        status_code: 200,
        status_description: 'Emails are sent to all subscribed users',
        userEmails,
      },
    });
  } catch (error) {
    res.status(error.statusCode).json({
      error: error.name,
      message: error.message,
      status_code: error.statusCode || 500,
      error_description: error.message,
    });
  }
}
```

## Configuring the Sendgrid email template

I could of course "hard code" the email contents, but with "dynamic Templates" in Sendgrid you can customize the email with the information you want. In my case information about the article that was published, including an URL.

To set up a dynamic email template in Sendgrid, go to [sendgrid.com](http://sendgrid.com/), log in and select *Email API* → *Dynamic Templates* from the settings menu. Then tap *Create a Dynamic Template*, then *Add Version*. You can now choose to use Sendgrid's design editor to design your email, or you can use an HTML editor. I used the HTML editor and added some standard text.

To insert dynamic content in the text - such as title or URL- you can use variable names. In my email API, I use the variable name `articleTitle` for the article title I receive from my CMS. To use this variable in the Sendgrid template, just insert `{{articleTitle}}` into the HTML code.

On the right hand side (see screenshot below) you can see what the email will look like. If you press *Test Data* in the menu at the top, you can enter test data for the various variables you use in the email template to check that everything looks OK.

![Screenshot, html editor in Sendgrid ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9oqqgzzc173akxx28cd9.png)

That's it! We now have created an email alert service that sends out an email every time new content is published in our headless CMS.

I also updated the user admin dashboard for our web pages, so that admins can turn on or off email alerts for users. The users are also able to do this by themself on their "My page" when logged in:

![Screenshot, mypage on the condominiums web page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xrghma9oy3jf1y8cikof.png)

The source code for this project is on [my Github](https://github.com/klekanger/gartnerihagen). If you're just interested in the email alert Gatsby Function, you'll find it [here](https://github.com/klekanger/gartnerihagen/blob/master/src/api/admin-users/email-alert-users.ts).

In this article you can read about [how I made a user admin dashboard with Gatsby Functions and Auth0](https://dev.to/klekanger/part-5-making-a-user-admin-dashboard-with-gatsby-functions-and-auth0-4ncn)

_This is a translation, original article is published on my personal web site [lekanger.no](https://www.lekanger.no/project/lag-en-serverless-webhook-som-sender-epost-nar-nytt-innhold-er-publisert)_