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
npm install @emails.sh/sdk# .env.local, which create-next-app already gitignores.
# The key comes from https://emails.sh/dashboard/api-keys.
EMAILSSH_API_KEY=esh_your_key_hereOne client for the app
// 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
'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
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.