Building an app or contact form on Netlify and need to add email sending functionality to it?
In this tutorial, I’ll show you how to do it via:
- Netlify Forms for simple form submission emails [click here to jump ahead]
- SMTP via Mailtrap for custom email sending [click here to jump ahead]
- Mailtrap’s Email API for a more scalable solution. [click here to jump ahead]
You can use all of these methods at once according to your needs, but before we start, make sure you have the following in place:
- Mailtrap account – If you haven’t already, sign up for a free account since Mailtrap provides the SMTP service and Email API, as well as the Email Testing inbox we’ll be using for this project.
- Netlify Account – Sign up with GitHub, GitLab, or email via the Netlify website, create a Team and proceed until the dashboard is accessible.
- Vite bootstrap manager – To bootstrap our project, we’ll use Vite, which you can download here.
- Node.js v18+ – To install the latest Node.js version, click here. While you’re at it you can also install the latest npm or yarn versions, which you’ll need to run some commands.
- Netlify CLI (Netlify Command-Line Interface) – Run
npm install netlify-cli -g
oryarn global add netlify-cli
. This installs thenetlify
command. You can verify the installation by runningnetlify --version
. Next, log in via CLI by runningnetlify login
(this will open a browser to authenticate your Netlify account).
Last but not least, I’ll show you my email testing process, so you can make sure your email-sending functionality works the way you intend it to.
Disclaimer: Every bit of code in this article has been prepared and tested by a developer before publication.
Send emails using Netlify Forms
Netlify Forms is a built-in Netlify feature that allows your deployed site to capture form submissions without any server-side code. Netlify will collect submissions from a form in your static site and even send you email notifications when someone submits the form.
Who is it for?
If your form is simple and you just need to be notified of submissions (for example, a “Contact Us” form that emails you the message), Netlify Forms is the quickest solution since it handles form data capture and notification for you. Keep in mind that the content of the notification email is not highly customizable, and it will contain the form data in a basic format.
Also, if you need to send custom emails or have complex email content, you might opt for the SMTP or API methods instead.
Now that we’ve got the theory out of the way, let’s create a React application and set up a React form step by step.
Step 1. Bootstrap your React app
Note: If you already have a React app you want to integrate Netlify Forms into, you can skip this step. 💡
To bootstrap our project, we’ll use Vite. For this, you can use the npm package manager:
# Create a new Vite project named 'netlify-emails' using the React template
npm create vite@latest netlify-emails -- --template react
# Move into the newly created project directory
cd netlify-emails
# Install all dependencies listed in package.json
npm install
Or yarn package manager:
# Create a new Vite project named 'netlify-email-demo' using the React template
yarn create vite@latest netlify-emails -- --template react
# Move into the newly created project directory
cd netlify-emails
# Install all dependencies listed in package.json
yarn install
his will create and initialize a React project in the netlify-emails directory, which should look something like this:
And the install
command should give you the following message along with dependencies:
Step 2. Add a static form to index.html
In a typical React app, which is a single-page application, our form is defined in JSX (e.g., in src/App.jsx or a separate component). However, Netlify’s bots only detect forms that exist in the static HTML of the site during the build process, not forms that are purely rendered client-side with JavaScript. So, to ensure Netlify picks up our form, we will do two things:
- Include a static HTML version of the form in the app’s index.html (this file is served as the base HTML). This hidden form will be used by Netlify to detect and set up the form submission handling.
- Include a corresponding React form component with the same form name and a hidden field that links to the static form.
Note: The same principle applies for all JavaScript-rendered forms. If your app doesn’t use JavaScript-rendered forms (e.g., you have a simple static website without any frameworks used), you can simply use the HTML form we’ll reveal later in the guide without the hidden attribute.
First, copy/paste the code snippet into your index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<form name="contactForm" netlify hidden>
<input type="text" name="name" />
<input type="email" name="email" />
<textarea name="message"></textarea>
</form>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
Code breakdown:
- Here, we use a form with
name="contactForm"
. - The
netlify
attribute enables Netlify’s form detection, andhidden
ensures this form is not visible on the page since it’s just a dummy form for detection. - We only included the essential fields such as
name
,email
, andmessage
without any labels or a submit button because the real form UI will be in React. - The static form’s purpose is solely to help Netlify’s bot parse our form fields—the bot can’t execute React, so it relies on this HTML.
Step 3. Create your React form UI
Now, in your React app, create the actual form that users will see and submit. It should have the same form name as the static form and include a hidden input form-name to tie it to Netlify Forms.
To do this, create a ContactForm.jsx file in the src folder and copy/paste the following code inside:
import React from "react";
function ContactForm() {
return (
<form name="contactForm" method="POST" className="contact-form">
{/* Hidden input to allow Netlify to map this form to the static form */}
<input type="hidden" name="form-name" value="contactForm" />
{/* Form fields */}
<label htmlFor="name" className="label">
Your Name:{" "}
<input type="text" name="name" placeholder="Enter your name" required />
</label>
<label htmlFor="email" className="label">
Your Email:{" "}
<input
type="email"
name="email"
placeholder="Enter your email"
required
/>
</label>
<label htmlFor="message" className="label">
Message:{" "}
<textarea
name="message"
placeholder="Type your message here"
required
></textarea>
</label>
<button type="submit">Send</button>
</form>
);
}
export default ContactForm;
Code breakdown:
- We set
name="contactForm"
on the<form>
to match the static form’sname
. - We included
method="POST"
because Netlify requires form submissions to be POST requests. - The crucial hidden input
<input type="hidden" name="form-name" value="contactForm" />
links this rendered form to the static form definition Netlify’s bot saw.- The hidden field’s value should exactly match the form’s
name
. So when the form is submitted, Netlify knows it corresponds to the form configuration for“contactForm”
.
- The hidden field’s value should exactly match the form’s
- Again, we included basic fields for
name
,email
, andmessage
, with name attributes matching those in the static form. The required attribute ensures the user fills them out.
Step 4. Import and render the form in your app
Next, you need to update the App.jsx file located in the src folder to render your form component.
To do this, simply replace the default template code with the code from the snippet below:
import ContactForm from "./ContactForm";
import "./App.css";
function App() {
return <ContactForm />;
}
export default App;
Step 5. Style your form
Lastly, let’s make some changes to src/App.css so that the form is positioned correctly:
#root {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
}
.label {
margin: 10px 0;
display: flex;
flex-direction: column;
gap: 8px;
}
Now we have a React form that users can interact with, and thanks to the hidden static form, Netlify will recognize it as a form to capture.
Step 6. Install and log into Netlify CLI
Next, let’s deploy your site to Netlify so that Netlify can register the form.
For a quick deployment, we’ll use Netlify CLI. If you haven’t already, make sure to install it, but if you have, make sure to verify it. For this, use either npm:
# Install the Netlify CLI globally
npm install netlify-cli -g
# Log into your Netlify account
netlify login
Or yarn:
# Install the Netlify CLI globally
yarn global add netlify-cli
# Log into your Netlify account
netlify login
Important: If you experience any issues with installing netlify-cli
globally using yarn, please make sure the .yarn/bin is included in your terminal’s PATH, which you can check by running echo $PATH
. If it’s not there, you need to add it. And depending on your shell (e.g., bash, zsh, or fish), add the following to your shell config file. For ~/.bashrc
, ~/.bash_profile
or ~/.zshrc
:
export PATH="$PATH:$(yarn global bin)"
Then restart your terminal or run:
source ~/.zshrc # or ~/.bashrc
Step 7. Build and initialize the Netlify project
To deploy our form to Netlify, we need to create a build version of our React app by running the following production build command:
npm:
npm run build
yarn:
yarn build
Inside the project folder, run the following command to initialize the Netlify site:
netlify init
If you’re creating a new build, you’ll be asked if you want to create without a git repository. Choose Yes, create and deploy site manually.
Then, you will be asked to pick a team and a site name (you can change the site name later in the Netlify Dashboard). A team to select should already be present if you have already registered your Netlify account.
This essentially registers a new site on Netlify and associates your local project with it.
Now, if you log in to your Netlify account, you should see the following page:
Note: If you prefer, you could also create a site through the Netlify web UI and then use netlify link
to connect it. However, netlify init
can handle both in one go.
Step 8. Deploy the site to Netlify
Moving on, since we have created a build of our project and initialized a Netlify project, let’s deploy our application to Netlify by running:
netlify deploy --prod --dir=dist
This command uploads the contents of the build directory to Netlify and deploys your site live. The --prod
flag does a production deployment but you could do a draft deploy without it.
Again, ensure the build step was run first, otherwise you might deploy an empty site.
Now, if you go to your Netlify dashboard, you will be greeted with a ‘Deploy success!’ message and the live URL of your site (e.g., https://your-site-name.netlify.app or in this demo’s case https://netlify-emails.netlify.app).
If you’ve followed everything correctly so far and if you open this URL in a browser, you your React app running, which should look something like this:
I know, it’s as pale as a paper, but feel free to style it according to your taste in src/App.css.
Step 9. Verify the form on Netlify and test a submission
Before testing the form, make sure to enable Form Detection for the created website in the Netlify dashboard.
If you reload the page, you should see that the Form detection is enabled ✅
Pro tip: During development, you can test Netlify Forms locally by simulating a form submission with a tool like curl
or using netlify dev
(Netlify’s local dev server). However, form submissions won’t show up in the Netlify UI until deployed. So, it’s often easier to deploy a draft version using netlify deploy
without --prod
to test the form submission on the hosted URL.
To test the form submission, fill out the form on your deployed site and submit it. Netlify will handle the form POST request. By default, after submission, you will see a Thank you! Confirmation message.
Want a custom thank you page? Add an action="/thank-you"
attribute to your form and create a corresponding page (or route in React) for that path. This is optional, but improves UX by showing a confirmation message.
At this point, your form submission will be captured by Netlify. You can view submissions in the Netlify UI under the Forms section (click on your form name to see a list of submissions and their field data).
Note: If you don’t see a list of active forms in the Forms tab, try to re-deploy your Netlify application with netlify deploy --prod --dir=dist
. However, keep in mind that it can sometimes take 10-15 minutes and a few page reloads for it to show up.
Enable Form Notifications
On top of being able to store submissions, Netlify Forms can also send you an email notification automatically for each new submission.
To enable email notifications for form submissions, follow these steps:
- Log in to Netlify and navigate to your site.
- Go to the Forms section, and click Form notifications.
- Scroll down, find Form submission notifications section, and click Add notification → Email notification.
- Choose the following:
- Event to listen for: New form submission
- Email to notify: The email address where you want to receive the form data
- Personally, I recommend creating and using a dedicated email for this.
- Custom email subject: This is optional, the default one will be used otherwise. But, if you need inspiration, we have a dedicated article for this. ⬅️
- Form: The form you want to track to send notifications (any form by default). In this demo’s case, it will be contactForm.
- Lastly, don’t forget to hit the Save button.
Now, Netlify will send an email to the specified address every time a submission is received. The email will come from formresponses@netlify.com by default and will include all the form fields and their values.
Technical tidbits:
If your form includes a field named email
like the one we added for the user’s email, Netlify’s notification will set the “Reply-To”
header to that email, so you can directly hit “Reply” in your email client to respond to the submitter
As mentioned before, the default email subject will be something generic like “New submission from [form-name]”. Since we customized the subject in the Netlify UI when creating an email notification subscription, it will use the provided subject instead.
Additionally, you can achieve the same by adding a hidden input field named subject
in your form HTML with the desired subject line. For example, in the static HTML form, you could add this line:
<input type="hidden" name="subject" value="New message from my site" />
Send emails using SMTP
In this approach, you will create a Netlify Function (a serverless function) that uses a popular Node.js library Nodemailer to send an email via a Mailtrap’s SMTP server.
Netlify Functions allow you to run backend code (e.g., an email-sending script) on Netlify’s servers in response to events (like an HTTP request). We’ll create a function that gets triggered when a form is submitted (or when an API endpoint is hit from your React app) and sends an email via SMTP.
Who is it for?
This method is ideal when you need to customize the email content or recipients beyond what Netlify Forms offers.
Now, let’s get down to some coding!
Step 1. Create the functions directory
By default, Netlify looks for serverless functions in a directory (e.g., netlify/functions).
So, let’s create a folder named netlify inside your project, with another one inside it named, you’ve guessed it, functions. Then, In the netlify/functions folder, create a file named send-email.js (you can name it something descriptive; the function name will be the file name).
This will be our serverless function to send an email via SMTP, which will hold our Nodemailer configuration.
Step 2: (Optional) Add a netlify.toml file
You may also create a netlify.toml file in the project root to explicitly define the functions directory, although Netlify CLI usually handles this automatically. If you decide to do so, add the following to netlify.toml:
[functions]
directory = "netlify/functions"
This tells Netlify to look in netlify/functions for your serverless function files.
Step 3. Install Nodemailer
Next, if you haven’t already, install Nodemailer in your project directory:
npm:
npm install nodemailer
yarn:
yarn add nodemailer
This will add Nodemailer to your project’s dependencies, which Netlify will include when deploying the function.
Step 4. Configure environment variables
By hardcoding sensitive credentials in your function code, you leave them vulnerable during transmission. So, I recommend using environment variables for the Mailtrap SMTP credentials.
To do this, log in to your Mailtrap account, go to your Sending Domains tab and find the Integration details. You will see a host (e.g., live.smtp.mailtrap.io), a port (e.g., 587), and an SMTP Username and Password for that sending domain.
You can add these as environment variables to Netlify, by running the following two commands:
netlify env:set MAILTRAP_USER replace_with_your_mailtrap_user
netlify env:set MAILTRAP_PASS replace_with_your_mailtrap_pass
Of course, don’t forget to replace the placeholders with your actual username and password provided by Mailtrap.
Step 5. Create the serverless function and add a sending logic
Now, to make Nodemailer connect to Mailtrap’s SMTP and send a plain-text email (I will describe HTML in the following chapter), go to the send-email.js file we created in step 1.
Once there, paste the following configuration inside it to send a plain-text email:
import nodemailer from "nodemailer";
export default async function handler(req) {
const bodyText = await req.text();
const { data } = JSON.parse(bodyText);
const transporter = nodemailer.createTransport({
host: "live.smtp.mailtrap.io",
port: 587,
auth: {
user: process.env.MAILTRAP_USER,
pass: process.env.MAILTRAP_PASS,
},
});
const mailOptions = {
from: "no-reply@yourdomain.com",
to: "you@example.com",
subject: "New Contact Form Submission",
text: `You have a new form submission!\n\nName: ${data.name}\nEmail: ${data.email}\nMessage: ${data.message}`,
};
try {
await transporter.sendMail(mailOptions);
return new Response(
JSON.stringify({ message: "Email sent successfully" }),
{
headers: { "Content-Type": "application/json" },
status: 200,
}
);
} catch (error) {
return new Response(
JSON.stringify({ error: "Failed to send email", details: error.message }),
{
headers: { "Content-Type": "application/json" },
status: 500,
}
);
}
}
Step 6. Deploy the function
Next, let’s deploy our function by running the following command:
netlify deploy --prod
After you run the command, you should see your function the Netlify dashboard:
Step 7. Trigger the function on form submission
To trigger the function and send an email when your form is submitted, we will use Netlify Forms to capture the form, but set up a Netlify Forms notification webhook that triggers the function. In that case, Netlify will send form submissions to a webhook URL, which could be your function URL. The body
of the POST request will contain data
field with all the provided properties from the filled inputs.
Okay, so, navigate to Site Configuration → Notifications, scroll to the Form submission notifications, and select HTTP POST request:
Then, in the following window, specify your Netlify Function URL at the URL to notify field, which should be an endpoint generated by Netlify, something like https://<your-site>.netlify.app/.netlify/functions/send-email
. You can find this endpoint by going to Logs → Functions in your Dashboard.
And here’s what the HTTP POST request window should look like when filled out:
And that’s it! If you fill out your form and trigger the function, the Nodemailer code will execute and send an email via Mailtrap’s SMTP. If you check your Mailtrap account’s Email Logs, you should see the email that was sent, containing the subject and text as we’ve configured it.
Further in the guide we’ll use Netlify Functions based approach with HTTP notification triggers.
Troubleshooting: If your emails are not showing up in your Mailtrap email logs:
- Double-check the SMTP credentials. You can test them by using the code snippet provided in Mailtrap’s inbox page (Mailtrap often provides a sample Nodemailer config to copy).
- Ensure you used the correct user/password pair for the inbox and that those are set in the Netlify environment.
- Check Netlify function logs (in the Netlify dashboard under Logs > Functions) for any errors. Common mistakes include forgetting to set environment vars or using the wrong host/port.
Send HTML email
With Nodemailer, you can send HTML content simply by adding an html
field to the mail options.
For example, here’s a code snippet you can use and customize according to your liking:
import nodemailer from "nodemailer";
export default async function handler(req) {
const bodyText = await req.text();
const { data } = JSON.parse(bodyText);
const transporter = nodemailer.createTransport({
host: "live.smtp.mailtrap.io",
port: 587,
auth: {
user: process.env.MAILTRAP_USER,
pass: process.env.MAILTRAP_PASS,
},
});
const mailOptions = {
from: "no-reply@yourdomain.com",
to: "you@example.com",
subject: "New Contact Form Submission",
text: `You have a new form submission!\n\nName: ${data.name}\nEmail: ${data.email}\nMessage: ${data.message}`,
html: `
<h1>New Contact Form Submission</h1>
<p><strong>Name:</strong> ${data.name}</p>
<p><strong>Email:</strong> ${data.email}</p>
<p><strong>Message:</strong><br/>${data.message}</p>
`,
};
try {
await transporter.sendMail(mailOptions);
return new Response(
JSON.stringify({ message: "Email sent successfully" }),
{
headers: { "Content-Type": "application/json" },
status: 200,
}
);
} catch (error) {
return new Response(
JSON.stringify({ error: "Failed to send email", details: error.message }),
{
headers: { "Content-Type": "application/json" },
status: 500,
}
);
}
}
Code explanation: Here, we’ve crafted a simple HTML string for the html
property, which includes some basic HTML tags to bold the field names and break lines for the message. The email still includes the text version for email clients that prefer plain text or can’t render HTML.
Next, deploy the updated function with:
netlify deploy --prod --dir=dist
Now, when you submit your form, you should receive a nicely formatted HTML email.
Additionally, you can use Mailtrap’s Preview to see the rendered HTML, which helps you make sure that your formatting is correct.
Send emails with attachments
If you want to attach a static file to every email regardless of user input, first, put an image or file you want to attach to your function folder, just like so:
Then, make sure to specify your file in netlify.toml to get it included during build:
[functions]
directory = "netlify/functions"
included_files = ["netlify/functions/image.jpeg"]
And to make Nodemailer send attachments, simply add an attachments
array in the mail options, for example:
import nodemailer from "nodemailer";
import { join } from "path";
export default async function handler(req) {
const bodyText = await req.text();
const { data } = JSON.parse(bodyText);
const transporter = nodemailer.createTransport({
host: "live.smtp.mailtrap.io",
port: 587,
auth: {
user: process.env.MAILTRAP_USER,
pass: process.env.MAILTRAP_PASS,
},
});
const mailOptions = {
from: "no-reply@yourdomain.com",
to: "you@example.com",
subject: "New Contact Form Submission",
text: `You have a new form submission!\n\nName: ${data.name}\nEmail: ${data.email}\nMessage: ${data.message}`,
html: `
<h1>New Contact Form Submission</h1>
<p><strong>Name:</strong> ${data.name}</p>
<p><strong>Email:</strong> ${data.email}</p>
<p><strong>Message:</strong><br/>${data.message}</p>
`,
attachments: [
{
filename: "image.jpeg",
path: join(__dirname, "image.jpeg"),
},
],
};
try {
await transporter.sendMail(mailOptions);
return new Response(
JSON.stringify({ message: "Email sent successfully" }),
{
headers: { "Content-Type": "application/json" },
status: 200,
}
);
} catch (error) {
return new Response(
JSON.stringify({ error: "Failed to send email", details: error.message }),
{
headers: { "Content-Type": "application/json" },
status: 500,
}
);
}
}
Code breakdown:
- In the attachment object, we’ve specified
filename
since it will be used as a name of the file when attaching to an email. - We’ve also specified
path
. - There is also
join
, which provides global variable__dirname
and our attachment file name in order to create a correct path string for the attachment.
Moving on, deploy the updated function with:
netlify deploy --prod --dir=dist
And as soon as you submit a form, you should see the attachment appear in a new Mailtrap email logs, where you can verify that the attachment is present.
Note: If your React app needs to allow file uploads (for example, a user attaches an image in the form), implementing that involves more work. A simpler approach is to have the React app send the file to the Netlify function via a fetch (as Base64 or using FormData in fetch). The function can then decode that and use it in the attachment content.
Due to scope, we won’t implement a full file upload here, but know that Nodemailer can take a base64
string as an attachment by specifying content
and encoding: 'base64'
. So you could, for example, send the file as base64 string from the frontend and then specify the example below in your function, where data.fileBase64
is the base64 string of the file content.
attachments: [
{
filename: data.filename,
content: data.fileBase64,
encoding: 'base64'
}
]
Send emails to multiple recipients
If you want to send the same email to multiple recipients (for example, notify several people of a form submission), you can simply add them in an array to to
.
Here’s a code snippet you can use:
import nodemailer from "nodemailer";
export default async function handler(req) {
const bodyText = await req.text();
const { data } = JSON.parse(bodyText);
const transporter = nodemailer.createTransport({
host: "live.smtp.mailtrap.io",
port: 587,
auth: {
user: process.env.MAILTRAP_USER,
pass: process.env.MAILTRAP_PASS,
},
});
const mailOptions = {
from: "no-reply@yourdomain.com",
to: ["you@example.com", "colleague@example.com"],
subject: "New Form Submission",
text: "There is a new submission. Please check the dashboard for details.",
// cc and bcc can also be used:
// cc: 'manager@example.com',
// bcc: 'audit@example.com',
};
try {
await transporter.sendMail(mailOptions);
return new Response(
JSON.stringify({ message: "Email sent successfully" }),
{
headers: { "Content-Type": "application/json" },
status: 200,
}
);
} catch (error) {
return new Response(
JSON.stringify({ error: "Failed to send email", details: error.message }),
{
headers: { "Content-Type": "application/json" },
status: 500,
}
);
}
}
After making these changes, deploy the function again:
netlify deploy --prod --dir=dist
Send emails using email API
Mailtrap offers a RESTful Email Sending API and an official Node.js SDK for it. So instead of dealing with SMTP servers and ports, you will interact with Mailtrap’s API by calling functions or endpoints. Under the hood, the result is the same (emails get sent), but the interface is different.
Who is it for?
If you prefer not to manage SMTP details, the API/SDK is convenient. For small-scale usage, both SMTP and API work; so it often comes down to personal preference or specific feature needs.
Now, let’s get down to coding:
Step 1. Create a new API-based function
The setup for using the Mailtrap API via a Netlify Function is similar to the SMTP function. We can make a new function, say send-email-api.js, in the same netlify/functions directory. This will contain our code to send email using Mailtrap’s SDK.
Step 2. Install Mailtrap SDK
If you haven’t already, install the mailtrap Node.js package.
npm:
npm install mailtrap
yarn:
yarn add mailtrap
Step 3. Configure Mailtrap API credentials
To use the Mailtrap Email Sending API, you need an API token. This token is found in your Mailtrap account in Sending Domains > Integration > API. Save this API key in your Netlify environment variables as well (just like we did for SMTP creds).
netlify env:set MAILTRAP_API_TOKEN replace-with-your-api-token
(Or add MAILTRAP_API_TOKEN
in the Netlify UI). We can also set an environment variable for the sender email or domain if needed.
Step 4. Write the API-based Netlify function
Open netlify/functions/send-email-api.js. To send a plain-text email, let’s use Mailtrap API:
import { MailtrapClient } from "mailtrap";
const token = process.env.MAILTRAP_API_TOKEN;
const senderEmail = "no-reply@yourdomain.com";
const mailtrap = new MailtrapClient({ token: token });
export default async function handler(req) {
const { data } = JSON.parse(await req.text());
const email = {
from: { email: senderEmail, name: "Mailtrap" },
to: [{ email: "you@example.com" }],
subject: "New Contact Form Submission (API)",
text: `New submission from ${data.name} - ${data.message}`,
};
try {
await mailtrap.send(email);
return new Response(
JSON.stringify({ message: "Email sent via API successfully" }),
{
headers: { "Content-Type": "application/json" },
status: 200,
}
);
} catch (error) {
console.error("Mailtrap API send error:", error);
return new Response(
JSON.stringify({
error: "API email send failed",
details: error.message,
}),
{
headers: { "Content-Type": "application/json" },
status: 500,
}
);
}
}
What we did:
- Imported
MailtrapClient
from themailtrap
package and initialized the client with the API token:new MailtrapClient({ token: token })
. - Defined a sender email. This must be a domain/address that Mailtrap knows is authorized (Sending Domains tab). In this example, yourdomain.com should be replaced with whatever domain you set up in Mailtrap.
- Constructed the email object with the required fields:
from:
an object with email and optionally name.to:
the recipients array.subject:
subject line.text:
plaintext content.
Lastly, deploy this function using Netlify CLI:
netlify deploy --prod --dir=dist
Step 5. Update form submission notifications
Now, let’s update the submission notifications to use this new Netlify Function instead of the SMTP-based one.
Once deployed and notifications set, trigger either using curl or by filling the form again. If everything is set up correctly, the Mailtrap API will accept the request of sending an email using Mailtrap SDK based Netlify Function.
In summary, sending a plain text email via the Mailtrap API is as straightforward as calling mailtrap.send()
with the necessary fields. If the function executes with a 200 response, you can be confident the request was accepted. However, note that the actual delivery might happen a second or two later.
Of course, you can check Mailtrap’s Email Logs for the status of the email.
Send HTML email
To send an HTML email using the Mailtrap API, you just include an html
property in the email payload. For this, simply insert the following code snippet inside the netlify/functions/send-email-api.js file:
import { MailtrapClient } from "mailtrap";
const token = process.env.MAILTRAP_API_TOKEN;
const senderEmail = "no-reply@yourdomain.com";
const mailtrap = new MailtrapClient({ token: token });
export default async function handler(req) {
const { data } = JSON.parse(await req.text());
const email = {
from: { email: senderEmail, name: "Mailtrap" },
to: [{ email: "you@example.com" }],
subject: "New Contact Form Submission (API)",
text: `New submission from ${data.name} - ${data.message}`,
html: `
<h1>Welcome!</h1>
<p>Thank you for contacting us, <b>${data.name}</b>.</p>
<p>We're glad to have you on board.</p>
`,
};
try {
await mailtrap.send(email);
return new Response(
JSON.stringify({ message: "Email sent via API successfully" }),
{
headers: { "Content-Type": "application/json" },
status: 200,
}
);
} catch (error) {
console.error("Mailtrap API send error:", error);
return new Response(
JSON.stringify({
error: "API email send failed",
details: error.message,
}),
{
headers: { "Content-Type": "application/json" },
status: 500,
}
);
}
}
This would send both plain text and HTML versions since Mailtrap uses the text
as the fallback for clients that can’t read HTML, as is according to industry-best practices.
After adding HTML, deploy/test again with:
netlify deploy --prod --dir=dist
And if you go to Mailtrap Email Logs, you can preview your HTML email in source code or see how it’s rendered on multiple devices.
Send emails with attachments
To add attachments via the SDK, you can include an attachments
array in the email
object.
Unlike Nodemailer, where we just provided a path, for this approach we need to call fs.readFileSync
to get the content of the image. For example:
import { MailtrapClient } from "mailtrap";
import { join } from "path";
import fs from "fs";
const token = process.env.MAILTRAP_API_TOKEN;
const senderEmail = "no-reply@yourdomain.com";
const mailtrap = new MailtrapClient({ token: token });
export default async function handler(req) {
const { data } = JSON.parse(await req.text());
const image = fs.readFileSync(join(__dirname, "image.jpeg"));
const email = {
from: { email: senderEmail, name: "Mailtrap" },
to: [{ email: "you@example.com" }],
subject: "New Contact Form Submission (API)",
text: `New submission from ${data.name} - ${data.message}`,
attachments: [
{
filename: "image.jpeg",
content: image,
disposition: "attachment",
},
],
};
try {
await mailtrap.send(email);
return new Response(
JSON.stringify({ message: "Email sent via API successfully" }),
{
headers: { "Content-Type": "application/json" },
status: 200,
}
);
} catch (error) {
console.error("Mailtrap API send error:", error);
return new Response(
JSON.stringify({
error: "API email send failed",
details: error.message,
}),
{
headers: { "Content-Type": "application/json" },
status: 500,
}
);
}
}
If you have multiple attachments, you can push multiple objects to the attachments
array.
Of course, don’t forget to deploy the function with:
netlify deploy --prod --dir=dist
Send emails to multiple recipients
With the Mailtrap API, sending to multiple recipients is straightforward, as you can just put multiple objects in the to
array when building the email object.
Here’s a code snippet you can copy in your send-email-api.js file:
import { MailtrapClient } from "mailtrap";
const token = process.env.MAILTRAP_API_TOKEN;
const senderEmail = "no-reply@yourdomain.com";
const mailtrap = new MailtrapClient({ token: token });
export default async function handler(req) {
const { data } = JSON.parse(await req.text());
const email = {
from: { email: senderEmail, name: "Mailtrap" },
to: [
{ email: "recipient1@example.com" },
{ email: "recipient2@example.com" },
],
subject: "New Contact Form Submission (API)",
text: `New submission from ${data.name} - ${data.message}`,
};
try {
await mailtrap.send(email);
return new Response(
JSON.stringify({ message: "Email sent via API successfully" }),
{
headers: { "Content-Type": "application/json" },
status: 200,
}
);
} catch (error) {
console.error("Mailtrap API send error:", error);
return new Response(
JSON.stringify({
error: "API email send failed",
details: error.message,
}),
{
headers: { "Content-Type": "application/json" },
status: 500,
}
);
}
}
This will instruct the Mailtrap API to send two emails (1 per recipient). However, if you need to send individualized emails (like customizing content per recipient), you can loop and call mailtrap.send()
for each. But if the content is identical, just multiple recipients, one call is enough.
Last but not least, deploy the function with, you’ve guessed it:
netlify deploy --prod --dir=dist
Test emails and email sending
Regardless of how you choose to send emails, testing your sending functionality is crucial if you don’t want your recipients to receive a faulty email or, even worse, not receive it at all.
For example, your emails might not get rendered by your recipient’s client properly, they can be blocked by spam filters, and your domain can be blacklisted. 🏮
For all of the above, and more, I use Mailtrap Email Testing, another inseparable part of Mailtrap Email Delivery Platform.
With Email Testing, I can capture SMTP traffic from staging and dev environments, and test my sending functionality without the risk of spamming my recipients. What’s more, I can analyze email content and validate HTML/CSS and make sure my email designs are flawless.
After previewing how my emails look on mobile, desktop, and tablet screens, I typically check my spam score. If I keep it below 5, I solve a significant number of potential email deliverability issues once my project moves to production.
I must also say that testing is super straightforward, takes only a few minutes to set up, and saves you a headache that could last quite longer! 🤕
SMTP
Netlify allows us to set up Deploy Previews to preview our functionality before we deploy our app to production so that real users can use it. This way, we have a separate environment to test emails using Testing SMTP credentials.
First, let’s set environment variables specifically for the deploy preview environment. They are the same environment variables as we’re currently using for our Netlify Functions, but they will use testing credentials accordingly.
So, create a free free Mailtrap account and then:
- Navigate to Email Testing and choose your inbox.
- Copy your credentials from the SMTP Settings tab.
- Run them with the environment variable commands:
netlify env:set MAILTRAP_USER your-testing–user --context=deploy-preview
netlify env:set MAILTRAP_PASS your-testing-pass --context=deploy-preview
Note: If it asks you to overwrite your environment variables, select Yes. It won’t actually remove your previous prod environment variables, because we specified the context with --context=deploy-preview
. This way, it will only update the deploy-preview
context values.
- Before we deploy the preview, if you check the Netlify Function you’ll notice that we still use the live SMTP
host
.
host: "live.smtp.mailtrap.io"
- To fix it, let’s modify the netlify/functions/send-email.js file accordingly. Find your transporter setup and modify it to use an environment variable for host instead of the string.
let transporter = nodemailer.createTransport({
host: process.env.MAILTRAP_HOST,
port: 587,
auth: {
user: process.env.MAILTRAP_USER,
pass: process.env.MAILTRAP_PASS,
},
});
- Now, let’s set up separate environment variables for the host for
production
anddeploy-preview
.
netlify env:set MAILTRAP_HOST your-live-host
netlify env:set MAILTRAP_HOST your-testing-host --context=deploy-preview
- Finally, deploy a preview version of our application.
netlify deploy --build --context=deploy-preview
You then can open your draft deploy preview URL in your browser.
If your emails look good and everything works correctly, you can deploy a Production version (like we previously did in the article), which will use your real SMTP credentials with the live SMTP host
.
Notes:
- Please open the Function link directly in the browser if it has no body parameter expected. Otherwise, use tools like
curl
to test your Netlify Functions withbody
parameter. - Netlify Functions are now deployed to another domain (preview domain), so the notification hooks that we previously set up don’t work anymore.
API
- Update the environment variables with the Email Testing credentials and set the
test inbox id
just like so:
netlify env:set MAILTRAP_API_TOKEN your-testing–api --context=deploy-preview
netlify env:set MAILTRAP_TEST_INBOX_ID your-test–inbox-id --context=deploy-preview
Note: You can find your text inbox id
in the URL of your Mailtrap testing inbox.
- Modify the netlify/functions/send-api-email.js file to include new properties and replace the placeholders like
MAILTRAP_API_TOKEN
.
const token = process.env.MAILTRAP_API_TOKEN;
const testInboxId = process.env.MAILTRAP_TEST_INBOX_ID;
const senderEmail = "no-reply@yourdomain.com";
const clientConfig = { token: token };
if (testInboxId) {
clientConfig.testInboxId = testInboxId;
clientConfig.sandbox = true;
}
const mailtrap = new MailtrapClient(clientConfig);
- Deploy a preview version of our application.
netlify deploy --build --context=deploy-preview
Lastly, if you open the latest Deploy Preview and submit a form via the endpoint link provided by Netlify, you should receive new emails in your Testing Inbox after running similarly.
Wrapping up
And that’s that, folks!
I’ve gone over three methods of implementing email functionality from a React app hosted on Netlify, namely:
- Netlify Forms for quick, serverless notifications.
- Nodemailer + Mailtrap SMTP in Netlify Functions for custom emails, leveraging Mailtrap’s sandbox for testing and free SMTP for actual sending.
- Mailtrap Email API in Functions for a perhaps cleaner integration and automated sending + testing.
Also, remember that you can use all of these methods at once. For example, you might even use Netlify Forms for one form and a custom function for another feature in the same site. So, see what fits your project best and choose adequately. Happy emailing! 📨
Further reading:
- How to Send Emails in ReactJS
- Nodemailer Guide with Code Snippets
- Node.js Email Validation
- How to Send Emails in Node.js