How to Send Emails with SendGrid and Next.js Serverless Functions for a Contact Form

Being able to communicate with others is a critical component of what makes people and businesses collaborate on the web. That usually starts with having some kind of form or interaction that triggers a notification or email. How can we use SendGrid to make sure those emails get delivered?

What's Inside 🧐

What is SendGrid?

SendGrid is an email delivery service that specializes in it’s ability to programmatically send emails through its API.

It offers a range of features along with it including an SMTP service, templating, and email validation, but for our purposes, we just need the ability to send an email with it’s easy to use API!

What are Next.js API routes?

To send those emails, we’re going to take advantage of Next.js API routes and build an API endpoint to handle interacting with the SendGrid API.

Next.js API routes are a feature that comes build into the React framework Next.js that ultimately allows the ability to deploy serverless functions that can be accessed via a URL.

While we could use the SendGrid API directly inside of a clientside application, we’ll be working with an API key to interact with the SendGrid API, which we absolutely can’t expose to the public.

Using a serverless function allows us to use that API key securely as a serverside request while still being able to safely trigger it from our application.

What are we going to build?

We’re going to start with a basic Next.js application with a form. Contact forms are a pretty common way to communicate with others whether you’re an individual or business giving people a way to get in touch.

To send our emails, we’ll first learn how to configure a domain to send emails from SendGrid, then create a new Next.js API route, where we’ll install the SendGrid SDK allowing us to programmatically send emails.

Note: we’ll be configuring SendGrid with a custom domain. There may be options to not use a custom domain, but it’s definitely recommended to set up your own

You’ll need to have a SendGrid account to work through this tutorial. You can sign up free

Quick note about spam and security

Before we dive in, you should know that what we’re walking through today doesn’t have any measures in it to prevent spam.

It’s pretty common for an open web form on the internet to get robots trying to send mail through it. This can also include being able to programmatically send messages to the endpoint.

Be sure to set up some form of spam prevention and security mechanism if you intend on using this solution as any type of open form on the web.

Step 0: Setting up a new Next.js app with a basic form

We’re going to start off with a new Next.js app using Create Next App.

Inside of your terminal, run:

yarn create next-app my-email-app
# or
npx create-next-app my-email-app

Note: feel free to use a different value than my-email-app as your project name!

Once installation has finished, you can navigate to that diretory and start up your development server:

cd my-email-app

yarn dev
# or
npm run dev

And once loaded, you should now be able to open up your new app at http://localhost:3000!

Now before we dive into setting up email, we’ll want a form in our app that will allow us to easily test sending emails.

To do this, inside of our homepage at pages/index.js, I’m going replace everything inside of the div with the classname of grid with:

<div className={styles.grid}>
  <style jsx>{`
    form {
      font-size: 1.2em;
    }

    label {
      display: block;
      margin-bottom: .2em;
    }

    input,
    textarea {
      width: 100%;
      padding: .8em;
    }

    button {
      color: white;
      font-size: 1em;
      background-color: blueviolet;
      padding: .8em 1em;
      border: none;
      border-radius: .2em;
    }
  `}</style>
  <form method="post">
    <p>
      <label htmlFor="name">Name</label>
      <input id="name" type="text" name="name" />
    </p>
    <p>
      <label htmlFor="email">Email</label>
      <input id="email" type="text" name="email" />
    </p>
    <p>
      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" />
    </p>
    <p>
      <button>Submit</button>
    </p>
  </form>
</div>

What we’re doing here is:

  • Setting up a basic HTML form with a few standard inputs you might find inside of a contact form
  • Adding a very basic set of styles that make the form a little bit easier to read and use

While I’m in here, I’m going to update the title and the description at the top of the page. Feel free to customize the page as you’d like before heading to the first step.

Contact form in Next.js app
New Next.js application with basic form

And with that we’re ready to get started!

Follow along with the commit!

Step 1: Adding a new Next.js API route to send form submissions to

For our contact form, we’ll be using a Next.js API route to securely send emails with the SendGrid API.

Next.js by default comes with an example API route that we can take advantage of to get started.

You can test this out by navigating to http://localhost:3000/api/hello inside of your browser, where you should see a response with a simple example object.

JSON response from Next.js API
Example Next.js API route response

If we look inside of the code under pages/api/hello.js , we can see exactly how this is working, where we’re exporting a default function, which will serve as our “handler” for our serverless function, where we use the res or response object to set a status of 200 (or a successful “Ok”) and return a static JSON object.

We’re going to transform this default function into our own, so the first thing we can do is rename this file to something that makes sense, so let’s rename:

pages/api/hello.js

To:

pages/api/mail.js

If we now visit http://localhost:3000/api/mail in our browser, we should see the exact same “John Doe” object as we did before.

Note: we could also create a new file in addition to hello.js, but because we’re not actually doing anything with the original, we can just start from there.

