# Next.js

A server action and a route handler, with the key kept out of the browser bundle.

The only rule that matters in Next.js is that the send happens on the server. A key in a client component is a key in the JavaScript bundle, and anybody can read it. Server actions, route handlers, and server components are all fine; "use client" files are not.

### Install

App Router, Next 14 or 15:
```bash
npm install @emails.sh/sdk
```

.env.local:
```bash
# .env.local, which create-next-app already gitignores.
# The key comes from https://emails.sh/dashboard/api-keys.
EMAILSSH_API_KEY=esh_your_key_here
```

Do not prefix it with NEXT_PUBLIC_. That prefix is what pushes a variable into the browser bundle, which is the one thing this key must never be in.

### One client for the app

lib/emails.ts:
```ts
// lib/emails.ts
import 'server-only';
import { Emailssh } from '@emails.sh/sdk';

if (!process.env.EMAILSSH_API_KEY) {
  throw new Error('EMAILSSH_API_KEY is not set. Add it to .env.local.');
}

export const mail = new Emailssh({ apiKey: process.env.EMAILSSH_API_KEY });
```

The server-only import turns an accidental import from a client component into a build error rather than a leaked key at runtime. Install it with npm install server-only.

### A server action

app/actions/subscribe.ts:
```ts
// app/actions/subscribe.ts
'use server';

import { mail } from '@/lib/emails';

export async function subscribe(formData: FormData) {
  const address = String(formData.get('email') ?? '');
  if (!address.includes('@')) return { error: 'That does not look like an email address.' };

  const { id } = await mail.send({
    from: 'Acme <hello@acme.com>',
    to: [address],
    subject: 'Confirm your subscription',
    html: '<p>Click the link in this email to confirm.</p>',
    text: 'Click the link in this email to confirm.',
    idempotencyKey: `subscribe-${address}`
  });

  return { id };
}
```

### Or a route handler

app/api/send/route.ts:
```ts
// app/api/send/route.ts
import { NextResponse } from 'next/server';
import { mail } from '@/lib/emails';

export async function POST(request: Request) {
  const { to, subject, html } = await request.json();

  try {
    const { id } = await mail.send({ from: 'Acme <hello@acme.com>', to: [to], subject, html });
    return NextResponse.json({ id });
  } catch (err) {
    // The message says what to do about it. Log it; do not show it to the user.
    console.error('emails.sh refused the send', err);
    return NextResponse.json({ error: 'Could not send that email.' }, { status: 502 });
  }
}
```

On Vercel, set EMAILSSH_API_KEY in the project's environment variables for every environment you deploy, then redeploy. A variable added without a redeploy is not in the running build.

---

Base URL: https://emails.sh/v1. Auth: `Authorization: Bearer esh_...`.
Whole API in one file: https://emails.sh/llms.txt. All documentation: https://emails.sh/docs.md.
