Skip to content

Next.js email guide

How to send email from Next.js

Send a transactional email from Next.js with the RouteKite REST API while keeping the API key in server-only code.

Reviewed September 6, 2026

Server only

Keep the rk_ API key outside browser components and public environment variables.

One request

Send HTML, plain text, recipient, subject, and sender to the email endpoint.

One message ID

Use the accepted message record to retrieve its current state and delivery history.

Prepare the server environment

You need a verified domain, a Sending Address, and an rk_ API key. The example uses a server-only module, so the credential never enters the client bundle.

  1. Verify your sending domain

    Add the DNS values shown by RouteKite and wait until the domain is ready.

  2. Create a Sending Address

    Use an address on the verified domain, such as orders@yourdomain.com.

  3. Create an API key

    Store it in the Next.js server environment and never prefix it with NEXT_PUBLIC_.

ROUTEKITE_API_KEY=rk_your_key

Send the email

Place this helper in server-owned application code and call it after the product event that requires the email.

import "server-only";

type OrderConfirmation = {
  recipient: string;
  orderNumber: string;
};

export async function sendOrderConfirmation({
  recipient,
  orderNumber,
}: OrderConfirmation) {
  const apiKey = process.env.ROUTEKITE_API_KEY;

  if (!apiKey) {
    throw new Error("ROUTEKITE_API_KEY is not configured");
  }

  const response = await fetch("https://routekite.com/api/v1/emails", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
      "Idempotency-Key": `order-${orderNumber}-confirmation`,
    },
    body: JSON.stringify({
      from: "orders@yourdomain.com",
      to: recipient,
      subject: `Order ${orderNumber} confirmed`,
      preview_text: "We received your order.",
      html: `<h1>Order confirmed</h1><p>Order ${orderNumber} is being prepared.</p>`,
      text: `Order ${orderNumber} is confirmed and being prepared.`,
    }),
    cache: "no-store",
  });

  const message = await response.json();

  if (!response.ok) {
    throw new Error(message.message ?? "RouteKite could not accept the email");
  }

  return message;
}

The idempotency key identifies this exact order-confirmation request. Retrying the same payload with the same key returns the original message instead of creating a second send.

Store the returned message ID

A successful request creates a RouteKite message record. Queued means the request was accepted and prepared for delivery.

{
  "id": "7dd5da7f-40b0-4c29-bb78-7e0d95ef9eb7",
  "status": "queued",
  "created_at": "2026-09-04T14:32:18.000Z"
}

Use the message ID with the retrieve-email and delivery-history endpoints. A queued or submitted state is not a delivered result, so read the later provider-reported state when delivery matters.

Before connecting a product event

Keep the sending boundary small and predictable so product retries do not create duplicate customer messages.

Protect the key
Read it only from server-side environment configuration.
Use stable idempotency
Derive the key from the business event rather than generating a new key for every retry.
Handle non-2xx responses
Log a safe internal reference and preserve the original product action.
Track the message ID
Use it to retrieve the current state and delivery history later.

Send the first message from your Next.js server.

Review the complete request contract, error model, attachments, and retrieval endpoints in Docs.