Before we move on, we can also update the response to something that makes a little more sense instead of “John Doe”.

Let’s update the response JSON to:

res.status(200).json({ status: 'Ok' })

While this is a small change, we’l be able to see that we’re getting a successful request, which is a pretty common pattern for APIs.

Now to test this out, we can set up our form that we created in the last step to automatically send a request whenever something is sent.

Inside pages/index.js, let’s first add an onSubmit handler to our form:

<form method="post" onSubmit={handleOnSubmit}>

Then, let’s define that handler above the return statement for our Home component:

async function handleOnSubmit(e) {
  e.preventDefault();

  const formData = {};

  Array.from(e.currentTarget.elements).forEach(field => {
    if ( !field.name ) return;
    formData[field.name] = field.value;
  });

  await fetch('/api/mail', {
    method: 'POST',
    body: JSON.stringify(formData)
  });
}

Breaking this down:

  • We’re creating a new function called handleOnSubmit
  • Inside of that function, we’re first preventing the default events from occurring, which in this case, is preventing the form from submitting to the browser
  • We create a new object called formData where we’ll store the information from the form
  • We create an array from our currentTarget‘s elements, which in this case is our form and the inputs of our form, and store the name and value of each of those inputs in our formData object
  • Finally, we take that data, and we POST it as a string to our API endpoint at /api/mail

To explain the above simply, we’re taking our form data and we’re sending it to our API endpoint.

We can test this in our browser filling out and submitting our form, where we can see that request being made to our endpoint.

API response from POST request to Next.js API route
Sending a POST request to a Next.js API route

If we click on the Response tab, we can also see our JSON object where we returned a status of “Ok”.

Request JSON response "Ok"
Valid response from API endpoint

So at this point, we have a form inside of our application that’s successfully sending data to our new Next.js API endpoint.

From here we’ll be able to start working with SendGrid, where we can configure and use it to send emails with that information!

Follow along with the commit!

Step 2: Configuring a domain to send emails from with SendGrid

To send our emails, we’re going to use SendGrid as our provider. We’ll connect a custom domain to SendGrid by setting up DNS records and generate an API key to use later with the SendGrid API.

Note: you might be able to send emails using the SendGrid domain, making this step optional, but you’ll be creating a better experience for your visitors by sending the emails from the same domain they’re interacting with!

Once you’re logged into SendGrid, we want to authenticate a domain. If you’re account is new, you may be able to find some links on the dashboard’s homepage, but we can find the page by navigating to the Settings, then Sender Authentication, where you can click Get Started.

SendGrid Sender Authentication Dashboard
Finding Sender Authentication in the SendGrid dashboard

Once there, SendGrid will first ask you to select your DNS host. If you know it and it’s listed, feel free to select it, otherwise select Other.

It will also ask if you’d like to rebrand all tracking links. You can leave this as No if you’re not sure what it does and change it later if you’d like.

SendGrid will then ask you for the domain you’d like to send from.

I’m going to be setting up colbyashi.com for my example, so I’ll ender colbyashi.com in the provided field, then click Next.

Authenticate domain in SendGrid
Entering in the domain you want to add

Finally, SendGrid asks you to add DNS entries for your domain.

I’m going to walk through how we can do this with Google Domains which is my provider. This should look very similar with other providers, but if you’re unsure of how to add these entries, try to search in your provider’s documentation how to add a “CNAME”.

For each record we have 2 values: the DNS Name and the Canonical Name.

SendGrid CNAME DNS Records
SendGrid DNS records

If we look at our available options inside of Google Domains, which again should look similar to others, we have a field marked by “@“ which will represent our SendGrid DNS Name and the Domain Name which will represent our SendGrid Canonical Name.

Google Domains custom resource records
Google Domains custom resource records

We can copy and paste each of these values and create new CNAME records for each.

Note: For each of the custom record “names”, you want to make sure you only include the beginning of each subdomain entry. For instance, for em5080.colbyashi.com, you only want to enter em5080 in Google Domains. This may different depending on your DNS provider.

The 1h value, or TTL, indicates the amount of time that value should “live” before it’s discarded by servers. If you’re not sure what you’re doing, you can just leave that as the default setting.

SendGrid DNS entries added as custom resource records

But once all of your records are added, you can head back to SendGrid, where you can select the checkbox that you added the entries, and hit Verify.

SendGrid verify records
Verifying DNS entries

Note: depending on the provider, these changes may take time to propagate and may not work immediately. Refer to your DNS provider to see how long it may take.

And if you’ve successfully verified, you’ll be taken to a success page and you’re now ready to go!

SendGrid successful domain verification
Successful verification

Now that our domain is verified, our final step will be to generate an API key which we’ll use in the next steps to send emails.

Navigate to Settings, API Key, then click Create API Key.

SendGrid API key dashboard
Creating a new API key in SendGrid

Here, you’ll be asked to give your API key a Name, I’m going to use “Contact Form”, and select what permissions you’d like to give to the key.

