Adding a contact form to a static site using Netlify Functions
Tags: javascript, gatsby, serverless, netlify
Published: 2020-07-26 16:16:34

That you can spice up a static site with dynamic functionality using so-called serverless functions is not that new anymore. Many have probably first learned about them through [AWS Lambda functions](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html). But they are not tied to any specific cloud provider:

> Conventionally, serverless functions are single-purpose, programmatic functions that are hosted on managed infrastructure. These functions, which are invoked through the Internet, are hosted and maintained by cloud computing companies.
>
> [What is a Serverless Function?](https://www.pubnub.com/blog/what-is-a-serverless-function/)

If you are hosting any of your projects on Netlify, you can use their offering which is simply called [functions](https://www.netlify.com/products/functions/). One interesting bit here is that they are actually AWS Lambda functions:

> Netlify lets you deploy serverless Lambda functions without an AWS account, and with function management handled directly within Netlify. Your serverless functions are version-controlled, built, and deployed along with the rest of your Netlify site, and we will automatically handle service discovery through our built-in API gateway.

In other words, Netlify abstracts some of the complexity around AWS Lambda away for you, and thus makes it very easy to get started with serverless functions. Also, their free tier is pretty generous.

## Setup

For this post, I'm assuming you have a running [Gatsby](https://www.gatsbyjs.org/) site hosted on Netlify to which you want to add a contact form. This implies that you are familiar with React, particularly with functional components and hooks, and also [Yarn](https://yarnpkg.com/).

I'm going to explain how to set up Sendgrid as the service provider and create a serverless function that is calling their API when your contact form is submitted.

If you are using a different [static site generator](https://www.staticgen.com/) and/or another transactional email service (Mailgun, Mailjet, etc.), this post should still help you with understanding how to create a contact form using Netlify functions.

## Signing up for Sendgrid

If you don't have one already, you need to create a [Sendgrid](https://sendgrid.com/) account. Their free plan currently allows you to send 40,000 emails during the first 30 days. After this period you can still send 100 emails per day "forever". This should be more than enough for running a contact form on a small private site.

Just go to [https://signup.sendgrid.com/](https://signup.sendgrid.com/) and follow the instructions.

## Sendgrid Web API

What allows us to send our contact form using a serverless function is Sendgrid's Web API. It supports several programming languages, including the one we want: Node.js. You can directly navigate to https://app.sendgrid.com/guide/integrate/langs/nodejs which outlines the process for using the API with Node.js:

1. Make sure you have Node.js installed
2. Create an API key
3. Create an environment variable
4. Install the package
5. Send your first email

I'm assuming you already have Node.js installed. So, as a first step you have to enter a name for your API key. That name is not overly important, simply choose something that makes sense to you. Once you did that, hit the `Create Key` button.

![Creating an API key in Sendgrind](https://dev-to-uploads.s3.amazonaws.com/i/lu0u414evt0ipj8gbd2x.png)

Now, the next step says you should create an environment variable for the key. But we're gonna come back to that later and instead continue with installing the Sendgrid npm package.

Inside your project folder run:

```javascript
yarn add @sendgrid/mail
```

The final step contains the code for sending a message to their API using that npm package. It's pretty simple:

```javascript
// using Twilio SendGrid's v3 Node.js Library
// https://github.com/sendgrid/sendgrid-nodejs
const sgMail = require('@sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
  to: 'test@example.com',
  from: 'test@example.com',
  subject: 'Sending with Twilio SendGrid is Fun',
  text: 'and easy to do anywhere, even with Node.js',
  html: '<strong>and easy to do anywhere, even with Node.js</strong>',
};
sgMail.send(msg);
```

But before we can actually make our first call, we have to do a few more things.

## Installing Netlify CLI

When I was looking for information on how to add a contact form to my Gatsby site on Netfliy, I only found solutions involving [netlify-lambda](https://github.com/netlify/netlify-lambda). But if you don't need a build-step for your function, like when you are using TypeScript, then you can achieve the same in a simpler way using [Netlify Dev](https://github.com/netlify/cli). More about the differences between these two tools can be found in the [netlify-lambda docs](https://github.com/netlify/netlify-lambda#when-to-use-netlify-dev-or-netlify-lambda-or-both).

Since Netlify Dev comes as part of [Netfliy CLI](https://cli.netlify.com/), you have to install it globally first:

```javascript
yarn global add netlify-cli
```

You can also do this using Homebrew, if you're on a Mac:

```javascript
brew install netlify-cli
```

## Creating evironment variables in Netlify

Especially if you commit your code into a public repo, you don't want your API key to be visible there. A common way to keep confidential information out of your code is to store it in environment variables.

> Environment variables are variables external to our application which reside in the OS or the container of the app is running in. An environment variable is simply a name mapped to a value.
>
> [Managing Environment Variables in Node.js with dotenv](https://stackabuse.com/managing-environment-variables-in-node-js-with-dotenv/)

First, we're gonna store them directly in Netlify, which is the production environment.

When you are logged in to Netlify, go to `Settings > Build & Deploy > Environment`.

![Form for setting environment variables in Netlify](https://dev-to-uploads.s3.amazonaws.com/i/ubi4tfz2j0rjc0zg8q7z.png)

We have to add two variables: `SENDGRID_API_KEY` and `SENDGRID_TO_EMAIL`.

Add the API key you generated earlier as the value of `SENDGRID_API_KEY`, and the email address to which the messages from the contact form should be delivered as the value of `SENDGRID_TO_EMAIL`. Don't forget to save.

## Making the environment variables available locally

To test our code while developing, we also need to make the environment variables available locally. In [Gatsby](https://www.gatsbyjs.org/docs/environment-variables/#client-side-javascript) we can do this by creating a `.env.development` file. It contains the same keys and values you put into Netfliy:

```yaml
SENDGRID_API_KEY="your_api_key"
SENDGRID_TO_EMAIL="your_email@provider.com"
```

You're gonna see how these are being accessed in the code of the function, which we are going to create next.

## Adding the serverless function

Now you have to create a new folder inside your `src` folder, where the serverless function(s) are going to live. I named mine `functions`, but it can be chosen freely. The latter also goes for the name of the file that holds your function.

Here is the complete code:

```javascript
const envConfig = require('dotenv').config({ path: `.env.${process.env.NODE_ENV}` });
const sgMail = require('@sendgrid/mail');
const { SENDGRID_API_KEY, SENDGRID_TO_EMAIL } = process.env;

exports.handler = async event => {
  const payload = JSON.parse(event.body);
  const { email, message } = payload;

  sgMail.setApiKey(SENDGRID_API_KEY);

  const msg = {
    to: SENDGRID_TO_EMAIL,
    from: email,
    subject: `New message from yourdomain.com`,
    text: message,
  };

  try {
    await sgMail.send(msg);

    return {
      statusCode: 202,
      body: 'Message sent',
    };
  } catch (error) {
    const statusCode = typeof error.code === 'number' ? error.code : 500;

    return {
      statusCode,
      body: error.message,
    };
  }
};
```

Gatsby comes with a package called [dotenv](https://www.npmjs.com/package/dotenv) that loads environment variables into Node's `process.env`, which is ["an object containing the user environment"](https://nodejs.org/docs/latest/api/process.html#process_process_env). So if you don't use Gatsby, you might have to install it first.

Accordingly, the first line of code uses `dotenv` to load the `.env.development` file we created earlier:

```javascript
const envConfig = require('dotenv').config({ path: `.env.${process.env.NODE_ENV}` });
```

After doing so, our environment variables are available as properties on `process.env`:

```javascript
const { SENDGRID_API_KEY, SENDGRID_TO_EMAIL } = process.env;
```

Exporting a `handler` method is a [convention for Netlify functions](https://docs.netlify.com/functions/build-with-javascript/#format). The one I defined is [asynchronous](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function). This is so that we can `await` the result of the call to Sendgrid, which returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). The method will be provided three arguments:

> Netlify provides the event and context parameters when the serverless function is invoked. You provide the callback parameter, which is optional, but recommended.

We only really need the `event` parameter though, which provides us with the email and message entered into the contact form:

```javascript
const payload = JSON.parse(event.body);
const { email, message } = payload;
```

Apart from that, the function basically just contains the code we saw in Sendgrid earlier, combined with a `try/catch` block for handling success and error cases when calling their API. In either case, an object with a `statusCode` and `body` property is returned for use in the contact form.

## Configuring Netlify

To make Netlify aware of any functions existing in your codebase, you can either use the Netlify UI or the [file-based configuration](https://docs.netlify.com/configure-builds/file-based-configuration/). The latter works by placing a `netlify.toml` config file at the root of your project, which is what I'm going to do here. In our case the content is very simple:

```toml
[build]
  command = "yarn build"
  functions = "src/functions/"
  publish = "public/"
```

`command` tells Netlify which command to use for running the build.

`functions` contains the path to the functions folder that you created earlier. If you chose something else than `functions`, you have to adapt the path accordingly.

`publish` specifies where the output of the build process is supposed to be placed. For a Gatsby site, this is typically the `public` folder.

## Creating the contact form

With the function and configuration in place, we can now add the contact form. I placed it inside `src > components > ContactForm.js`:

```javascript
export const ContactForm = () => {
  const [isPosting, setIsPosting] = useState(false);
  const [postingError, setPostingError] = useState(false);
  const [postingSuccess, setPostingSuccess] = useState(false);
  const emailEl = createRef();
  const messageEl = createRef();
  const postMail = async () => {
    const email = emailEl.current.value;
    const message = messageEl.current.value;

    setIsPosting(true);

    try {
      const res = await fetch('/.netlify/functions/sendmail', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          email,
          message,
        }),
      });

      if (!res.ok) {
        setPostingError(true);
      } else {
        setPostingSuccess(true);
      }      
    } catch (e) {
      setPostingError(true);
    } finally {
      setIsPosting(false);
    }
  };

  const handleSubmit = e => {
    e.preventDefault();

    postMail();
  };

  return (
    <>
      {postingSuccess ? (
        <SuccessMessage>Message sent. Thank you!</SuccessMessage>
      ) : (
        <form method="post" onSubmit={handleSubmit}>
          <Fieldset>
            <Legend>Contact form</Legend>
            <Label htmlFor="email">Your email address*</Label>
            <Input
              type="email"
              aria-label="Your email address"
              name="email"
              id="email"
              placeholder="Email address"
              ref={emailEl}
              disabled={isPosting ? 'disabled' : undefined}
              required
            />
            <Label htmlFor="message">Your message*</Label>
            <Textarea
              ref={messageEl}
              id="message"
              aria-label="Your message"
              placeholder="Message"
              disabled={isPosting ? 'disabled' : undefined}
              rows="5"
              required
            />
            <Button disabled={isPosting ? 'disabled' : undefined}>Send</Button>
          </Fieldset>
        </form>
      )}
      {postingError ? <ErrorMessage>Something went wrong, please try again (later).</ErrorMessage> : null}
    </>
  );
};
```

This creates a simple mail form with an email and message field defined as [uncontrolled components](https://reactjs.org/docs/uncontrolled-components.html). I'm not going to go into the specifics of the React code here as that's not the point of this post.

The important bit is that when the form is submitted, the `postMail` function gets called inside the event handler, which makes a [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) call to `'/.netlify/functions/sendmail'`. This path adheres to the following pattern:

```javascript
https://[YOUR_URL]/.netlify/[FUNCTION_FOLDER]/[FUNCTION_NAME]
```

This is of course going to trigger our function and, given that there is no error, send the data from the contact form to Sengrid, which in turn will process and send it to the email address defined in `SENDGRID_TO_EMAIL`.

## Testing your contact form

Now you can test your form. Just run `netlify dev` inside your project folder. This is going to start a server on `http://localhost:8888`. If you then navigate to the page with your form, fill it out and submit, a mail should be sent to the email address you set in the `SENDGRID_TO_EMAIL` variable.

After you push your code and the build on Netlify is successful (if you have set up [continuous deployment in Netlify](https://docs.netlify.com/site-deploys/create-deploys/#deploy-with-git), the build will start automatically after pushing), the form should also work in production.

*(Cover image on top by [Filiberto Santillán on Unsplash](https://unsplash.com/@filisantillan?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText))*