Skip to content
Back to Lumail Notes
11 min readtransactionalemail

How to Send Email from Next.js: SMTP, APIs, and Automation

Learn how to send email from Next.js with secure server-side code, SMTP or an email API, templates, automation, and deliverability best practices.

How to Send Email from Next.js: SMTP, APIs, and Automation
In this guide

To send email from Next.js, add server-only sending logic inside an API Route or Server Action, then connect it to either an SMTP server, Gmail, or a transactional email API. For production apps, an email API is usually the most reliable path because it handles authentication, deliverability, and templates without you managing a mail server yourself.

Developers who want to send email from Next.js usually run into the same question early: should the app talk directly to an SMTP server, or should it call an email API instead? Both approaches work, but they come with different tradeoffs around deliverability, maintenance, and how much infrastructure you have to manage yourself. This guide walks through what you need before you write any code, how to wire up sending inside the App Router, and how to decide between SMTP, Gmail, and a dedicated email API as your Next.js app grows past a handful of transactional messages.

What You Need Before Sending Email from Next.js

Before you write a single line of sending code, a few prerequisites determine whether your emails actually reach an inbox instead of a spam folder. Next.js itself has no built-in mail client: it runs your JavaScript or TypeScript on the server, but the actual delivery always happens through either an SMTP connection or an HTTP call to a mail provider.

  • A verified sending domain. Authenticate it with SPF, DKIM, and DMARC records so receiving mail servers trust messages coming from your app rather than flagging them as spoofed.
  • Server-only credentials. API keys or SMTP passwords must live in environment variables (.env.local or your hosting provider's secret manager), never in client components or anything bundled for the browser.
  • A sending method. Either SMTP credentials from a mail server, a Gmail account with an app password for low-volume testing, or an API key from a transactional email provider.
  • A place to trigger sending. In the App Router, that is a Route Handler (app/api/.../route.ts) or a Server Action marked with "use server". Both run exclusively on the server, which keeps secrets out of the client bundle.
  • A plan for templates. Even a simple password reset email benefits from a reusable template rather than string concatenation scattered across your codebase.

If you want a broader reference for structuring sending logic across different frameworks and runtimes, you can look at a guide hub that covers how to send email from your code in more detail, including patterns beyond Next.js specifically.

Send Email from a Next.js API Route or Server Action

The App Router gives you two practical places to put sending logic: a Route Handler that responds to an HTTP request, or a Server Action called directly from a form or client component. Both keep your mail credentials server-side, which is the single most important security rule when you send email from Next.js.

Using a Route Handler

A Route Handler is useful when a third-party service (like a webhook) or your own frontend needs to trigger an email over HTTP. Here is a minimal example using SMTP with Nodemailer:

// app/api/send-email/route.ts
import { NextResponse } from "next/server";
import nodemailer from "nodemailer";

const transporter = nodemailer.createTransport({
  host: process.env.SMTP_HOST,
  port: Number(process.env.SMTP_PORT),
  secure: true,
  auth: {
    user: process.env.SMTP_USER,
    pass: process.env.SMTP_PASS,
  },
});

export async function POST(request: Request) {
  const { to, subject, html } = await request.json();

  await transporter.sendMail({
    from: '"Your App" <[email protected]>',
    to,
    subject,
    html,
  });

  return NextResponse.json({ status: "sent" });
}

This pattern works whether you call it from a client-side fetch, a form submission, or an internal cron job. The key detail is that SMTP_HOST, SMTP_USER, and SMTP_PASS only exist on the server runtime, never in a file imported by a client component.

Using a Server Action

Server Actions remove the need for a separate API route entirely when the trigger is a form submission, such as a signup or contact form. Mark the file with "use server" and call your mail client directly:

// app/actions/send-welcome.ts
"use server";

export async function sendWelcomeEmail(formData: FormData) {
  const email = formData.get("email") as string;

  await fetch("https://api.yourprovider.com/v1/emails", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.EMAIL_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      to: email,
      subject: "Welcome aboard",
      html: "<p>Thanks for signing up.</p>",
    }),
  });
}

Both approaches handle the same core problem: validating input, calling the mail provider server-side, and returning a response the UI can react to. For application-triggered messages like this, a dedicated transactional email API removes the need to manage SMTP infrastructure or retry logic yourself.

Error handling and retries

Whichever method you choose, wrap the sending call in a try/catch block and log failures with enough context (recipient, template name, timestamp) to debug later. Transient failures (timeouts, rate limits) are common with any mail provider, so a basic retry with backoff is worth adding once you move past a prototype.

Choose Between SMTP, Gmail, and an Email API

Once sending works in principle, the real decision is which transport to rely on long-term. Each option fits a different stage of a project.

MethodBest forSetup effortDeliverability controlScales to production
Gmail (Nodemailer + app password)Local testing, personal projectsLowLimited, shared Gmail reputationNo, daily sending limits apply
Self-managed SMTP serverTeams that already run mail infrastructureHighFull control, but you own SPF/DKIM/DMARC maintenanceYes, with ongoing ops work
Transactional email APISaaS apps, info products, production transactional mailLow to mediumManaged by the provider, with webhooks for statusYes, built for volume and monitoring

Gmail with Nodemailer is the fastest way to confirm your Next.js code works, since it only needs an app password and no domain setup. It is not meant for production: Gmail enforces strict daily sending caps and does not give you delivery tracking.

A self-managed SMTP server gives you full control over headers and routing, but you are responsible for maintaining your own IP reputation, authentication records, and bounce handling. This fits teams that already operate mail infrastructure for other reasons.

