Have you always wanted to send emails with JavaScript? Or more specifically, how to send emails with React or even NextJS? Then let's discover together how to use SendGrid! It's an easy and quick solution to set up, which will ensure that our emails are sent and, most importantly, that they are successfully received.
Step 1: Create a NextJS Project
This step is perfectly optional. If you already have a NextJS project along with a perfectly functional form that correctly receives your users' data (contact form), you can skip to Step 4.
To understand how the email sending process works, we suggest starting with a NextJS project created from scratch.
In this project, we will add a contact page that will display a form where we can enter our first name, last name, email address, and the content of our message. You can then customize this project by reusing the same method we will explore together to send all kinds of emails, ranging from security notifications when a user logs in to sending a security code to finalize and confirm their registration.
To create a project with NextJS, let's start by using this command:
npx create-next-app my-contact-formThis command will create a project named "My Contact Form" with all the necessary dependencies.
We will, of course, add more dependencies as we proceed with this mini-course.
Step 2: Create a Contact Page
To add a new page in a NextJS project, simply add a JavaScript file in the pages folder.
Since we want to add a contact page, we want this page to be accessible via /contact. Therefore, we will create the contact.js page in the pages folder.
You can also create a contact folder inside the pages folder and add an index.js file there. This method is valid if you prefer it.
Now, let's add the JSX that will allow us to display a nice contact form. Here's what the final code for the contact.js page will look like for now:
And for the CSS (in the globals.css file):
Step 3: Initialize React Hook Form
We have just added a new page and placed a contact form on it: we can be proud of the progress we've made so far.
The problem is as follows: our form isn't working. And that's rather annoying.
What we mean is that the form isn't sending data to NextJS—or rather, we aren't retrieving the submitted data.
To do this, we will use the react-hook-form library. This excellent library is one of the best ways to handle React and NextJS forms currently. There are, of course, many other possibilities, and you are free to use the method that suits you best.
Let's start by installing React Hook Form:
npm install react-hook-formNow, let's go to our contact.js page and add this new dependency at the top of the file:
import { useForm } from 'react-hook-form';We can now begin to retrieve the data from our form!
Step 4: Display the Received Data
To display the data we receive, we need to start by setting up React Hook Form to tell it, "I'm giving you control of the first name field," then "last name," "email address," and finally "content."
To achieve this, we will need to use several variables that are returned by React Hook Form. Here's how we'll do it.
// Library
import { useForm } from 'react-hook-form';
export default function Contact() {
// Variables
const { register, handleSubmit, formState: { errors } } = useForm();
// JSX
return ...
}All these variables are retrieved from useForm() and have their uses:
register- allows you to register a form field with React Hook FormhandleSubmit- allows you to retrieve all the form data and send it to a very specific methoderrors- allows you to know when a form field is invalid
Of course, we're not here to give a mini-course on React Hook Form, but here are some basic elements that can be interesting when you're not familiar with this library.
So, we have the necessary variables for React Hook Form to function correctly. Now, let's add our form fields! Here's what we have so far for the prenom (first name) field:
<input className="input" placeholder="Email Address" />Here's what we will have thanks to React Hook Form:
<input
className="input"
placeholder="Email Address"
{...register('prenom', { required: true }) }
/>Here, we register the form field with the identifier prenom for React Hook Form and add a validation rule to make the field required each time. Here's what our entire form will look like:
I now propose to retrieve all this information when our form is submitted.
Here's some good news: it's very easy to set up thanks to React Hook Form!
We will add the onSubmit event to the <form> tag of our form and tell it to call the handleSubmit method provided by React Hook Form.
<form onSubmit={handleSubmit()}>
Don't forget that this method allows you to retrieve all the form information and send it to a very specific method: our own method! Let's say we will have an onSubmitHandler method, so let's specify it in handleSubmit right away.
<form onSubmit={handleSubmit(onSubmitHandler)}>All that's left is to display our information by creating the onSubmitHandler method in our contact page.
// Library
import { useForm } from 'react-hook-form';
export default function Contact() {
// Variables
const { register, handleSubmit, formState: { errors } } = useForm();
// Method
const onSubmitHandler = data => {
console.log(data);
}
// JSX
return ...
}Let's add information, submit the form... Our data is correctly displayed in the console! Wonderful.
You might know our reputation: we always go in-depth, even when it's just content provided for free. So, we will show you how to display a message to your users when they don't fill in all the information.
Specifically, when a form field is empty, React Hook Form highlights it when the user tries to submit the form. But displaying a small message asking them to fill in the field is much better.
Here's how we can do this (don't forget our errors variable that we added):
With just this code, when our user doesn't fill in their first name, a small message will appear and disappear on its own like magic once they've added or haven't added a value. Magical, isn't it?
Step 5: Create an Account on SendGrid
We now have a contact form that is perfectly functional, so all that's left is to send this information to send an email. And for that, we obviously need SendGrid in this mini-course.
You can create an account on SendGrid by going here.
Creating an account is free; you will only pay if you wish to send more than 100 emails per day (which is extremely generous).
Step 6: Create a Template
We now want to go to the Email API category and finally to Dynamic Templates.
Dynamic templates are emails that you can personalize with very specific information. Thus, we will set up the design of our email and fill it with the information provided by our user at the time of form submission: SendGrid is magic! (SendGrid is magic! - Yes, we're thinking of you who don't like English))
We will click on Create a Dynamic Template - the big blue button on the page - and give our template a name. For example, contact.
You should now see the Contact template on your page. Click on it and then click on Add Version.
We can now choose between two options:
- Add a design (Your email design);
- Use a SendGrid template (SendGrid Email Designs).
You can use whichever one you prefer. Know that we recommend using a SendGrid template to start with. Here's the one we will choose:
![]()
We recommend using the designer mode and not the code mode. It's simpler to navigate the template this way; you can always modify the HTML when we need to.
So, try to customize the template according to your tastes. If you have trouble, you can always check out our YouTube video on this subject.
Here's what we've done:
![]()
We will now make the template dynamic. The goal is to have a personalized message with the information provided by each user, so we expect to have:
- firstName;
- lastName;
- content;
- email.
Without capital letters and without any special characters.
What is very simple with SendGrid is that to add dynamic information, you just need to go into the HTML and replace the text with:
{{ prenom }}To display the first name dynamically. And if you want to allow HTML, just add another set of curly braces around it.
{{{ contenu }}}As you have probably understood, try to add all dynamic information to your template so that each sent message is unique.
You can test your variables using the preview mode (click on Preview). Then you can click on the "Add Test Data" button and enter some JSON. Here's an example of JSON to test your template on SendGrid with the variables.
{
"contenu": "I would like to create a website for my new company SpaceX. Could you provide information about the Rocket Program? I've heard a lot of good things.",
"prenom": "Elon",
"nom": "Musk",
"email": "elon@spacexploration.com"
}
Step 7: Create an API on NextJS
The contact form is functional, and our template on SendGrid is too; we just need to send the information to have the email sent. To do this, we will create an API using the system provided by NextJS.
Simply create an api folder inside the pages folder and add a file there, for example, contact.js. Everything written in this file will remain hidden from our user's eyes. We can add our SendGrid login credentials here, for example. All the code will be executed on the server, not in the browser. This is one of the big advantages provided by NextJS. Here's what our API will look like:
- Verification of the used method;
- Verification of the email address syntax;
- A request to send the information to SendGrid;
- Verification to return a success or failure of the sending process.
Of course, you can create all this yourself; that's actually what we would like you to do: there's no better way to learn than by trying. Nevertheless, here's what we have:
// Library
import sgMail from "@sendgrid/mail";
export default function handler(req, res) {
if (req.method !== "POST") {
res.status(405).json({ message: "INVALID_METHOD" });
return;
}
// Variables
const { prenom, nom, email, contenu } = req.body;
if (!prenom || !nom || !email || !contenu) {
res.status(400).json({ message: "INVALID_PARAMETER" });
return;
}
// Email address syntax
const pattern =
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (!pattern.test(email)) {
res.status(400).send({
message: "EMAIL_SYNTAX_INCORRECT",
});
return;
}
// Transform line breaks for HTML
const message = contenu
.replace(/\n/g, "<br>")
.replace(/\r/g, "<br>")
.replace(/\t/g, "<br>")
.replace(/<(?!br\s*\/?)[^>]+>/g, ""); // removes all HTML except
tags
// Provide the API key
sgMail.setApiKey(process.env.KEY_SENDGRID);
// Create the message
const sendGridMail = {
to: "hello@believemy.com",
from: "louisnicolas@believemy.com",
templateId: "d-98ac3ceded9745448d3f4522a0f3eb26",
dynamic_template_data: {
prenom: prenom,
nom: nom,
email: email,
contenu: message,
},
};
// SendGrid
(async () => {
try {
await sgMail.send(sendGridMail);
res.status(200).json({
message: "EMAIL_SENT_SUCCESSFULLY",
});
} catch {
res.status(500).json({
message: "ERROR_WITH_SENDGRID",
});
return;
}
})();
}As you can see, nothing too new if you're already familiar with NextJS. If not, a quick tour of our comprehensive NextJS course is highly recommended. We have also used an environment variable (process.env.KEY_SENDGRID). To add an environment variable, go to the next.config.js file and add this:
module.exports = {
reactStrictMode: true,
env: {
KEY_SENDGRID:
"YOUR_API_KEY",
},
};Your key to allow you to make requests to SendGrid's API can be found in your account, under settings and API keys. Simply create a new one with all permissions.
Step 8: Call the API on the Client
Our API works; all that's left is to use it!
To call it, we need a trigger, a moment when we know our form is submitted would be perfect! Upon some reflection, we have precisely this trigger: the onSubmitHandler method.
Here's what we could do:
const onSubmitHandler = async (data) => {
const response = await fetch("/api/contact", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
const result = await response.json();
if (!response.ok) {
console.log("error");
} else {
console.log("ok");
}
};Don't forget to add a little message during the sending process so that your user doesn't wait while the form is processing. For this, we can use a state with useState. Let's call it isLoading.
// Libraries
// ...
import { useState } from 'react';
export default function Contact() {
// Variables
// ...
// State
const [isLoading, setIsLoading] = useState(false);
// ...
}Let's modify the state when we use the API we designed and add a condition to prevent sending multiple requests at the same time:
const onSubmitHandler = async (data) => {
if (!isLoading) {
setIsLoading(true);
const response = await fetch("/api/contact", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
const result = await response.json();
setIsLoading(false);
if (!response.ok) {
console.log("error");
} else {
console.log("ok");
}
}
};If we don't perform any checks, a user who clicks the "Send" button 15 times would have sent the same email 15 times! Always think to check if the form is not already in the process of being sent.
Finally, all that's left is to clear the form and display a small message once the email is successfully received by SendGrid. Here's what we'll do to clear the form:
// Variables
const {
register,
handleSubmit,
reset, // we add the reset method
formState: { errors },
} = useForm();
// State
const [isLoading, setIsLoading] = useState(false);
// Method
const onSubmitHandler = async (data) => {
if (!isLoading) {
setIsLoading(true);
const response = await fetch("/api/contact", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
const result = await response.json();
setIsLoading(false);
if (!response.ok) {
console.log("error");
} else {
console.log("ok");
reset(); // here
}
}
};Very easy, isn't it? We just need to add the success message, and everything will be perfectly functional. We recommend using a new state isSended.
// Libraries
// ...
import { useState } from 'react';
export default function Contact() {
// Variables
// ...
// States
const [isLoading, setIsLoading] = useState(false);
const [isSended, setIsSended] = useState(true);
// ...
}Here's what we have now in onSubmitHandler:
const onSubmitHandler = async (data) => {
if (!isLoading) {
setIsLoading(true);
const response = await fetch("/api/contact", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
const result = await response.json();
setIsLoading(false);
if (!response.ok) {
console.log("error");
} else {
console.log("ok");
reset();
setIsSended(true);
}
}
};All that's left is to display the success message above our form when the isSended state changes and is true.
// The rest of the code
<form
style={{ width: "500px", margin: "auto" }}
onSubmit={handleSubmit(onSubmitHandler)}
>
{isSended && (
<p>
Your message has been successfully sent. We will respond to you quickly.
</p>
)}
<div
style={{
backgroundColor: "#f5f5f5",
padding: "30px",
borderRadius: "5px",
textAlign: "left",
}}
>
// The end of the codeAnd there you have it! We now have a very well-designed contact form, perfectly usable and functioning thanks to SendGrid, ensuring that all emails are properly sent to the destination.
All that's left is to reuse this same model to send other emails on other occasions if you wish. Just create a template—whether dynamic or not—and send a request to SendGrid.
If you want to learn more about NextJS, we have a comprehensive course dedicated to this subject. Also, feel free to share this article with your network on your social media. Thank you!
Feel free to download the project source code here.







