Integration
Send emails from Next.js with Postkit
Send transactional emails from Next.js using Server Actions, Route Handlers, or API routes. Postkit's REST API works with the App Router and Pages Router alike.
1. Set your API key
# .env.local POSTKIT_API_KEY=pk_live_...
2. Send an email
typescript
// app/api/send/route.ts
import { NextResponse } from "next/server";
export async function POST(req: Request) {
const { to, name } = await req.json();
const res = await fetch("https://api.postkit.eu/v1/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.POSTKIT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "hello@yourapp.eu",
to,
subject: "Welcome aboard!",
html: `<h1>Welcome, ${name}!</h1>`,
}),
});
const data = await res.json();
return NextResponse.json(data);
}3. Handle webhooks
Postkit sends delivery events (sent, delivered, bounced, opened, clicked) via HMAC-SHA256 signed webhooks following the Standard Webhooks specification.
typescript
// app/api/webhooks/postkit/route.ts
import { NextResponse } from "next/server";
import crypto from "crypto";
export async function POST(req: Request) {
const body = await req.text();
const signature = req.headers.get("webhook-signature") ?? "";
const timestamp = req.headers.get("webhook-timestamp") ?? "";
const msgId = req.headers.get("webhook-id") ?? "";
// Verify HMAC-SHA256 (Standard Webhooks)
const secret = Buffer.from(
process.env.POSTKIT_WEBHOOK_SECRET!.replace("whsec_", ""),
"base64"
);
const content = `${msgId}.${timestamp}.${body}`;
const expected = crypto
.createHmac("sha256", secret)
.update(content)
.digest("base64");
const signatures = signature.split(" ");
const valid = signatures.some((sig) => {
const val = sig.replace(/^v1,/, "");
return crypto.timingSafeEqual(
Buffer.from(val, "base64"),
Buffer.from(expected, "base64")
);
});
if (!valid) {
return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
}
const event = JSON.parse(body);
console.log("Postkit event:", event.type, event.data.email_id);
return NextResponse.json({ received: true });
}