An email API is usually the better default once an app sends anything a user depends on, like password resets or order confirmations. It replaces raw SMTP calls with an HTTP request, often includes webhook events for delivered, opened, or bounced status, and keeps authentication and infrastructure maintenance on the provider's side rather than yours.

For developers who want a typed client instead of raw HTTP calls, documentation like the Lumail emails SDK shows how a TypeScript SDK can wrap sending, templates, and status checks into a few function calls from inside a Route Handler or Server Action.

Add Templates, Campaigns, and Automated Workflows

Transactional sending (one email triggered by one user action) is only part of the picture. Many SaaS founders, developers, and info-product creators eventually need two more things: reusable templates and recurring sends that do not require a developer to trigger each one manually.

Templates

Hardcoding HTML strings inside a Route Handler works for a prototype, but it becomes unmanageable once you have more than two or three email types. Options include:

  • React-based email templates rendered to HTML on the server before sending, which lets you reuse your existing component patterns.
  • Provider-hosted templates referenced by name or ID, where you pass dynamic data (first name, order number) and the provider renders the final HTML.
  • Markdown-to-HTML templates for simpler transactional messages like notifications or digests.

Whichever format you pick, keep template content separate from sending logic so a copy change does not require touching your API route code.

Campaigns versus transactional email

A transactional email responds to a specific user action: a signup confirmation, a password reset, an invoice. A campaign email is sent deliberately to a list of subscribers, often on a schedule or in response to a marketing trigger rather than a single account event. Mixing the two in the same sending flow is a common mistake: campaigns need subscriber lists, tags, and unsubscribe handling that transactional messages do not.

Automated workflows

Beyond one-off sends, many teams eventually want sequences: a welcome series triggered after signup, a reminder a few days after a trial starts, or a re-engagement email based on inactivity. Building this from scratch inside Next.js means tracking subscriber state, scheduling delayed sends, and avoiding duplicate triggers, which quickly becomes its own small application. This is typically where a platform with built-in workflows and tagging (rather than custom cron logic) saves meaningful engineering time, since the trigger conditions, delays, and audience segmentation are handled outside your application code.

A simple checklist before going to production

  • Domain authentication (SPF, DKIM, DMARC) is verified, not just configured.
  • Transactional and campaign sending use separate logic, lists, or providers as appropriate.
  • Templates live outside the Route Handler or Server Action, not inline as string literals.
  • Failed sends are logged with enough detail to retry or alert on.
  • API keys and SMTP credentials are stored only in server-side environment variables.
  • Unsubscribe handling is in place for any email that is not strictly transactional.

Getting a single email to send from a Next.js route is straightforward. The harder, more durable work is choosing a transport that fits your volume, separating transactional logic from marketing campaigns, and keeping templates maintainable as the number of email types grows. Starting with the checklist above before writing production code will save you from rebuilding sending logic later.

Frequently asked questions

Can I send email from Next.js without exposing my API key?

Yes. Keep the email provider API key or SMTP password in server-only environment variables, and call the provider from a Server Action, Route Handler, or other trusted server-side module. Do not place secrets in client components or variables prefixed with NEXT_PUBLIC_. Validate submitted data on the server, restrict the endpoint to the intended operation, and avoid returning provider credentials or detailed internal errors to the browser.

Which Next.js runtime should I use for sending email?

Use the Node.js runtime when your SMTP library or email SDK depends on Node.js features such as TCP connections, sockets, or built-in modules. An email API that supports standard HTTP requests may work in the Edge runtime, but you should confirm compatibility with both the SDK and provider. Set the runtime explicitly when necessary, and test the deployed function because local development can hide runtime differences.

How should I validate a Next.js email form before sending?

Validate the recipient address, required fields, subject, message length, and any user-controlled template variables on the server, even if the browser already performs validation. Normalize input where appropriate, reject unexpected fields, and encode content safely before inserting it into HTML templates. Add rate limiting, bot protection, and authorization for public forms. These controls reduce abuse, accidental sends, header injection risks, and unwanted provider usage.

How do I handle email failures in a Next.js application?

Treat email delivery as a potentially failing external operation. Catch provider or SMTP errors, return a safe user-facing response, and log structured details such as the operation, recipient identifier, request ID, and provider error code without logging secrets or message content unnecessarily. For important messages, use retries with limits and idempotency protection so a repeated request does not create duplicate emails. Consider a queue for work that should not block the response.

How do I send emails for free during Next.js development?

Use a local SMTP capture tool, a mock email adapter, or a provider's development and sandbox features when available. These options let you inspect messages without sending real mail to customers. Keep development credentials separate from production credentials, use test recipients, and make the mailer configurable through environment variables. Check the provider's current terms for any limits or feature restrictions instead of assuming that a development option is permanently free.

How do I prevent emails sent from Next.js from landing in spam?

Send from a verified domain and configure the authentication records required by your provider, commonly SPF, DKIM, and DMARC. Use a consistent, recognizable From address, accurate subjects, valid unsubscribe handling for marketing messages, and clean recipient lists. Avoid sending unsolicited mail or sudden high-volume traffic. Monitor bounces, complaints, and provider feedback, then suppress invalid or unsubscribed addresses. Authentication improves trust, but it cannot guarantee inbox placement.

Can Next.js send automated marketing emails safely?

Yes, but automation should run only for recipients who have given the required consent and should respect unsubscribe requests, suppression lists, and applicable email rules. Keep campaign scheduling and retries outside the request that collects a signup, using a queue, scheduled worker, or provider workflow when appropriate. Separate transactional messages from marketing messages, record consent and template versions, and make every automated workflow idempotent to avoid duplicate sends.