# Send email with Next.js

> Send email from Next.js with Email Fast: a server action or route handler using @email-fast/nodejs, with idempotency — nothing secret ever reaches the client.

Canonical: https://emailfast.dev/send-email-with/nextjs

In Next.js, email is a server-side job. If the send starts from your own form, use a
server action; if it starts from anything else — a fetch call, a webhook, a cron route —
use a route handler. Both run `@email-fast/nodejs` on the server, so the API key never
enters the client bundle. This page is the short version; the
[full Next.js quickstart](/docs/quickstart-nextjs) walks both patterns end to end.

```bash
npm install @email-fast/nodejs
```

```ts
// app/actions/email.ts
"use server";
import { EmailFast } from "@email-fast/nodejs";

const ef = new EmailFast({ apiKey: process.env.EMAILFAST_API_KEY!, baseUrl: "https://api.emailfast.dev" }); // ef_sandbox_… in dev, ef_live_… at launch

export async function sendReceipt(formData: FormData) {
  const email = String(formData.get("email"));
  const orderId = String(formData.get("orderId"));

  try {
    const { id } = await ef.send({
      to: email,
      subject: `Your receipt for order ${orderId}`,
      html: "<p>Order {{order}} is confirmed.</p>",
      data: { order: orderId },
      idempotency_key: `receipt-${orderId}`, // a double-submit replays, never re-sends
    });
    return { ok: true, id };
  } catch (err) {
    console.error("send failed:", err);
    return { ok: false };
  }
}
```

```tsx
// app/order/page.tsx
import { sendReceipt } from "../actions/email";

export default function Page() {
  return (
    <form action={sendReceipt}>
      <input name="email" type="email" required />
      <input name="orderId" required />
      <button>Email my receipt</button>
    </form>
  );
}
```

a 202 from the API means the send is committed to a durable, partitioned outbox before we answer — a crash can't lose it, and a retry with the same idempotency key can't double-send.

## Gotchas

- Server action vs route handler: actions are for sends your own UI triggers. Use a route handler (`app/api/…/route.ts`) when the trigger is external — a webhook, a cron ping, another service.
- Never prefix the key with `NEXT_PUBLIC_` — that inlines it into the client bundle. Keep it as `EMAILFAST_API_KEY` in `.env.local`.
- The Node SDK needs the Node runtime. If a route opts into the Edge runtime, set `export const runtime = "nodejs"` or call the REST endpoint with `fetch` instead.
- Derive the idempotency key from your domain (order id, user id), not `Math.random()` — a random key makes every retry a brand-new send.

## Next

- [Full Next.js quickstart](/docs/quickstart-nextjs) — route handlers, env setup, the frontend-only option
- [The email API](/features/email-api) — batch, scheduling, message timelines
- [All docs](/docs)
