company logo

Emailing Info with SendGrid and a Serverless API Route in Next.js

Posted on June 19, 2023
Written by Seth Nelson

Email communication has been in use for several decades, and over the years, we have continuously explored ways to automate and streamline the process of sending information via email servers. With a multitude of libraries and packages available, choosing the right solution for your business can be a challenge. Fortunately, Next.js offers powerful built-in capabilities that enable you to handle email-related tasks without requiring a separate backend application. This makes it straightforward to interact with an email REST API. In this guide, I will walk you through a simple integration to set up an email service in your Next.js application. Please note that this guide assumes the usage of TypeScript. If you are using JavaScript, some of the type declarations may not be necessary.

Next.js and Serverless API Routing

Next.js offers a unique feature that allows you to build APIs directly within your application, without the need for a separate backend. By default, Next.js provides a directory called pages/api, where any files placed inside are automatically mapped and treated as API endpoints instead of pages.

Here is a simple example of what this might look like:

export default function handler(req: NextApiRequest, res: NextApiResponse) { res.status(200).json({ name: 'John Doe' }) }

Creating the API Route

First, you want to create your API route. In the pages/api directory, create your new file email.ts.

const handler = async (req: NextApiRequest, res: NextApiResponse) => { ... } export default handler

Now, let's add the necessary content. Since we are using the POST method, it's important to include a validation check for the request. Since our API call to this route will receive a standard request body with data, we can set up basic validations and responses to handle it.

When working with SendGrid, you will need to install the package @sendgrid/mail. This package provides a function called sendGridMail that allows you to validate your credentials and communicate with the SendGrid REST API.

if (req.method === 'POST') { const message = { text: 'Email text', // this should be a string representation of the data html: data // you may add HTML formatting for a template if desired from: '', to: '', subject: 'New Email - Powered by Next.js and SendGrid!', } await sendGridMail .send(message) .then(() => { console.log('Email sent successfully') }) .catch((err) => { console.error('Error sending email:', err) }) return res .status(200) .json({ message: `Request sent successfully: ${res.statusMessage}` }) }

At this stage, it is necessary to create a SendGrid account. They offer comprehensive documentation on how to set up a user, which you can find here.

After obtaining your SENDGRID_API_KEY, it is recommended to store it in an environment variable (env var) for security and adherence to best practices. Once your sender is verified, you will have access to the SendGrid API at a fundamental level. Now, you can set the API_KEY in the API Route handler we created earlier.


Creating the API Call

I prefer to consolidate all my API calls into a single file to avoid confusion and reduce code duplication in individual component files. This promotes function reusability and simplifies the process of making requests within a component.

Create a new file called api.ts. You have the flexibility to choose its location, but I recommend creating a new directory like lib/api.ts. This file will act as a centralized location for all our API calls, enabling efficient management and easy addition of future calls. Within the api.ts file, let's implement a function to make requests to our recently created API route.

export const sendContactForm = async (data: ContactFormData) => await fetch('/api/email', { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, }) .then((res) => { console.log('Request sent successfully', res.status) return res.json() }) .catch((err) => { console.error('Error sending request:', err) throw new Error('Failed to send request') })

With the POST request to our new email endpoint set up, we can now invoke this function from any part of our application and provide the necessary values. As an example, I'm using it for a contact form. When the button is clicked, a callback function is executed, which triggers the API call and updates the state upon successful completion.

const handleSubmit = useCallback(async (values) => { await sendContactForm(values) .then(() => { setIsSubmitted(true) }) .catch((err) => { console.error('Error sending request:', err) }) }, [])

Closing Thoughts

I evaluated several libraries, including Email.js and Nodemailer, which offer hooks and methods for utilizing SendGrid. However, I found that relying solely on SendGrid and its dedicated library was remarkably straightforward and lightweight for our application.

Depending on your use case, I highly recommend storing the "to" and "from" email addresses as environment variables. This practice simplifies testing by using a specific set of variables for local or development environments, while enabling the use of different email addresses in production to prevent inadvertent email spamming during testing.

Thanks for reading!


Seth Nelson photo
Seth Nelson
Software Engineer
company logo
ServicesTeam IntegrationStrategy and DesignAgile Team SolutionsProof of Concept
TeamOur EmployeesCareersAbout UsOur Customers
WorkAtomic ObjectSimple ThreadZEAL
Contact Us
176 W. Logan St. #335
Noblesville IN, 46060