Skip to content

Send emails with Next.js

Learn how to send transactional emails using PostStack and Next.js.

The PostStack TypeScript SDK is a first-class fit for Next.js — fully typed, tree-shakeable, and isomorphic for the bits that need to run server-side. Always call `poststack.emails.send(...)` from a Route Handler, Server Action, or Server Component, never from a Client Component: shipping your API key to the browser would expose your account to anyone with devtools. The SDK uses the platform `fetch`, so it runs on Node.js, the Edge Runtime, and Bun-based deploys without changes.

1. Install the SDK

bash
npm install @poststack.dev/sdk

2. Initialize the client

typescript
import { PostStack } from '@poststack.dev/sdk';

const poststack = new PostStack(process.env.POSTSTACK_API_KEY!);

3. Send an email

typescript
// app/api/send/route.ts
import { NextResponse } from 'next/server';
import { PostStack, PostStackError } from '@poststack.dev/sdk';

const poststack = new PostStack(process.env.POSTSTACK_API_KEY!);

export async function POST() {
  try {
    const { id } = await poststack.emails.send({
      from: 'hello@yourdomain.com',
      to: ['user@example.com'],
      subject: 'Hello from Next.js!',
      html: '<h1>Welcome!</h1>',
    });
    return NextResponse.json({ id });
  } catch (err) {
    if (err instanceof PostStackError) {
      return NextResponse.json({ error: err.message }, { status: err.statusCode });
    }
    throw err;
  }
}

4. Handle errors

Next.js idioms for error handling, retries, and structured logging when calling the PostStack API.

typescript
import { NextResponse } from 'next/server';
import { PostStack, PostStackError } from '@poststack.dev/sdk';

const poststack = new PostStack(process.env.POSTSTACK_API_KEY!);

export async function POST(req: Request) {
  const body = (await req.json()) as { to: string; subject: string; html: string };

  try {
    const { id } = await poststack.emails.send({
      from: 'hello@yourdomain.com',
      to: [body.to],
      subject: body.subject,
      html: body.html,
    });
    return NextResponse.json({ id });
  } catch (err) {
    if (err instanceof PostStackError) {
      // Forward PostStack's status code so the client sees 422 for
      // validation failures, 429 for rate limits, 5xx for upstream issues.
      console.error('PostStack error', {
        status: err.statusCode,
        message: err.message,
        requestId: err.requestId,
      });
      return NextResponse.json({ error: err.message }, { status: err.statusCode });
    }
    throw err;
  }
}

Framework integrations

App Router — Route Handlers

The recommended path. Create `app/api/send/route.ts` exporting an async `POST` function, build the SDK client at module scope, and `await poststack.emails.send(...)` inside the handler. Module-scope instantiation keeps the connection pool warm between requests on serverless and Edge runtimes.

App Router — Server Actions

Mark the action with `"use server"` and call `poststack.emails.send(...)` directly. Server Actions inherit the request, so you can read cookies, headers, and form data without a separate fetch hop. Use this for form-driven sends (contact, support, signup confirmation).

Edge Runtime

The SDK runs unmodified on the Edge Runtime since it relies only on the platform fetch. Set `export const runtime = "edge"` in your route file. Edge is great for lower-latency sends from globally distributed users, though the cold-start improvement is small for European-only audiences.

Pages Router (legacy)

Use `pages/api/send.ts` with a default-exported handler. Same client, same method calls — only the import surface changes (`NextApiRequest`/`NextApiResponse` instead of the App Router’s Request/Response).

Common pitfalls

  • Calling from a Client Component

    Never call `poststack.emails.send(...)` in a `"use client"` component. The API key would ship to the browser and be visible in the bundle. Always route through a server-side handler or Server Action.

  • Using `NEXT_PUBLIC_` for the API key

    `NEXT_PUBLIC_*` variables are exposed to the client. Use a plain `POSTSTACK_API_KEY` env var — Next.js will keep it server-only.

  • Recreating the client on every request

    Instantiate the `PostStack` client once at module scope. Re-creating it inside the request handler discards the HTTP connection pool and adds latency under load.

FAQ

Where should I instantiate the SDK in a Next.js app?

At module scope inside the file that uses it (a Route Handler or Server Action). Module-scope instantiation lets the runtime reuse the HTTP connection pool across requests on Node and Edge.

Does the SDK work on the Edge Runtime?

Yes — it uses the platform fetch and has no Node-only dependencies. Set `export const runtime = "edge"` on your route to opt into Edge.

Can I send from a Server Action?

Yes. Mark the action with `"use server"` and call `poststack.emails.send(...)`. Server Actions are the cleanest fit for form-driven flows like contact and signup confirmations.

What about React Email for templates?

Use React Email to author templates as React components, build them with `npx react-email build`, and either upload the compiled HTML to PostStack as a named template or pass it inline on each send.

Related guides

Ready to send emails with Next.js?

Create a free account and get your API key in under a minute.