Integration
Send email from Next.js with SNDR.
App Router, Pages Router, Server Actions, Route Handlers, Edge runtime — one SDK works across every Next.js surface. Cold-start-friendly, no native dependencies, deploys to Vercel cleanly.
Next.js is the framework most SNDR customers build on. The SDK is engineered to feel native there: ESM-first, fetch-based (no Node-only crypto path), tiny enough to import from an Edge Function, and typed end-to-end with TS 5. The recipes below cover the four patterns every Next.js codebase ends up needing — a Server Action that sends, a Route Handler that sends, a webhook receiver with signature verification, and the Edge-runtime escape hatch.
Setup
Install + configure
pnpm add @rkiza/sndr
# or
npm install @rkiza/sndr
# or
yarn add @rkiza/sndr# .env.local
SNDR_API_KEY=sndr_live_x9k2...
SNDR_WEBHOOK_SECRET=... # only if you receive webhooksRecipes
The patterns you actually need.
Send from a Server Action (App Router)
Server Actions are the cleanest way to send email from a form. The SDK runs in the Node runtime by default. Mark the action with 'use server' and you're done — Next handles the serialisation.
// app/contact/actions.ts 'use server'; import { Sndr } from '@rkiza/sndr'; import { z } from 'zod'; const sndr = new Sndr(process.env.SNDR_API_KEY!); const contactSchema = z.object({ name: z.string().min(2).max(120), email: z.string().email(), message: z.string().min(10).max(8000), }); export async function sendContact(formData: FormData) { const input = contactSchema.parse({ name: formData.get('name'), email: formData.get('email'), message: formData.get('message'), }); await sndr.emails.send({ from: 'hello@yourdomain.com', to: 'team@yourdomain.com', reply_to: input.email, subject: `New contact from ${input.name}`, text: input.message, }); }Send from a Route Handler
For non-form requests — e.g. a POST from another service or a scheduled job — use a Route Handler. The SDK auto-generates an Idempotency-Key per logical call and reuses it on retries, so a transient 5xx never double-delivers.
// app/api/welcome/route.ts import { NextResponse } from 'next/server'; import { Sndr, SndrError } from '@rkiza/sndr'; const sndr = new Sndr(process.env.SNDR_API_KEY!); export async function POST(req: Request) { const { email, name } = await req.json(); try { const { id } = await sndr.emails.send({ from: 'hello@yourdomain.com', to: email, subject: `Welcome, ${name}`, html: `<p>Thanks for joining us, ${name}.</p>`, }); return NextResponse.json({ id }); } catch (err) { if (err instanceof SndrError) { return NextResponse.json( { error: { code: err.code, message: err.message } }, { status: err.status }, ); } throw err; } }Verify an incoming webhook
When SNDR posts an event to your /api/webhooks/sndr endpoint, you need the raw body to verify the HMAC signature. Next.js's Route Handlers give you the raw stream directly — don't re-serialise.
// app/api/webhooks/sndr/route.ts import { verifyWebhookSignature } from '@rkiza/sndr'; import type { WebhookEventPayload } from '@rkiza/sndr'; export async function POST(req: Request) { const raw = Buffer.from(await req.arrayBuffer()); const ok = verifyWebhookSignature( raw, req.headers.get('x-sndr-signature') ?? '', process.env.SNDR_WEBHOOK_SECRET!, ); if (!ok) return new Response('bad signature', { status: 400 }); const event: WebhookEventPayload = JSON.parse(raw.toString('utf8')); switch (event.type) { case 'email.delivered': // event.data is typed as { email_id, to: string[] } console.log('delivered', event.data.email_id); break; case 'email.bounced': console.log('bounced', event.data.email_id, event.data.error); break; case 'email.unsubscribed': console.log('unsub', event.data.addresses); break; } return new Response('ok'); }Send from the Edge runtime
The SDK uses platform fetch, so it works in Edge Functions without any extra config. Set the runtime export to 'edge' and the cold start drops to ~20ms.
// app/api/otp/route.ts import { NextResponse } from 'next/server'; import { Sndr } from '@rkiza/sndr'; export const runtime = 'edge'; const sndr = new Sndr(process.env.SNDR_API_KEY!); export async function POST(req: Request) { const { email } = await req.json(); const code = String(Math.floor(100000 + Math.random() * 900000)); await sndr.emails.send({ from: 'auth@yourdomain.com', to: email, subject: `Your sign-in code: ${code}`, text: `Your code is ${code}. It expires in 10 minutes.`, }); // ...persist code with TTL... return NextResponse.json({ ok: true }); }Send a magic link
The same Edge-runtime pattern as OTP, with the auth flow shifted to a tokenised URL. The SDK auto-generates an idempotency key so a double-submit doesn't email twice.
// app/api/magic-link/route.ts import { Sndr } from '@rkiza/sndr'; import { randomBytes } from 'node:crypto'; const sndr = new Sndr(process.env.SNDR_API_KEY!); export async function POST(req: Request) { const { email } = await req.json(); const token = randomBytes(32).toString('hex'); // ...persist token → email with 15-min TTL... const link = `https://yourdomain.com/auth/callback?token=${token}`; await sndr.emails.send({ from: 'auth@yourdomain.com', to: email, subject: 'Sign in to yourdomain', html: `<p><a href="${link}">Click here to sign in</a>. Link expires in 15 minutes.</p>`, }); return new Response('check your email'); }
Next.js FAQ
Does the SDK work in the Edge runtime?
Yes. The SDK uses platform fetch and has no Node-only dependencies. Set export const runtime = "edge" on any Route Handler and the SDK works as-is.
How do I keep the API key out of client bundles?
Never instantiate the Sndr client in a Client Component or anywhere the bundle ships to the browser. Use Server Actions, Route Handlers, or middleware — all of which run server-side and never expose process.env.SNDR_API_KEY to the client.
What about the Pages Router?
Identical SDK, just a different surface. Instantiate Sndr at module scope inside pages/api/<route>.ts and call sndr.emails.send from the handler.
How do I avoid double-sending on retries?
The SDK auto-generates a UUID Idempotency-Key per logical send and reuses it across retries. You can override with a stable key from your domain (e.g. order id) by passing idempotencyKey in RequestOptions.
How do I test webhook delivery locally?
Tunnel localhost with ngrok or cloudflared, register the public URL as a webhook endpoint in the SNDR dashboard, then trigger sends. The dashboard shows every delivery attempt with the response code and body — debug failures end-to-end without a redeploy.
Ship your first Next.js email in 5 minutes.
Free tier includes 3,000 emails / month. No card required.
Start building
Your next message starts here.
Connect your domain, create an API key, and send with SNDR.