Next.js Quickstart

Wire Mailstorm into a Next.js 14+ project. Server Actions for the modern App Router pattern, and a Route Handler for client-driven flows. Works on any deploy target — Vercel, self-hosted, Cloudflare Pages, etc.

Server-only: Mailstorm is called server-side. Don't import the SDK in a Client Component or expose the API key in client bundles. Use Server Actions, Route Handlers, or API routes (pages router).

1. Install

terminal
npm install @mailstorm/resend

2. Environment

.env.local
MAILSTORM_API_KEY=ms_test_...
# or ms_live_... in production

3. Server Action (App Router)

Recommended pattern for forms in Next.js 14+. The function runs only on the server; the API key never leaves it.

app/actions/welcome.ts
"use server";
import { Resend } from "@mailstorm/resend";

const resend = new Resend(process.env.MAILSTORM_API_KEY!);

export async function sendWelcome(formData: FormData) {
  const email = formData.get("email") as string;
  const { data, error } = await resend.emails.send({
    from: "hi@yourdomain.com",
    to: email,
    subject: "Welcome",
    html: "<p>Thanks for signing up.</p>",
  });
  if (error) return { ok: false, error: error.message };
  return { ok: true, id: data?.id };
}
app/signup/page.tsx
import { sendWelcome } from "../actions/welcome";

export default function SignupPage() {
  return (
    <form action={sendWelcome}>
      <input name="email" type="email" required />
      <button>Sign up</button>
    </form>
  );
}

4. Route Handler (App Router)

Use this when a client component or third-party calls back into your API.

app/api/send/route.ts
import { Resend } from "@mailstorm/resend";
import { NextResponse } from "next/server";

const resend = new Resend(process.env.MAILSTORM_API_KEY!);

export async function POST(req: Request) {
  const body = await req.json();
  const { data, error } = await resend.emails.send({
    from: "alerts@yourdomain.com",
    to: body.email,
    subject: body.subject,
    html: body.html,
  });
  if (error) return NextResponse.json({ error }, { status: 500 });
  return NextResponse.json({ id: data?.id });
}

5. Webhooks

Listen for delivered, bounced, opened, clicked, complained, and inbound events. Configure the URL in your Mailstorm dashboard at /webhooks.

app/api/mailstorm-webhook/route.ts
import { NextResponse } from "next/server";
import { createHmac, timingSafeEqual } from "node:crypto";

export async function POST(req: Request) {
  const sig = req.headers.get("X-Mailstorm-Signature");
  const raw = await req.text();

  const expected = createHmac("sha256", process.env.WEBHOOK_SECRET!)
    .update(raw)
    .digest("hex");
  if (!sig || !timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return new Response("bad signature", { status: 401 });
  }

  const event = JSON.parse(raw);
  // switch event.type and handle
  return NextResponse.json({ ok: true });
}

6. Vercel deploy notes

7. React Email (templates)

Use the open-source React Email library to write emails as React components. It's framework-agnostic and pairs cleanly with Mailstorm:

terminal
npm install @react-email/components @react-email/render
emails/welcome.tsx
import { Html, Heading, Text } from "@react-email/components";

export default function Welcome({ name }: { name: string }) {
  return (
    <Html>
      <Heading>Hi {name}</Heading>
      <Text>Thanks for signing up.</Text>
    </Html>
  );
}
app/actions/welcome.ts
"use server";
import { render } from "@react-email/render";
import { Resend } from "@mailstorm/resend";
import Welcome from "@/emails/welcome";

const resend = new Resend(process.env.MAILSTORM_API_KEY!);

export async function sendWelcome(name: string, email: string) {
  const html = await render(<Welcome name={name} />);
  return resend.emails.send({
    from: "hi@yourdomain.com", to: email,
    subject: "Welcome", html,
  });
}

What's next