In this tutorial, I’ll show you how to add an email-sending functionality for your Next.js web application and make it send messages from contact forms, user notifications, password resets, and more.
If you feel like jumping ahead, here are the chapters:
- Setting up a Next.js Project [jump ahead]
- Using EmailJS (Client-Side, no custom backend) [jump ahead]
- Using Nodemailer with Mailtrap SMTP (Server-Side) [jump ahead]
- Using Mailtrap’s Official Node.js SDK (Server-Side Email API) [jump ahead]
And before setting up a Next.js, make sure you have the necessary prerequisites:
- Mailtrap Account – If you haven’t already, sign up for a free account since we’ll be using Mailtrap’s SMTP service and Email API, as well as the Email Testing inbox to fine-tune everything in the end.
- Node.js Installation – The workflow in this article is compatible with Node.js v18+, so make sure to install the latest version of Node.js on your system.
Disclaimer: Every bit of code in this article has been prepared and tested by a developer before publication.
Set up a Next.js project
Before diving into email functionality, let’s set up a fresh Next.js project using the latest stable version (as of May 2025) with the App Router structure.
1. Create a new Next.js app
For this step, you can use the Next.js official CLI tool create-next-app. Simply run one of the commands below and replace “my-next-app” with your desired project name.
npx:
npx create-next-app@latest my-next-app
yarn:
yarn create next-app my-next-app
Both of the commands above will launch an interactive prompt in your terminal, which will ask you a series of questions. For this demo, use the following choices, but also feel free to use the options that work for you best:
✔ Would you like to use TypeScript? … No ✔ Would you like to use ESLint? … Yes ✔ Would you like to use Tailwind CSS? … No ✔ Would you like your code inside a `src/` directory? … No ✔ Would you like to use App Router? (recommended) … Yes ✔ Would you like to use Turbopack for `next dev`? … Yes ✔ Would you like to customize the import alias (`@/*` by default)? … No |
2. Navigate to the project and review the structure
Once the setup is complete, navigate into your project directory:
cd my-next-app
Your Next.js app’s folder structure should look something like this:
my-next-app/ |── app/ │ |── favicon.ico │ |── globals.css │ |── layout.js │ |── page.js │ └── page.module.css |── public/ │ |── file.svg │ └── …other svg files |── .gitignore |── eslint.config.mjs |── jsconfig.json |── package.json |── README.md └── next.config.js |
Now, let me explain some directories and files real quick so you get a better picture:
- The app/ directory is the core of the App Router. It contains your application routes and components. By default, you have app/page.js (the homepage) and app/layout.js (a root layout that wraps pages). The globals.css file is imported by the layout and can contain global styles.
- There is no pages/ folder because we opted for the App Router (e.g., if you had chosen “No” for App Router, you’d see a pages/ directory instead).
- The public/ folder holds static assets like images, including the default favicon and Vercel SVG.
- Standard config files like package.json and next.config.js are in the root.
For our purposes, we will organize email-related code as follows:
- Client-side components (e.g., a contact form UI) will go in the app directory (or a subfolder within it). We can also create a subfolder like app/components/ for reusable components.
- Server-side logic (such as an API route to send emails) will go under app/api/. In Next.js App Router, any file named ‘route.js’ inside app/api defines an API endpoint (similar to how pages/api worked in older versions). For example, a file at app/api/contact/route.js would map to an API endpoint /api/contact.
3. Run the development server
Lastly, verify everything is set up correctly by running the dev server:
npm:
npm run dev
yarn:
yarn dev
This starts the development server. Open your browser to http://localhost:3000. You should see the default Next.js welcome page (with the heading “Welcome to Next.js” or some starter content). This confirms your project is up and running.
For now, you can stop the server with Ctrl+C and proceed with implementing the email functionality.
Send email without a backend using EmailJS
EmailJS is a service that allows you to send emails directly from JavaScript by connecting your client-side app to providers, such as Gmail, Outlook, Mailtrap — all without setting up your own server or SMTP. This makes it a perfect choice for static sites or front-end-only applications where you want a quick way to send form submissions via email.
Now, let’s set it up and send some emails!
1. Setting up EmailJS
Before writing any code, follow these steps to configure EmailJS:
- Sign up for EmailJS
Go to the EmailJS website and create a free account. You can use the free tier for basic purposes.
- Set notifications email
Navigate to the ‘Account’ page on the sidebar menu on your left. Then, in the Notifications section, insert your Mailtrap sending domain (e.g., no-reply@my.own.domain).
- Connect an Email Service
In the EmailJS dashboard, navigate to Email Services and select Mailtrap.
Next, configure the service by providing the following details:
→ Username and Password: Find your credentials by going to your Mailtrap dashboard and then navigating to Sending Domains > Integration > SMTP.
→ Service ID: A unique identifier for this service. You can name it something like “mailtrap_service” or any name you like.
→ Email Feature – Select “Sending” from the dropdown menu.
Important: Don’t mark the ‘Send test email to verify configuration’ checkbox since then the whole setup and configuration won’t work.
With all the details provided, click ‘Create Service’ to finalize this step and have the service listed in your EmailJS dashboard.
- Create an Email Template
In the EmailJS dashboard, go to Email Templates and click “Create New Template”. In the template editor, you’ll set up the email that will be sent out. This includes:
→ Subject of the email (for example, “New Contact Form Submission”).
→ Body content of the email. You can write a message and use placeholders for form fields (e.g., {{name}}, {{message}} where you want to insert the sender’s name or message).
→ Dynamic data such as {{name}}, {{email}}, and {{message}} where the user’s input will go.
Pro tip: If you include HTML in your template content, wrap placeholders with triple braces like {{{message}}} to properly render HTML content.
Note: You can also configure advanced options like auto-reply, add attachments, etc., but for a simple use case, keep it basic.
Important:
- Do not tick the “Use Default Email Address” box since there is currently an issue, which makes the whole integration obsolete since the EmailJS account won’t match the sending domain. Instead, enter your actual email-sending address.
- I’ve said it already, but if you choose Mailtrap as your service, make sure that your sender email has to be from the same domain you added and verified when setting up your Mailtrap account.
You can also check out how your template looks by clicking on the ‘Test it’ button.
This will open a new popup window where you need to provide some details, such as the service used and the values for template parameters. ⬇️
If everything goes as expected, you will get “200 OK” as a result, meaning the email was sent successfully.
- Save your IDs and Keys
Once you’ve set up the service and template, make sure to save your Service ID, Template ID, and EmailJS Public Key. You can find these in your EmailJS dashboard. This step is important since we’ll use these in the Next.js code to initialize EmailJS and send emails.
2. Installing the EmailJS Client SDK
Now, let’s install a JavaScript SDK (provided by EmailJS) by running one of the following commands:
npm:
npm install @emailjs/browser
yarn:
yarn add @emailjs/browser
(The @emailjs/browser package is the official EmailJS SDK for browser environments.)
3. Configuring environment variables
Since we’ll be using some API keys and credentials for EmailJS and Mailtrap, I advise against hardcoding sensitive information.
Instead, I recommend using an .env.local file in the project root to store environment variables. This way, Next.js will automatically load variables from this file into process.env and it will be git-ignored by default to keep secrets safe.
To do this, simply create a file named .env.local in the root of my-next-app/ and add the following:
# EmailJS credentials
NEXT_PUBLIC_EMAILJS_PUBLIC_KEY=your_emailjs_public_key
NEXT_PUBLIC_EMAILJS_SERVICE_ID=your_service_id
NEXT_PUBLIC_EMAILJS_TEMPLATE_ID=your_template_id
Note: The NEXT_PUBLIC_ prefix ensures these will be available in the browser environment.
4. Initializing EmailJS in a React component
Before we write the code to send emails, we need to initialize EmailJS with our public key, which lets EmailJS verify your client requests. For example, in a plain HTML/JS environment, you might include a script and call emailjs.init(‘YOUR_PUBLIC_KEY’) as shown in their official documentation.
In our Next.js app, we can do this in our React component code. To keep things organized, create a new file: app/components/ContactForm.js (you may need to create the components folder inside app/). This will be a React component that includes a form and uses EmailJS to send the form data.
Then, open app/components/ContactForm.js and add the following code:
"use client"; // This directive is important to make this a Client Component (can use browser APIs)
import { useRef } from 'react';
import emailjs from '@emailjs/browser';
// Initialize EmailJS with your Public Key
emailjs.init(process.env.NEXT_PUBLIC_EMAILJS_PUBLIC_KEY);
export default function ContactForm() {
// useRef to access the form DOM node
const formRef = useRef();
const sendEmail = (e) => {
e.preventDefault(); // prevent default form submission behavior
if (!formRef.current) return;
const form = formRef.current;
// Use EmailJS to send form data
emailjs.sendForm(
process.env.NEXT_PUBLIC_EMAILJS_SERVICE_ID,
process.env.NEXT_PUBLIC_EMAILJS_TEMPLATE_ID,
form
)
.then((response) => {
console.log('SUCCESS!', response.status, response.text);
// You could show a success message to the user here
})
.catch((error) => {
console.error('FAILED...', error);
// Show an error message to the user if needed
});
// Optionally, reset the form or handle UI state after sending
form.reset();
};
return (
<form ref={formRef} onSubmit={sendEmail}>
<label>
Name:
<input type="text" name="name" required />
</label>
<br/>
<label>
Your Email:
<input type="email" name="email" required />
</label>
<br/>
<label>
Message:
<textarea name="message" rows="4" required></textarea>
</label>
<br/>
<button type="submit">Send Message</button>
</form>
);
}
Some notes:
- We import emailjs from the SDK. We call emailjs.init(…) with our public key to initialize the service (this is one-time; EmailJS requires the public key be set before sending). This line will pick up NEXT_PUBLIC_EMAILJS_PUBLIC_KEY from our environment variables (thanks to Next.js, it will be substituted into the code at build time).
- We use emailjs.sendForm(…) to send the form. This method automatically collects the values from the form fields specified by name. We provide it the Service ID, Template ID, and the form element. Under the hood, EmailJS will fill the template with the form values and send the email via the service (Mailtrap) we configured.
- The form includes fields name, email, and message, so make sure these names match the placeholders you used in your EmailJS template. For example, if your template expects {{name}}, {{email}}, {{message}}, this form is set up correctly.
5. Using the form in a Next.js page
Now, we need to use this ContactForm component in a page so that it’s rendered.
For example, let’s place it on app/page.js, which is the index page:
import ContactForm from "./components/ContactForm";
export default function Page() {
return (
<main>
<h1>Contact Us</h1>
<p>Fill out the form below to send us a message:</p>
<ContactForm />
</main>
);
}
Save the files and restart the dev server with npm run dev or yarn dev if not already running.
Then, open http://localhost:3000, and you should see your contact form. Try filling it out and submitting it.
On submission, if everything is configured correctly, the form should trigger the EmailJS send. In your terminal/console you should see a log “SUCCESS! 200 …” from the console.log in our code, indicating EmailJS accepted the request.
Send email with a backend using SMTP
For a server-side approach, we’ll use Next.js API routes (backend endpoints) to send emails using Node.js. The idea is to have the client (browser) make a request to a Next.js API route (for example, when a form is submitted), and that route will handle sending the email. This allows more complex email logic (like using libraries or interacting with databases) securely.
For this, we’ll use Nodemailer, a Node.js library that makes it easy to connect to an SMTP server via credentials and start sending emails right away.
1. Install Nodemailer
To install Nodemailer, you can either use npm:
npm install nodemailer
Or yarn:
yarn add nodemailer
2. Update SMTP credentials
Before we write any code, let’s update the .env.local file to include our Mailtrap SMTP credentials:
# EmailJS credentials
NEXT_PUBLIC_EMAILJS_PUBLIC_KEY=your_emailjs_public_key
NEXT_PUBLIC_EMAILJS_SERVICE_ID=your_service_id
NEXT_PUBLIC_EMAILJS_TEMPLATE_ID=your_template_id
# Mailtrap SMTP credentials
MAILTRAP_USER=api
MAILTRAP_PASS=your_mailtrap_smtp_password
Note: Did you notice how we don’t use the NEXT_PUBLIC_ prefix for these? Our MAILTRAP_USER and MAILTRAP_PASS environment variables will be used on the server side, so we don’t actually need to use the public prefix to expose them to the browser environment. This guarantees that the environment variables won’t be leaked to the client side.
3. Add email-sending logic
Now, let’s create a Next.js API route for sending email with Nodemailer. Create a file app/api/contact/route.js in your project:
// app/api/contact/route.js
import { NextResponse } from 'next/server';
import nodemailer from 'nodemailer';
export async function POST(request) {
try {
const data = await request.json(); // parse JSON body from the request
const { name, email, message } = data;
// Set up Nodemailer transporter with Mailtrap SMTP credentials
const transporter = nodemailer.createTransport({
host: "live.smtp.mailtrap.io", // Mailtrap SMTP host
port: 587, // Mailtrap SMTP port
auth: {
user: process.env.MAILTRAP_USER, // SMTP username from .env.local
pass: process.env.MAILTRAP_PASS // SMTP password from .env.local
}
});
// Email message options
const mailOptions = {
from: `"Contact Form" <noreply@yourdomain.com>`, // sender address
to: "your-email@example.com", // your email or whoever should get the contact message
subject: "New Contact Form Submission",
text: `You have a new message from ${name} (${email}):\n\n${message}`
// You can add html: "<p>...</p>" to send HTML emails as well
};
// Send the email
await transporter.sendMail(mailOptions);
return NextResponse.json({ ok: true, status: "Email sent successfully" });
} catch (error) {
console.error("Error sending email:", error);
return NextResponse.json({ ok: false, error: error.message }, { status: 500 });
}
}
Let’s explain this route handler:
- We configure a Nodemailer transporter using our Mailtrap SMTP settings. The host and port are provided by Mailtrap.
- This example uses “live.smtp.mailtrap.io” and port 587.
- We use process.env.MAILTRAP_USER and process.env.MAILTRAP_PASS that we stored in .env.local – Next.js will inject these at runtime. This gives Nodemailer the credentials to authenticate with the SMTP server.
- We create mailOptions which includes the sender from, the recipient to, the subject, and the text of the email. Here we’re constructing a simple text email that includes the message and the sender’s details.
- You should replace “your-email@example.com” with the actual email address where you want to receive the contact messages (this could be your personal or business email).
- The from field is set to a placeholder like noreply@yourdomain.com. Instead, you should use an email that matches the domain you verified on Mailtrap.
Now, your Next.js backend can send an email via SMTP whenever this /api/contact endpoint is hit with a POST request.
4. Use the API route
To trigger the sending logic from the client side, you need to make a POST request to /api/contact with the appropriate data.
For example, in your React component (perhaps the same contact form, if you want to now use the server approach instead of EmailJS), you could change the app/components/ContactForm.js file accordingly:
// Example: ContactForm using server API instead of EmailJS
"use client";
import { useState } from 'react';
export default function ContactFormServer() {
const [status, setStatus] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
const formData = {
name: e.target.name.value,
email: e.target.email.value,
message: e.target.message.value
};
try {
const res = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
const result = await res.json();
if (res.ok && result.ok) {
setStatus('Your message has been sent!');
e.target.reset();
} else {
throw new Error(result.error || 'Failed to send message');
}
} catch (err) {
console.error(err);
setStatus('Sorry, there was an error sending your message.');
}
};
return (
<form onSubmit={handleSubmit}>
<label>
Name: <input type="text" name="name" required/>
</label><br/>
<label>
Your Email: <input type="email" name="email" required/>
</label><br/>
<label>
Message: <textarea name="message" required></textarea>
</label><br/>
<button type="submit">Send Message</button>
{status && <p>{status}</p>}
</form>
);
}
This is a basic example: on submit, it posts to our /api/contact route, then handles the JSON response. If successful, it displays a success message and resets the form; if there’s an error, it shows an error message.
Send HTML email
With Nodemailer, sending an HTML email is as easy as adding an html field in the mailOptions in your app/api/contact/route.js file. Check it out:
// ... (transporter setup as above)
const mailOptions = {
from: `"Contact Form" <noreply@yourdomain.com>`, // sender address
to: "your-email@example.com", // your email or whoever should get the contact message
subject: "New Contact Form Submission",
html: `
<h1 style="color: teal;">Welcome to MyApp,</h1>
<p>Hi there, thanks for <b>joining MyApp</b>! We're excited to have you on board.</p>
<p>Feel free to explore our features and let us know if you have any questions.</p>
`,
// Note: We can still include a "text" field as a fallback for email clients that prefer plain text.
};
await transporter.sendMail(mailOptions);
Pro tip: It’s a good practice to also include a plain text fallback mailOptions.text for email clients that cannot render HTML.
Send email with attachment
If you want to send files via email (e.g., monthly reports, invoices, etc.), Nodemailer supports attachments via the attachments option in the mailOptions object, which you can update in the app/components/ContactForm.js file:
// Example: ContactForm using server API
"use client";
import { useState } from "react";
export default function ContactFormServer() {
const [status, setStatus] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("name", e.target.name.value);
formData.append("email", e.target.email.value);
formData.append("message", e.target.message.value);
// Handle multiple file attachments properly
[...e.target.attachments.files].forEach((file) =>
formData.append("attachments", file)
);
try {
const res = await fetch("/api/contact", {
method: "POST",
// Don't set Content-Type when using FormData - browser will set it automatically
// with the correct boundary parameter
body: formData,
});
const result = await res.json();
if (res.ok && result.ok) {
setStatus("Your message has been sent!");
e.target.reset();
} else {
throw new Error(result.error || "Failed to send message");
}
} catch (err) {
console.error(err);
setStatus("Sorry, there was an error sending your message.");
}
};
return (
<form onSubmit={handleSubmit}>
<label>
Name: <input type="text" name="name" required />
</label>
<br />
<label>
Your Email: <input type="email" name="email" required />
</label>
<br />
<label>
Message: <textarea name="message" required></textarea>
</label>
<br />
<label>
Attachments: <input type="file" name="attachments" multiple />
</label>
<br />
<button type="submit">Send Message</button>
{status && <p>{status}</p>}
</form>
);
}
Quick breakdown:
- The new input with type=”file” allows users to add attachments.
- The formData object can send attachments correctly to our API route.
- I also removed content type to allow the browser to detect the data format that we send and use multipart/form-data instead automatically.
Then, in the app/api/contact/route.js, modify the mailOptions object to include the new attachments property with the attachment data details in it as well as add the new logic to parse out the file attachments from the multipart/form-data object. Just like so:
// app/api/contact/route.js
import { NextResponse } from "next/server";
import nodemailer from "nodemailer";
export async function POST(request) {
try {
const formData = await request.formData();
const name = formData.get("name");
const email = formData.get("email");
const message = formData.get("message");
// Process attachments - convert File objects to buffers that Nodemailer can handle
const fileAttachments = formData.getAll("attachments");
const attachments = [];
// Process each file and convert to buffer
for (const file of fileAttachments) {
const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);
attachments.push({
filename: file.name,
content: buffer,
contentType: file.type,
});
}
// Set up Nodemailer transporter with Mailtrap SMTP credentials
const transporter = nodemailer.createTransport({
host: "live.smtp.mailtrap.io", // Mailtrap SMTP host
port: 587, // Mailtrap SMTP port
auth: {
user: process.env.MAILTRAP_USER, // SMTP username from .env.local
pass: process.env.MAILTRAP_PASS, // SMTP password from .env.local
},
});
// Email message options
const mailOptions = {
from: `"Contact Form" <noreply@yourdomain.com>`, // sender address
to: "your-email@example.com", // your email or whoever should get the contact message
subject: "New Contact Form Submission",
text: `You have a new message from ${name} (${email}):\n\n${message}`,
attachments: attachments,
};
// Send the email
await transporter.sendMail(mailOptions);
return NextResponse.json({ ok: true, status: "Email sent successfully" });
} catch (error) {
console.error("Error sending email:", error);
return NextResponse.json(
{ ok: false, error: error.message },
{ status: 500 }
);
}
}
Code breakdown:
- request.formData() parses the multipart/form-data object.
- formData.get() gets the required properties from the parsed form data.
- file.arrayBuffer() and Buffer.from() to get the compatible data format for the nodemailer attachments.
And one more thing: the attachments option can take objects with various properties. Nodemailer also allows content (e.g., a Buffer or base64 string) if you already have the file data in memory, and even a cid property for embedding images within HTML emails.
Send email to multiple recipients
To send an email to multiple recipients, simply provide a comma-separated list of emails or an array of email addresses in the to field, like so:
// ... (transporter setup as above)
const mailOptions = {
from: `"Contact Form" <noreply@yourdomain.com>`, // sender address
to: "alice@example.com, bob@example.com, charlie@example.com",
// You could also use an array, e.g.:
// to: ["alice@example.com", "bob@example.com", "charlie@example.com"],
subject: "New Contact Form Submission",
text: `You have a new message from ${name} (${email}):\n\n${message}`,
// You can add cc or bcc if needed, e.g.:
// cc: "customer-support@example.com",
// bcc: "admin@example.com",
};
await transporter.sendMail(mailOptions);
In the above example, the to field includes three email addresses separated by commas. This means the email will be sent to all three recipients..
Tip: You can also use cc to send a copy of the email to others so that all recipients can see who is included. There’s also bcc for blind copies, which is useful if you don’t want your recipients to see each other.
Send email using email API
If you want to automate and speed things up a bit, you can send emails server-side in Next.js via Mailtrap’s very own Node.js SDK that interacts with email API for sending emails.
First, install the Mailtrap Node.js client SDK:
npm install mailtrap
Before we write any code, let’s update the .env.local file to include our Mailtrap API credentials. You can get the Mailtrap API token from Sending Domains > Integration > API section.
# EmailJS credentials
NEXT_PUBLIC_EMAILJS_PUBLIC_KEY=your_emailjs_public_key
NEXT_PUBLIC_EMAILJS_SERVICE_ID=your_service_id
NEXT_PUBLIC_EMAILJS_TEMPLATE_ID=your_template_id
# Mailtrap SMTP credentials
MAILTRAP_USER=api
MAILTRAP_PASS=your_mailtrap_smtp_password
# Mailtrap API credentials
MAILTRAP_TOKEN=your_mailtrap_token
Let’s create a new Next.js API route for sending email with Mailtrap API. Create a file app/api/contact/mailtrap/route.js in your project and paste the following code snippet:
// app/api/contact/mailtrap/route.js
import { NextResponse } from "next/server";
import { MailtrapClient } from "mailtrap";
const mailtrap = new MailtrapClient({ token: process.env.MAILTRAP_TOKEN });
export async function POST(request) {
try {
const data = await request.json();
const { name, email, message } = data;
// Send email using Mailtrap
const result = await mailtrap.send({
from: { email: "noreply@yourdomain.com", name: "Contact Form" },
to: [{ email: "your-email@example.com" }],
subject: "New Contact Form Submission",
text: `You have a new message from ${name} (${email}):\n\n${message}`,
});
console.log("Email sent successfully:", result);
// Return success response
return NextResponse.json({ ok: true, status: "Email sent successfully" });
} catch (error) {
// Return error response
console.error("Error sending email:", error);
return NextResponse.json(
{ ok: false, error: error.message },
{ status: 500 }
);
}
}
Now, your Next.js backend can send an email via Mailtrap API whenever this /api/contact/mailtrap endpoint is hit with a POST request.
Using the API route
To trigger this from the client side, you need to make a POST request to /api/contact with the appropriate data.
For example, in your React component, you could change the app/components/ContactForm.js file accordingly to use /api/contact/mailtrap instead of /api/contact, as well as the application/json data format compatible with this new Next.js API route. Check it out:
// Example: ContactForm using Mailtrap API instead of Nodemailer SMTP
"use client";
import { useState } from 'react';
export default function ContactFormServer() {
const [status, setStatus] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
const formData = {
name: e.target.name.value,
email: e.target.email.value,
message: e.target.message.value
};
try {
const res = await fetch('/api/contact/mailtrap', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
const result = await res.json();
if (res.ok && result.ok) {
setStatus('Your message has been sent!');
e.target.reset();
} else {
throw new Error(result.error || 'Failed to send message');
}
} catch (err) {
console.error(err);
setStatus('Sorry, there was an error sending your message.');
}
};
return (
<form onSubmit={handleSubmit}>
<label>
Name: <input type="text" name="name" required/>
</label><br/>
<label>
Your Email: <input type="email" name="email" required/>
</label><br/>
<label>
Message: <textarea name="message" required></textarea>
</label><br/>
<button type="submit">Send Message</button>
{status && <p>{status}</p>}
</form>
);
}
How it works: On submit, it posts to our /api/contact/mailtrap route, and then handles the JSON response. If successful, it displays a success message and resets the form; if there’s an error, it shows an error message.
Send HTML email
Sending an HTML email through the Mailtrap API is similar to plain text; we just provide an html field instead of (or in addition to) the text field in the app/api/contact/mailtrap/route.js file.
Here’s an example of sending a welcome email with some HTML content:
// ... (MailtrapClient initialization setup as above)
await mailtrap.send({
from: { email: "noreply@yourdomain.com", name: "MyApp Team" },
to: [ { email: "your-email@example.com" } ],
subject: "🎉 Welcome to MyApp!",
html: `
<html>
<body style="font-family: Arial, sans-serif; line-height: 1.5;">
<h2>Hello and Welcome!</h2>
<p>Thank you for signing up for <b>MyApp</b>. We're thrilled to have you with us.</p>
<p>If you have any questions, just reply to this email--we're here to help.</p>
<p>Cheers,<br>The MyApp Team</p>
</body>
</html>
`
});
How it works: The html field can contain any HTML content as a string. You can construct it manually as shown, or load it from an HTML file or template. When the email is sent, the recipient will see the formatted HTML content.
Send email with attachment
To send attachments, simply add the attachments option in the mailOptions object:
// Example: ContactForm using Mailtrap API
"use client";
import { useState } from "react";
export default function ContactFormServer() {
const [status, setStatus] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("name", e.target.name.value);
formData.append("email", e.target.email.value);
formData.append("message", e.target.message.value);
// Handle multiple file attachments properly
[...e.target.attachments.files].forEach((file) =>
formData.append("attachments", file)
);
try {
const res = await fetch("/api/contact/mailtrap", {
method: "POST",
// Don't set Content-Type when using FormData - browser will set it automatically
// with the correct boundary parameter
body: formData,
});
const result = await res.json();
if (res.ok && result.ok) {
setStatus("Your message has been sent!");
e.target.reset();
} else {
throw new Error(result.error || "Failed to send message");
}
} catch (err) {
console.error(err);
setStatus("Sorry, there was an error sending your message.");
}
};
return (
<form onSubmit={handleSubmit}>
<label>
Name: <input type="text" name="name" required />
</label>
<br />
<label>
Your Email: <input type="email" name="email" required />
</label>
<br />
<label>
Message: <textarea name="message" required></textarea>
</label>
<br />
<label>
Attachments: <input type="file" name="attachments" multiple />
</label>
<br />
<button type="submit">Send Message</button>
{status && <p>{status}</p>}
</form>
);
}
In the app/api/contact/mailtrap/route.js, modify the mailOptions object to include the new attachments property with the attachment data details in it, as well as add the new logic to parse out the file attachments from the multipart/form-data object. Like so:
// app/api/contact/mailtrap/route.js
import { NextResponse } from "next/server";
import { MailtrapClient } from "mailtrap";
const mailtrap = new MailtrapClient({ token: process.env.MAILTRAP_TOKEN });
const toBase64 = async (file) => {
const bytes = await file.arrayBuffer();
const base64Content = Buffer.from(bytes).toString("base64");
return base64Content;
};
export async function POST(request) {
try {
// Get form data
const formData = await request.formData();
const name = formData.get("name");
const email = formData.get("email");
const message = formData.get("message");
const formAttachments = formData.getAll("attachments");
const attachments = await Promise.all(
formAttachments.map(async (attachment) => {
const content = await toBase64(attachment);
return {
filename: attachment.name,
disposition: "attachment",
content,
};
})
);
// Send email using Mailtrap
const result = await mailtrap.send({
from: { email: "noreply@yourdomain.com", name: "Contact Form" },
to: [{ email: "your-email@example.com" }],
subject: "🎉 Welcome to MyApp!",
text: `You have a new message from ${name} (${email}):\n\n${message}`,
attachments,
});
console.log("Email sent successfully:", result);
// Return success response
return NextResponse.json({ ok: true, status: "Email sent successfully" });
} catch (error) {
// Return error response
console.error("Error sending email:", error);
return NextResponse.json(
{ ok: false, error: error.message },
{ status: 500 }
);
}
}
Send email to multiple recipients
To send an email to multiple recipients using the Mailtrap SDK, you can add multiple objects in the to array. This is straightforward and similar to how we handled it with Nodemailer, except here we must use the array (since the Mailtrap SDK expects to to be an array of recipient objects).
In the app/api/contact/mailtrap/route.js, modify the mailOptions object to include the new to property with the recipients in it:
// ... (MailtrapClient setup)
const result = await mailtrap.send({
from: { email: "noreply@yourdomain.com", name: "Contact Form" },
to: [
{ email: "alice@example.com" },
{ email: "bob@example.com" },
{ email: "charlie@example.com" },
],
subject: "🎉 Welcome to MyApp!",
text: `You have a new message from ${name} (${email}):\n\n${message}`,
});
Test emails and email sending
Now, before moving to production, I strongly advise you to test your emails and sending workflow. Otherwise, you risk having your recipients receive improperly formatted emails, hitting spam filters, getting blacklisted, and more.
So, how do you test your emails without filling your personal inbox or spamming your recipients?
For this, I recommend Mailtrap Email Testing, an inseparable part of the Mailtrap Email Delivery Platfor
Email Testing provides a safe environment to inspect and debug emails in staging, dev, and QA environments. On top of this, it also offers a plethora of other features that allow you to:
- Inspect whether your HTML emails are rendered properly by different email clients like Gmail, Yahoo, Outlook, etc.
- Fix your emails according to spam points, avoid spam filters, and prevent potential email deliverability issues once your app moves to production.
- Get useful technical information like SMTP transaction and email header info.
With that said, let me show you how easy to set up and use Mailtrap Email Testing is!
SMTP
Create a free Mailtrap account and then:
- Head to the Email Testing tab and select your inbox.
- Copy your credentials from the SMTP Settings tab.
- Insert your Mailtrap fake SMTP server credentials into your env.local file, like so:
# EmailJS credentials
NEXT_PUBLIC_EMAILJS_PUBLIC_KEY=your_emailjs_public_key
NEXT_PUBLIC_EMAILJS_SERVICE_ID=your_service_id
NEXT_PUBLIC_EMAILJS_TEMPLATE_ID=your_template_id
# Mailtrap SMTP credentials (Should be testing credentials after updating)
MAILTRAP_USER=your_mailrap_testing_smtp_username
MAILTRAP_PASS=your_mailtrap_testing_smtp_password
# Mailtrap API credentials
MAILTRAP_TOKEN=your_mailtrap_token
- Update the Next.js API Route to use the testing SMTP host in the transporter setup in the app/api/contact/route.js file.
// Set up Nodemailer transporter with Mailtrap SMTP credentials
const transporter = nodemailer.createTransport({
host: "sandbox.smtp.mailtrap.io", // Mailtrap Testing SMTP host
port: 2525, // Mailtrap Testing SMTP port
auth: {
user: process.env.MAILTRAP_USER, // SMTP username from .env.local
pass: process.env.MAILTRAP_PASS, // SMTP password from .env.local
},
});
From now on, whenever you trigger your Next.js API Route either manually or by submitting a form, an email will be sent only to your Mailtrap testing inbox.
API
- Navigate to Email Testing → Integration → API section.
- Obtain the Inbox ID and the API token for the testing inbox.
- Update the .env.local file to include the API token:
# EmailJS credentials
NEXT_PUBLIC_EMAILJS_PUBLIC_KEY=your_emailjs_public_key
NEXT_PUBLIC_EMAILJS_SERVICE_ID=your_service_id
NEXT_PUBLIC_EMAILJS_TEMPLATE_ID=your_template_id
# Mailtrap SMTP credentials (Should be testing credentials after updating)
MAILTRAP_USER=your_mailrap_testing_smtp_username
MAILTRAP_PASS=your_mailtrap_testing_smtp_password
# Mailtrap API credentials (Should be testing token after updating)
MAILTRAP_TOKEN=your_mailtrap_testing_token
- Navigate to the Account Settings tab and obtain your Account ID.
- Update the app/api/contact/mailtrap/route.js file with Testing API token, Inbox ID, and Account ID.
- Also, when using testing API credentials, we have to set the sandbox: true property:
const mailtrap = new MailtrapClient({
token: process.env.MAILTRAP_TOKEN,
testInboxId: process.env.TEST_INBOX_ID,
accountId: process.env.ACCOUNT_ID,
sandbox: true,
});
Wrapping up
And with that, we’ve come to the end of our article!
Feel free to use it as your go-to guide for setting up a new project, adding sendingfunctionality, and, last but not least, testing your emails!
If you feel like expanding your JavaScript knowledge, be sure to check out our blog, where you can find other related articles, such as:
- Send Emails in Vercel Next.js App
- Nodemailer vs EmailJS
- How to Send Emails in JavaScript
- JavaScript Contact Form