Skip to main content

Next.js guide

How to send email from Next.js

Send invites, receipts and password resets from an App Router server action or route handler. One SDK call, your own domain, and 3,000 free emails a month.

Setup

Set up email in your Next.js app.

Do this once. After that, sending is one function call from any server code.

  1. Step 1: Verify your sending domain

    Add your domain in the Sendaro dashboard and publish its DKIM, SPF and DMARC records. Mail from a verified domain lands in the inbox instead of spam. See domain setup.

  2. Step 2: Install the SDK and store your key

    Create an API key with send access and add it to your server environment. Without the NEXT_PUBLIC_ prefix, Next.js keeps it out of the browser.

    Terminal
    npm install sendaro
    .env.local
    # .env.local (never prefix it with NEXT_PUBLIC_)
    SENDARO_API_KEY=rf_your_api_key
  3. Step 3: Create one shared client

    One module that only server code can import. Every action and route uses it.

    lib/email.ts
    // lib/email.ts
    import "server-only";
    import { Sendaro } from "sendaro";
    
    export const sendaro = new Sendaro(); // reads SENDARO_API_KEY

Server action

Send email from a server action.

A form posts straight to a function on the server. No API route, no client-side fetch.

app/invite/actions.ts
// app/invite/actions.ts
"use server";

import { sendaro } from "@/lib/email";
import { requireUser } from "@/lib/auth"; // your own auth check

export async function sendInvite(formData: FormData) {
  const user = await requireUser();
  const email = String(formData.get("email"));

  await sendaro.send(
    {
      from: "Acme <team@yourdomain.com>",
      to: email,
      subject: `${user.name} invited you to Acme`,
      html: "<p>Accept your invite to join the team.</p>",
      text: "Accept your invite to join the team.",
    },
    { idempotencyKey: `invite-${user.id}-${email}` },
  );
}
app/invite/page.tsx
// app/invite/page.tsx
import { sendInvite } from "./actions";

export default function InvitePage() {
  return (
    <form action={sendInvite}>
      <input name="email" type="email" required />
      <button type="submit">Send invite</button>
    </form>
  );
}

Server actions can be called by anyone who can load the page, so check the user first. The idempotencyKey means a double click or a retry within 24 hours returns the first email instead of sending a second.

Route handler

Send email from a route handler.

For emails triggered from outside the page: payment webhooks, cron jobs, other services.

app/api/receipts/route.ts
// app/api/receipts/route.ts
import { sendaro } from "@/lib/email";

export async function POST(request: Request) {
  // Check who is calling before you send anything.
  const { orderId, email } = await request.json();

  const { id } = await sendaro.send(
    {
      from: "Acme <receipts@yourdomain.com>",
      to: email,
      subject: `Receipt for order ${orderId}`,
      html: `<p>Thanks for order ${orderId}.</p>`,
    },
    { idempotencyKey: `receipt-${orderId}` },
  );

  return Response.json({ id });
}

Never expose a route that sends mail to any address a stranger posts: it becomes a spam relay on your domain. Authenticate the caller, or only send to addresses you already have on file.

Webhooks

Know when an email is delivered or bounced.

Sendaro posts signed events like email.delivered and email.bounced to your app. Verify the signature before you trust the body.

app/api/sendaro/route.ts
// app/api/sendaro/route.ts
import { verifyWebhook } from "sendaro";

export async function POST(request: Request) {
  // Use the raw body. Parsing and re-serialising JSON breaks the signature.
  const payload = await request.text();

  const valid = verifyWebhook(
    payload,
    request.headers.get("rf-signature"),
    process.env.SENDARO_WEBHOOK_SECRET!,
    request.headers.get("rf-timestamp"),
  );
  if (!valid) return new Response("Invalid signature", { status: 401 });

  const event = JSON.parse(payload);
  if (event.type === "email.bounced") {
    // Flag the address in your own database.
  }
  return new Response(null, { status: 204 });
}

FAQ

Next.js email questions.

Use a server action when the email comes from a form in your own app, such as an invite or a contact form. Use a route handler when something outside the page triggers it: a payment webhook, a cron job or another service.

Yes. The SDK makes an HTTPS request, so it works anywhere your Next.js server code runs in the Node.js runtime, including serverless functions. There is no SMTP port to open.

It makes the build fail if a client component ever imports your email client, so your API key can't end up in the browser bundle. Install it with npm install server-only.

Sendaro takes the HTML and text you pass. You can render that HTML any way you like, including from React components on the server, as long as the result is a string.

The Free plan includes 3,000 emails a month and needs no card. Paid plans add volume; every plan has the full API, webhooks and logs.

Send your first email today.

Free includes 3,000 emails every month. No card needed.