A Brief Guide on Setting Up a Contact Form in Next.js with App Router and Sending Emails via Nodemailer.
An overview of what we’ll be doing here
Build a contact form using Next.js and the app router. We'll design the form UI and then set up an API route to send emails through Nodemailer to our Gmail with form data.
Creating a new NextJS project.
npm create next-app contact-form
Open the contact-form folder in VSCode (or any IDE you use).
The server now starts at localhost:3000. You can view the website by going to this URL. ( If your 3000 port, it will be in consecutive order like 3001,3002, and so on..)
Creating a simple contact form
You can start by editing app/page.tsx and remove excess code, keeping only the main tag. We'll build our form there. The form needs to be a client component. Instead of converting page.tsx (not advised), create a component folder with a new file: contact.jsx for the form.
Lastly, install react-hook-form for form management.
npm i react-hook-form
Also, we will be separating the email-sending logic into a separate file in the utils folder to simplify our codebase.
For Simplicity, code is here, enjoy. for app/page.tsx
import Contact from '@/components/contact';
export default function Home() {
return (
<main className='flex min-h-screen flex-col items-center justify-center p-24 bg-white'>
<Contact />
</main>
);
}
Add one more npm package for the animation of sending a message from this package
npm install --save react-toastify
components/contact.tsx
"use client";
import React, { useState } from 'react';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
const Contact = () => {
const [isLoading, setIsLoading] = useState(false);
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const notify = (message) => toast(message);
const clearFormData = () => {
setName("");
setEmail("");
setMessage("");
};
async function handleSubmit(e) {
e.preventDefault();
setIsLoading(true);
try {
await sendFormData({ name, email, message });
clearFormData();
notify("🚀 Your message is on its way! Thanks for reaching out 😊. Have a fantastic day ahead! 🌟");
} catch (error) {
notify(`Error: ${error.message}`);
} finally {
setIsLoading(false);
}
}
async function sendFormData(data) {
const response = await fetch('/api/email', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error("Failed to send message");
}
return response.json();
}
return (
<>
<form onSubmit={handleSubmit}>
<div className='mb-5'>
<label htmlFor='name' className='mb-3 block text-base font-medium text-black'>Full Name</label>
<input
type='text'
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter Your Name"
className='w-full rounded-md border border-gray-300 bg-white py-3 px-6 text-base font-medium text-gray-700 outline-none focus:border-purple-500 focus:shadow-md'
required
/>
</div>
<div className='mb-5'>
<label htmlFor='email' className='mb-3 block text-base font-medium text-black'>Email Address</label>
<input
type='email'
placeholder="Enter Your Email "
className='w-full rounded-md border border-gray-300 bg-white py-3 px-6 text-base font-medium text-gray-700 outline-none focus:border-purple-500 focus:shadow-md'
required
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className='mb-5'>
<label htmlFor='message' className='mb-3 block text-base font-medium text-black'>Message</label>
<textarea
rows={4}
placeholder="Write Your Message Here..."
className='w-full resize-none rounded-md border border-gray-300 bg-white py-3 px-6 text-base font-medium text-gray-700 outline-none focus:border-purple-500 focus:shadow-md'
required
value={message}
onChange={(e) => setMessage(e.target.value)}
></textarea>
</div>
<button type='submit' disabled={isLoading} className='hover:shadow-form rounded-md bg-blue-500 py-3 px-8 text-base font-semibold text-white outline-none'>
{isLoading ? 'Loading...' : 'Send'}
</button>
</form>
<ToastContainer />
</>
);
};
export default Contact;
utils/send-email.ts
import { FormData } from '@/components/contact';
export async function sendEmail(data: FormData) {
const apiEndpoint = '/api/email';
fetch(apiEndpoint, {
method: 'POST',
body: JSON.stringify(data),
})
.then((res) => res.json())
.then((response) => {
alert(response.message);
return true
})
.catch((err) => {
alert(err);
});
}
Setting up Nodemailer and API
Now that our basic form is completed, let’s build our API using route handlers in NextJS. But first, we need to install nodemailer. Nodemailer is a module for Node.js that makes sending emails from a server an easy task. To install it along with its types:
npm i nodemailer @types/nodemailer
Create a new folder api and another folder email inside it. Now create a new file route.ts inside it. The file path will be app/api/email/route.ts. It is done this way to create our api with the endpoint as “api/email”. Check out this doc for more information. The current project structure will be as follows:
import { type NextRequest, NextResponse } from 'next/server';
import nodemailer from 'nodemailer';
import Mail from 'nodemailer/lib/mailer';
export async function POST(request: NextRequest) {
const { email, name, message } = await request.json();
const transport = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.MY_EMAIL,
pass: process.env.MY_PASSWORD,
},
});
const mailOptions: Mail.Options = {
from: process.env.MY_EMAIL,
to: process.env.MY_EMAIL,
// cc: email, (uncomment this line if you want to send a copy to the sender)
subject: `Message from ${name} (${email})`,
text: message,
};
const sendMailPromise = () =>
new Promise<string>((resolve, reject) => {
transport.sendMail(mailOptions, function (err) {
if (!err) {
resolve('Email sent');
} else {
reject(err.message);
}
});
});
try {
await sendMailPromise();
return NextResponse.json({ message: 'Email sent' });
} catch (err) {
return NextResponse.json({ error: err }, { status: 500 });
}
}
Create .env for storing your Gmail Credentils.
Finally your VSCode looks like this below.
After Filling all the details, and clicking send button you will see this message in top right corner.
Here all the functions for the above form are verified and you can see new email to your Gmail