Send emails with Next.js

Install

npm install mailstein

Put the key in .env.local. It must not be NEXT_PUBLIC_ — anything with that prefix is compiled into the browser bundle, and a sending key in a browser bundle is a key anyone can use to send as you.

MAILSTEIN_API_KEY=ms_...

A Route Handler

// app/api/send/route.ts
import { Mailstein } from "mailstein";
import { NextResponse } from "next/server";

const mailstein = new Mailstein(process.env.MAILSTEIN_API_KEY);

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

  const { data, error } = await mailstein.emails.send({
    from: "hello@yourdomain.com",
    to: email,
    subject: "Welcome",
    html: `<p>Hello ${name}, thanks for signing up.</p>`,
    // Derived from the thing being emailed about, so a retry of this
    // signup does not send a second welcome. See /idempotency.
    tags: ["welcome"],
  });

  if (error) {
    // Log the detail, return something the user can act on. The message may
    // name an internal domain or an id, so it is not for the browser.
    console.error(error);
    return NextResponse.json({ error: "Could not send" }, { status: 502 });
  }

  return NextResponse.json({ id: data.emailId });
}

A Server Action

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

import { Mailstein } from "mailstein";

const mailstein = new Mailstein(process.env.MAILSTEIN_API_KEY);

export async function notify(formData: FormData) {
  const email = String(formData.get("email"));
  const { error } = await mailstein.emails.send({
    from: "hello@yourdomain.com",
    to: email,
    subject: "Thanks",
    html: "<p>We got your message.</p>",
  });
  if (error) throw new Error(error.message);
}

Do not send from the Edge runtime on a user request

A send takes a few hundred milliseconds and can fail. Doing it inline means the user waits for it, and a transient failure becomes a failed signup.

Return the response first and send afterwards — a queue, a background job, or after() from next/server:

import { after } from "next/server";

export async function POST(request: Request) {
  const { email } = await request.json();
  after(async () => {
    await mailstein.emails.send({ /* ... */ });
  });
  return NextResponse.json({ ok: true });
}

React Email

If you write your templates as React components, render them to HTML and pass that:

import { render } from "@react-email/render";
import WelcomeEmail from "@/emails/welcome";

const html = await render(<WelcomeEmail name={name} />);
await mailstein.emails.send({ from, to, subject: "Welcome", html });
Something here wrong or missing? It is generated from the running API — tell us and we will fix the source.