Learn how to send transactional email from Next.js with AWS SES and Netlify Functions, including domain verification, SMTP setup, contact forms, security, and deployment.
A practical way to send transactional email from a Next.js application hosted on Netlify is to use Amazon SES for email delivery and a Netlify Function as the server-side sending layer. This keeps your email credentials away from the browser and gives you a simple path for contact-form notifications, transactional messages, and other application emails.
This guide walks through the setup from domain verification to deployment, explains the important security decisions, and shows a working implementation using Nodemailer and AWS SES SMTP.
How the Setup Works
The basic architecture is:
Next.js form
↓
Netlify Function
↓
AWS SES
↓
Recipient's inbox
The browser collects the form data, but the actual email request is handled server-side.
That separation matters because SMTP usernames, passwords and other email credentials should never be exposed in client-side JavaScript.
You can also use the same pattern for:
- Contact-form notifications
- Customer confirmations
- Transactional messages
- Download confirmations
- Application alerts
- PDF or resource delivery
The exact architecture can change depending on your hosting provider and application requirements, but the principle is the same: keep email credentials and sending logic on the server side.
Why Use AWS SES with Netlify Functions?
SES can be a good fit when you want a transactional email service with usage-based pricing and direct control over your sending setup.
Netlify Functions provide the server-side execution layer without requiring you to run a separate application server just to process a contact form.
This combination can work well when:
- Your Next.js application is already deployed on Netlify.
- You need transactional email rather than a full marketing platform.
- You want to manage the sending identity yourself.
- You are comfortable working with AWS and DNS configuration.
- You want the email logic to remain close to your application code.
It is not the only valid choice. Services such as Postmark, Resend, SendGrid and other providers may be more appropriate depending on your team's priorities, existing infrastructure and email requirements.
The goal is to choose the simplest reliable setup for the application rather than assuming one provider is best for every project.
Part 1: Set Up AWS SES
1. Choose an SES Region
SES is region-specific, so choose a region that fits your deployment, operational and compliance requirements.
Once you choose the region, keep your verified identities and sending configuration consistent with that region.
2. Verify Your Domain
In the AWS console, open Amazon SES and create a verified identity for your sending domain.
AWS will provide DNS records for domain verification and DKIM.
Add the required records to the DNS provider that manages your domain.
For example:
yourdomain.com
hello@yourdomain.com
A verified domain is preferable to relying on a single verified address because it gives you a consistent sending identity for the application.
3. Configure Email Authentication
Follow the DNS instructions provided by SES for your identity.
In addition to DKIM, make sure your overall domain authentication strategy is configured correctly for your email setup. SPF and, where appropriate, DMARC are important parts of a healthy sending configuration.
Do not assume that verifying an SES identity alone guarantees inbox placement. Authentication is one part of sender reputation and deliverability.
4. Request Production Access
New SES accounts begin in sandbox mode.
While in sandbox, sending is restricted. To use SES for real production recipients, request production access and describe your actual use case honestly.
For a contact form, your explanation might mention:
- Contact-form notifications
- Transactional confirmations
- User-initiated requests
- Expected sending volume
- How you handle bounces and complaints
AWS assigns sending limits based on your account and use case, so do not hard-code an assumed production quota into your application design.
For current limits and pricing, check the official AWS documentation before estimating capacity or cost:
Part 2: Configure Netlify
Project Structure
A simple project can use a Netlify Functions directory such as:
your-nextjs-app/
├── app/
├── public/
├── netlify/
│ └── functions/
│ └── contactMail.js
├── netlify.toml
└── package.json
The exact structure can differ depending on your Next.js and Netlify setup. Follow the current Netlify Functions conventions for your project.
Environment Variables
Store credentials in your Netlify environment configuration, not in source control.
For example:
SES_SMTP_USER=your_smtp_username
SES_SMTP_PASS=your_smtp_password
INTERNAL_RECEIVER_EMAIL=hello@yourdomain.com
For local development, use a local environment file that is excluded from Git.
Never commit SMTP credentials to your repository.
Install Nodemailer
If you are using SES SMTP with Nodemailer:
npm install nodemailer
Nodemailer handles the SMTP connection and message construction while SES handles the delivery infrastructure.
Part 3: Build the Server-Side Function
A simplified contact function can look like this:
const nodemailer = require("nodemailer");
exports.handler = async function (event) {
if (event.httpMethod !== "POST") {
return {
statusCode: 405,
body: JSON.stringify({ error: "Method not allowed" }),
};
}
try {
const { name, email, phone, message } = JSON.parse(event.body || "{}");
if (!name || !email || !message) {
return {
statusCode: 400,
body: JSON.stringify({ error: "Missing required fields" }),
};
}
const transporter = nodemailer.createTransport({
host: process.env.SES_SMTP_HOST,
port: Number(process.env.SES_SMTP_PORT || 465),
secure: true,
auth: {
user: process.env.SES_SMTP_USER,
pass: process.env.SES_SMTP_PASS,
},
});
await transporter.sendMail({
from: '"Your Brand" <hello@yourdomain.com>',
to: process.env.INTERNAL_RECEIVER_EMAIL,
replyTo: email,
subject: `New website enquiry from ${name}`,
text: `${message}\n\nPhone: ${phone || "Not provided"}`,
});
return {
statusCode: 200,
body: JSON.stringify({ success: true }),
};
} catch (error) {
console.error("Contact mail error:", error);
return {
statusCode: 500,
body: JSON.stringify({ error: "Unable to send message" }),
};
}
};
This example is intentionally simple.
For production, you should also consider:
- Stronger server-side validation
- Request size limits
- Rate limiting
- Bot protection
- Input sanitization
- Structured error logging
- Retries or queueing for important workloads
- Monitoring and alerting
Why replyTo matters
Keep the from address on your verified domain and use the visitor's address as replyTo.
That way, the message is sent from an identity your mail system controls while your team can still reply directly to the person who submitted the form.
Part 4: Connect the Next.js Form
Your client component can call the server-side function:
"use client";
const handleSubmit = async (event) => {
event.preventDefault();
const response = await fetch("/.netlify/functions/contactMail", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name,
email,
phone,
message,
}),
});
const data = await response.json();
if (data.success) {
// Show success state
} else {
// Show error state
}
};
The important point is that the browser does not receive the SES credentials. It only calls your server-side function.
Part 5: Protect the Contact Endpoint
A public contact form is an easy target for automated spam.
Email delivery should therefore not be the only consideration.
At minimum:
Validate on the server
Never rely only on browser validation.
Check:
- Required fields
- Email format
- Message length
- Request size
- Unexpected input
Add rate limiting
A single IP or client should not be able to submit hundreds of messages in a short period.
Add bot protection where necessary
Depending on the volume of spam, you may use CAPTCHA, a managed challenge, honeypot fields, behavioral checks, or another appropriate control.
Avoid reflecting unsanitized input
Do not place submitted names or message content directly into HTML email without escaping or sanitizing it correctly.
This matters both for security and for reliable email rendering.
Part 6: Email Templates
Email HTML is different from normal website HTML.
Email clients have varying levels of support for modern CSS, so conservative layouts and inline styles are still common when consistent rendering matters.
For important transactional emails:
- Keep the layout simple.
- Use clear typography.
- Use responsive widths.
- Include a useful preheader.
- Test links and images.
- Avoid depending on advanced CSS features.
- Test across the clients your customers actually use.
For example, a simple branded email can use a centered table layout with inline styles:
<table
role="presentation"
width="100%"
cellpadding="0"
cellspacing="0"
border="0"
>
<tr>
<td align="center" style="padding:24px;background:#f3f4f6;">
<table
role="presentation"
width="100%"
cellpadding="0"
cellspacing="0"
border="0"
style="max-width:520px;background:#ffffff;"
>
<tr>
<td style="padding:32px;font-family:Arial,sans-serif;color:#222;">
<h1 style="margin:0 0 16px;font-size:24px;">
Thanks for getting in touch
</h1>
<p style="margin:0;line-height:1.6;">
We received your message and will get back to you soon.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
What about dark mode?
Email clients can apply their own dark-mode transformations.
There is no single CSS trick that guarantees identical rendering everywhere, so test the actual emails in the clients that matter to your audience.
That is more reliable than assuming a specific dark-mode override will behave identically across every client.
Part 7: Attachments and PDFs
SES can send messages with attachments through the SMTP/Node.js layer.
For example:
attachments: [
{
filename: "guide.pdf",
path: "/tmp/guide.pdf",
},
]
But large attachments can increase message size and may be restricted by some mail systems.
For larger resources, a secure download link is often a better experience than attaching the entire file.
Part 8: Test Before Production
Before launching, verify:
- SES domain identity is verified
- DKIM is configured
- Production access is approved if required
- SMTP credentials are stored securely
- Environment variables exist in the production environment
- The form works from the deployed website
- Internal notification emails arrive
- Reply-to behavior works
- Error states are handled
- Spam protection is enabled
- Emails render correctly on the clients you care about
Also test failure cases.
For example:
What happens if SES is unavailable?
What happens if the visitor submits a 5 MB message?
What happens if the same IP submits 100 requests?
A production email flow needs to handle failure gracefully rather than exposing implementation errors to the user.
Common Problems
| Problem | Likely cause | What to check |
|---|---|---|
535 authentication error | Incorrect SMTP credentials | Recreate or recheck the SES SMTP credentials |
| Recipient cannot receive email | SES sandbox or account restriction | Check SES account status and sending permissions |
| Connection timeout | Wrong SMTP host/port or network issue | Verify the SES endpoint for your region |
429 or throttling | Sending rate exceeded | Check your SES quota and add queueing/backoff if needed |
| Missing form fields | Weak request validation | Validate the payload server-side |
| Emails look broken | Client-specific HTML/CSS behavior | Simplify the template and test actual clients |
| Contact form is full of spam | No abuse protection | Add rate limiting and bot controls |
What Does This Stack Cost?
Do not rely on a fixed monthly number for an AWS/Netlify email stack.
AWS SES pricing depends on your current SES pricing plan and usage, while Netlify costs depend on the site plan and function usage.
For current pricing, check:
The important question is not simply “Which email provider is cheapest?”
Look at:
- Sending volume
- Delivery requirements
- Provider features
- Operational overhead
- Existing infrastructure
- Monitoring and support
- Compliance requirements
How CorgenX Uses This Kind of Architecture
We have used Next.js, server-side email handling and transactional messaging patterns in production projects for contact forms, notifications and resource delivery.
The exact implementation varies by project. We choose the email provider and application architecture based on the project's volume, hosting environment, security requirements, and operational needs rather than forcing every project onto the same stack.
That distinction matters because AWS SES + Netlify is one workable option, not a universal rule for every Next.js application.
Explore Web Development Services
Explore Custom Web Applications
FAQs
Can I send email from Next.js without exposing SMTP credentials?
Yes. The email-sending logic should run on the server side, such as a Netlify Function or another backend service. The browser should never receive your SMTP credentials.
Is AWS SES a good choice for a Next.js application?
It can be a good choice when you want a transactional email service with direct control over the sending setup and are comfortable managing AWS configuration. Other providers may be simpler depending on the project.
Can I use AWS SES on Netlify?
Yes. A Netlify Function can handle the server-side request and send email through SES, provided the SES account and credentials are configured correctly.
Do I need Nodemailer?
No. Nodemailer is one way to send email through SMTP from Node.js. You can also use the AWS SDK or another supported approach depending on your architecture.
Why are my emails going to spam?
Authentication is only one part of deliverability. Sender reputation, message content, recipient engagement, bounce and complaint rates, domain configuration, and mailbox-provider policies can all matter.
Should I use SMTP or the AWS SDK?
Both can work. SMTP through Nodemailer can be convenient when your application already uses Nodemailer-style message construction. The AWS SDK can be preferable when you want to interact with SES directly through AWS APIs.
Can I send PDFs or other attachments?
Yes, but keep attachment size and recipient-mailbox restrictions in mind. For larger resources, a secure download link is often better.
Is this setup suitable for every Next.js project?
No. The right approach depends on the application's hosting, email volume, security needs, operational requirements, and existing infrastructure.