Guide · updated July 30, 2026

How to send email from Next.js

Password resets, receipts, magic links — almost every app eventually needs to send email, and Next.js doesn't ship a mailer. The good news: with the App Router it takes one route handler and one environment variable. This guide shows three ways to do it — plain fetch, the Resend SDK, and SMTP — plus the domain setup that keeps you out of spam and a way to test without emailing real people.

What you need

One rule before any code: never send email from the client. Your API key must live server-side — a route handler, server action, or server component — or anyone reading your bundle can send email as you.

Step 1: Get an API key

Create a key in your provider's dashboard and put it in .env.local:

# .env.local — never commit this file
SENDIBL_API_KEY=sb_...

Environment variables without the NEXT_PUBLIC_ prefix stay on the server in Next.js, which is exactly what you want here.

Step 2: Send with a route handler

The zero-dependency version — a route handler that POSTs to the email API with fetch:

// app/api/welcome/route.ts
import { NextResponse } from "next/server";

export async function POST(request: Request) {
  const { email, name } = await request.json();

  const res = await fetch("https://api.sendibl.com/emails", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SENDIBL_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      from: "Acme <hello@yourdomain.com>",
      to: [email],
      subject: "Welcome to Acme",
      html: `<p>Thanks for signing up, ${name}!</p>`,
    }),
  });

  if (!res.ok) {
    const error = await res.json();
    return NextResponse.json({ error }, { status: 502 });
  }

  const { id } = await res.json();
  return NextResponse.json({ id });
}

That's a complete production sender. Call it from a form action or another server function — not directly from untrusted client input without validation, or you've built an open relay for spammers.

Alternative: use the Resend SDK

Prefer an SDK? Because Sendibl's API is shape-compatible with Resend's, the official resend package works against either provider — pointed at Sendibl it just needs a base URL:

npm install resend
// lib/email.ts
import { Resend } from "resend";

export const email = new Resend(process.env.SENDIBL_API_KEY, {
  baseUrl: "https://api.sendibl.com",
});

// anywhere server-side:
await email.emails.send({
  from: "Acme <hello@yourdomain.com>",
  to: ["customer@example.com"],
  subject: "Your receipt",
  html: "<p>Thanks for your order!</p>",
});

This is also the cheapest migration path in either direction — swap the key and base URL and the rest of your code doesn't know anything changed. SDK details here.

Alternative: SMTP with Nodemailer

Some codebases already speak SMTP, and some tools only speak SMTP. Nodemailer against an SMTP relay works fine from a route handler:

npm install nodemailer
// lib/smtp.ts
import nodemailer from "nodemailer";

export const transporter = nodemailer.createTransport({
  host: "smtp.sendibl.com",
  port: 23990,
  secure: false, // STARTTLS upgrade — required before auth
  auth: {
    user: "sendibl",
    pass: process.env.SENDIBL_API_KEY,
  },
});

Sends through the relay show up in the same logs and fire the same webhooks as API sends. For a new Next.js app, though, prefer the HTTP API — serverless platforms are friendlier to short HTTP requests than to SMTP connections. SMTP relay docs.

Step 3: Verify your domain

Sending from an unverified domain is how email ends up in spam. Add your domain in the dashboard, publish the DKIM and SPF records it gives you (one click if your DNS is on Cloudflare), and send from an address on that domain. Verification usually completes in minutes; domain setup docs.

Step 4: Test without emailing real people

Use test addresses instead of your own inbox: Sendibl's delivered@test.sendibl.com and bounced@test.sendibl.comsimulate delivery and bounces end to end — events, webhooks, logs — without a single real recipient, and they work before you've verified a domain. Test mode docs.

Where to go from here