Get your API key
All integrations Next.js

Next.js + MailKite

next-mailkite is the official MailKite package for Next.js — a signature-verified App Router route handler for inbound email, plus a sendEmail() re-export of the MailKite SDK. Next.js has no swappable "mailer" concept, so this is a webhook-consumer-and-sender package, not a transport driver.

What you need

  • A verified domain with SPF + DKIM published
  • Your API key (mk_live_…)
  • A Next.js 13.4+ project using the App Router

Install

terminal
npm install next-mailkite

Receive email — an auto-reply bot in two files

Create app/api/mailkite/inbound/route.ts. createMailKiteRouteHandler() verifies the x-mailkite-signature header (HMAC-SHA256, no network call) and hands the parsed event to your handler:

app/api/mailkite/inbound/route.ts
import { createMailKiteRouteHandler } from 'next-mailkite';
import handler from '@/lib/mailkite-handler';

export const { POST } = createMailKiteRouteHandler(handler);

Your handler's default export runs for every verified delivery:

lib/mailkite-handler.ts
import { sendEmail, replyOk } from 'next-mailkite';
import type { MailKiteInboundHandler } from 'next-mailkite';

const handler: MailKiteInboundHandler = async (event) => {
const m = event.message;
if (event.type !== 'email.received' || !m) return replyOk();

await sendEmail({
from: 'bot@myapp.ai', // an address on your verified domain
to: m.from,
subject: `Re: ${m.subject ?? 'your email'}`,
inReplyTo: m.messageId,
text: 'Thanks — got your message. A human will follow up soon.',
});
return replyOk();
};

export default handler;

Point your MailKite domain's webhook URL at https://yourapp.com/api/mailkite/inbound (dashboard → domain → Webhooks). No handler yet? Call createMailKiteRouteHandler() with no arguments — deliveries are still verified, logged, and acknowledged; nothing is dropped silently.

Your handler can return:

  • undefined / void — acknowledged with replyOk()
  • a string — use replyOk(), replySpam(), replyDrop(), replyBlockSender() for control-mode replies
  • a plain object — JSON-serialized
  • a Response — returned as-is
  • a thrown error — 500, so MailKite retries the delivery

Send email

sendEmail() is a thin wrapper over the mailkite SDK's send() that reads MAILKITE_API_KEY from the environment. Call it from server actions or route handlers — never from a "use client" component:

app/actions.ts
'use server';
import { sendEmail } from 'next-mailkite';

export async function sendInvoice(to: string) {
return sendEmail({
from: 'hello@myapp.ai',
to,
subject: 'Your invoice #1042',
html: '<p>Thanks! Receipt attached.</p>',
});
}

Environment variables

.env.local
# .env.local
MAILKITE_API_KEY=mk_live_...
MAILKITE_WEBHOOK_SECRET=whsec_...
VariableUsed byWhere to get it
MAILKITE_WEBHOOK_SECRETthe route handler (signature verification)dashboard → Webhooks
MAILKITE_API_KEYsendEmail()dashboard → API keys

Test it

terminal
curl -X POST https://yourapp.com/api/mailkite/inbound \
-H "Content-Type: application/json" \
-H "x-mailkite-signature: t=...,v1=..." \
-d '{"type":"email.received","message":{"from":"test@example.com","to":"hello@yourdomain.com","subject":"Hello","text":"Test"}}'

Runtime notes

MailKite.verifyWebhook() uses node:crypto, so the inbound route must run on the Node.js runtime — the App Router default. If you set export const runtime = 'edge' on the route file, signature verification breaks; leave the runtime unset (or explicitly nodejs).

Troubleshooting

  • 500 "webhook secret not configured" — set MAILKITE_WEBHOOK_SECRET in your environment. The route refuses to process a delivery it can't verify.
  • 401 "invalid signature" — check the raw request body reaches the handler untouched; a body-parsing middleware ahead of the route can break signature verification, which is computed over the exact bytes MailKite sent.
  • Works locally, 500s on Vercel — confirm the route isn't set to runtime = 'edge' (see Runtime notes above).

See the next-mailkite npm package, the runnable starter, or Inbound webhooks for the full payload reference.