Building a **contact form with Next.js** is a fundamental task for modern web developers who want to bridge the gap between their static content and user interaction. While traditional methods involved setting up complex SMTP servers or managing heavy backends, the advent of serverless architecture and specialized email APIs has simplified this process significantly. According to W3Techs (2024), Next.js usage has grown to power over 12 percent of the top 10,000 websites, reflecting its dominance in the React ecosystem. By combining the power of Next.js Server Actions with Resend, a modern email API designed for developers, you can deploy a robust communication tool in minutes. This tutorial focuses on the App Router architecture, which provides the most efficient way to handle form submissions without creating separate API routes. You will learn to handle data validation, delivery, and user feedback through a streamlined workflow.
Prerequisites
- Node.js 18.17 or later installed on your local machine.
- A basic understanding of React and TypeScript.
- A free account at Resend.com to obtain an API key.
- Next.js 14 or 15 project using the App Router.
Key takeaway: Ensure your environment meets these version requirements to take full advantage of modern Server Actions and TypeScript safety.
Initial project setup

First, you need to initialize a new Next.js project if you do not have one already. Open your terminal and run the following command to scaffold a new application with the recommended settings, including Tailwind CSS for styling and TypeScript for type safety.
npx create-next-app@latest my-contact-form --typescript --tailwind --eslint
Navigate into your project directory and install the Resend SDK. This package provides the necessary methods to interact with the Resend API from your server-side code.
cd my-contact-form
npm install resend
In addition, you must store your API credentials securely. Create a file named .env.local in your root directory and add your Resend API key. You can find this key in the Resend dashboard under the API Keys section.
RESEND_API_KEY=re_your_api_key_here
Furthermore, you should consider using a validation library like Zod if you plan to scale this form. For this tutorial, we will keep the logic focused on the core integration. From experience, keeping your environment variables strictly server-side is the best way to prevent accidental leaks of sensitive keys to the client browser.
Key takeaway: Proper environment configuration is the foundation of a secure email integration in a serverless environment.
Define your email template structure
Before sending an email, you need to define how that email looks when it arrives in your inbox. Resend allows you to use standard React components as email templates, which makes styling much easier than writing raw HTML tables. Create a new directory called components and add a file named email-template.tsx.
import * as React from 'react';
interface EmailTemplateProps {
firstName: string;
message: string;
email: string;
}
export const EmailTemplate: React.FC> = ({
firstName,
message,
email,
}) => (
New Contact Form Submission
From: {firstName} ({email})
Message:
{message}
);
That said, you can also use the React Email library for more advanced components. However, for a simple contact form, a clean functional component with inline styles works perfectly. In my experience, inline styles are safer for email templates because many email clients, such as older versions of Outlook, often strip out external CSS or style blocks.
Consequently, using a typed interface ensures that your server action always provides the correct data to the template. This reduces runtime errors and makes your code more maintainable as you add more fields to your form in the future.
Key takeaway: React components offer a familiar and type-safe way to build email layouts without the headaches of traditional HTML email coding.
Create the server action for email delivery
Next.js Server Actions allow you to handle form submissions directly in a function that runs on the server. This eliminates the need to fetch an API endpoint manually from the frontend. Create a new file called app/actions.ts to house your logic.
"use server";
import { Resend } from 'resend';
import { EmailTemplate } from '@/components/email-template';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function sendEmail(formData: FormData) {
const firstName = formData.get('firstName') as string;
const email = formData.get('email') as string;
const message = formData.get('message') as string;
if (!firstName || !email || !message) {
return { error: 'All fields are required.' };
}
try {
const { data, error } = await resend.emails.send({
from: 'Contact Form ',
to: ['[email protected]'],
subject: 'New Message from Website',
react: EmailTemplate({ firstName, email, message }),
});
if (error) {
return { error: error.message };
}
return { success: true, id: data?.id };
} catch (err) {
return { error: 'A server error occurred. Please try again later.' };
}
}
The part that actually matters is the “from” field in the resend configuration. If you are using a free Resend account without a verified domain, you must use [email protected] as the sender. Furthermore, you can only send emails to the address you used to sign up for Resend until you verify a custom domain.
In practice, I always recommend wrapping the API call in a try-catch block. According to Postmark (2023), average delivery times for transactional emails should be under 10 seconds to maintain user trust. Using Server Actions ensures the user stays on the same page while the background task completes, providing a smoother experience than traditional page reloads. Visit our development category for more tips on server-side logic.
Key takeaway: Server Actions simplify the backend by treating form submissions as asynchronous functions rather than separate HTTP routes.
Build the interactive frontend component
Now you need a user interface to collect the data. You will create a client component that uses the server action we just defined. In your app/page.tsx file, implement a standard HTML form that utilizes the action attribute.
"use client";
import { useState } from 'react';
import { sendEmail } from './actions';
export default function ContactForm() {
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
const [message, setMessage] = useState('');
async function handleSubmit(formData: FormData) {
setStatus('loading');
const result = await sendEmail(formData);
if (result.error) {
setStatus('error');
setMessage(result.error);
} else {
setStatus('success');
setMessage('Thank you! Your message has been sent.');
}
}
return (
Contact Us
);
}
A common mistake here is neglecting the loading state. Users frequently double-click buttons if they do not see immediate feedback. By disabling the button and showing a “Sending…” state, you prevent duplicate emails and improve the perceived performance of the application.
In addition, using the action prop in the form tag is the preferred way to integrate with Next.js Server Actions. This approach allows the framework to handle the submission lifecycle gracefully. For more information on frontend patterns, check out our software section.
Key takeaway: Client-side state management combined with Server Actions provides a responsive and reliable user experience for form submissions.
Testing your integration
Once your code is in place, start your development server using the command below. This will launch your application on port 3000 by default.
npm run dev
Navigate to your browser and fill out the form. Use the email address associated with your Resend account to ensure the message arrives during the testing phase. After clicking send, you should see the success message.
Check your Resend dashboard. The “Emails” tab will show a log of every message sent through the API, including the status and any delivery errors. This is invaluable for debugging why a specific email might have failed to reach its destination. What most guides miss is that Resend also provides a “Events” web-hook system if you eventually need to track when a user opens the email or clicks a link inside it.
Key takeaway: The Resend dashboard is a powerful tool for monitoring delivery status and verifying that your Server Actions are firing correctly.
Common errors and troubleshooting
API key authorization issues
If you receive a 401 error or an authorization failed message, double-check your .env.local file. Ensure the variable name matches exactly what you are calling in your action.ts file and that you have restarted your development server after adding the environment variable.
Validation and missing fields
If the email arrives but is empty, verify that the “name” attribute on your HTML input tags matches the strings you are using in formData.get(‘fieldName’). TypeScript will not catch these string-based mismatches at compile time, so manual verification is necessary.
Unverified domain restrictions
A frequent source of confusion is the restricted “from” address. If you try to send from a custom domain like [email protected] without verifying that domain in the Resend settings, the API will reject the request. Stick to the onboarding email for initial development.
Key takeaway: Most integration issues stem from environment variable mismatches or unverified sender identities in the Resend platform.
Building a **contact form with Next.js** and Resend provides a modern, scalable solution for user communication without the overhead of legacy mail servers. By leveraging Server Actions, you keep your logic secure on the server while providing a snappy experience on the client side. Resend simplifies the developer experience with its React-first approach and clear documentation, making it a top choice for projects of all sizes. As your application grows, you can easily add features like spam protection with Turnstile or schema validation with Zod. The next step for your project should be implementing a validation library to ensure the data you receive is clean and formatted correctly before it ever hits your inbox. This foundation allows you to focus on building features rather than wrestling with email protocols.
Key takeaway: Combining Next.js and Resend creates a professional, low-maintenance communication layer that scales effortlessly with your application.
Cover image by: Alpha En / Pexels