For our purposes, I’m going to create my key with full access, but if you’d like to secure your key, you can use Restricted Access to only give permissions to the functionality you’d like.

Once you click Create & View, SendGrid will show you your new API key.

SendGrid API Key created
SendGrid API Key

As they note, this will be the only time you can view this key, so make sure to copy it to a secure location to use later.

If you misplace it, you’ll need to delete this key and regenerate a new one.

But now that we have both our domain verified and our API key generated, we can now get back to the application where we can use SendGrid in our app!

Step 3: Installing and configuring SendGrid in a Next.js app

In order to send emails programmatically, we’re going to take advantage of the SendGrid SDK that’s available to install as a node package.

Back inside of our terminal, let’s first install SendGrid with:

yarn add @sendgrid/mail
# or
npm install @sendgrid/mail --save

Once that’s finished installing, we’ll want to import the package into our mail API route.

Inside pages/api/mail.js, at the top add:

const mail = require('@sendgrid/mail');

With our mail package imported, the first thing we’ll need to do is configure SendGrid with our API key.

Right after our require statement, above our function handler, add:

mail.setApiKey(process.env.SENDGRID_API_KEY);

You might be tempted to paste in the API key right into the function as plain text, but instead, we’re using an environment variable.

Saving an API key as plain text is a security risk. If you check this into source control like git, you risk that key being exposed to others.

With environment variables, you also gain the ability to switch API keys in different environments. Maybe you want to track development usage differently than production usage or have different permissions. Using environment variables allows you to easily do that!

So now that we’re setting our key with an environment variable, we need to create that variable.

In the root of our project, create a new file called .env.local and inside add:

SENDGRID_API_KEY="[YOUR API KEY]"

Set the value to your API key that we generated earlier inside of SendGrid.

Note: typically you would want to make sure you add environment variable files to your .gitignore, but because .env.local is a Next.js convention, they already have it in that file. That way you’re preventing it from accidentally getting added to git!

And now, we should be ready to start sending our emails!

Follow along with the commit!

Step 4: Programmatically sending emails from an API route with SendGrid

Finally we can start sending emails!

To get started, the first thing we want to to is parse the data we’re sending from our application.

Inside of pages/api/mail.js at the top of the function, add:

const body = JSON.parse(req.body);

This will turn the string of data into a JSON object that we’ll be able to use to find our form data. If you’re following along with me, this will include a name, email, and message property.

We can even test this out by logging the body object right after:

console.log('body', body);

And if we try to submit the form, we should now be able to see those values in our terminal logs:

Terminal form data
Logging our form data from the API

Now, we can use that data, and send an email with it, so we can notify someone of of the new contact.

Back inside of our API function, we can first create a string-based message with:

const message = `
  Name: ${body.name}\r\n
  Email: ${body.email}\r\n
  Message: ${body.message}
`;

We’re using our body data to add our fields and using \r\n to add returns so that the email sends formatted nicely.

And finally, we can send that message, by setting who we want to send it to, where we want to send it from (some email address from our verified domain), along with a subject explaining what the message is, and passing that all to the mail.send function.

mail.send({
  to: 'hello@colbyfayock.com',
  from: 'hello@colbyashi.com',
  subject: 'New Message!',
  text: message,
  html: message.replace(/\r\n/g, '<br>'),
});

You’ll notice in the above that I’m also including an HTML property where I’m replacing the return characters with a <br> in the even that the person viewing the email is using HTML.

Note: because we authenticated our domain with SendGrid, you should be able to use whatever you want for the first part of the email address. (Ex: [name]@colbyashi.com)

Before we try to send the message though, we want to make sure that before we finish the request, that we have successfully sent our email.

On our mail.send invocation, we can chain a then on the end, and wrap our res.status inside:

mail.send({
  to: 'hello@colbyfayock.com',
  from: 'hello@colbyashi.com',
  subject: 'New Message!',
  text: message,
  html: message.replace(/\r\n/g, '<br>'),
}).then(() => {
  res.status(200).json({ status: 'Ok' });
});

Note: alternatively you can make the API function async and use await.

But now, if we try filling out our form and hitting send again, we should be able to keep an eye on our email inbox, and see our new message!

New message in Gmail
Successfully received email from API

Follow along with the commit!

What are the next steps?

Deploying the application

You have a few options. The obvious choice might be Vercel, as they support Next.js API routes out of the box, but Netlify does as well. The only caveat is you’ll need to make sure that you’re configuring the application to POST the API requests to the right address, which may include configuring an environment variable to set up your hostname.

Setting up spam protection

As mentioned earlier, if you leave a form open on the web, it’s likely you’re going to get spam.

Make sure that if you’re using this form open on the web that you’re setting up appropriate protection such as a simple honeypot, one of the various CAPTCHA solutions available, or a premium service that will manage that with filters.