Send
// app/api/contact/route.ts
export async function POST(req: Request) {
const { name, email, message } = await req.json();
const res = await fetch("https://api.usemektup.com/v1/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MEKTUP_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "website@yourapp.com",
to: "hello@yourapp.com",
replyTo: email,
subject: `Message from ${name}`,
text: message,
}),
});
return Response.json(await res.json(), { status: res.status });
}Add MEKTUP_API_KEY to your environment variables on Vercel or in .env.local. The same body works in a server action.
Receive
// app/api/inbound/route.ts
import crypto from "node:crypto";
export async function POST(req: Request) {
const raw = await req.text(); // the exact body Mektup signed
const expected = crypto.createHmac("sha256", process.env.MEKTUP_WEBHOOK_SECRET!)
.update(raw).digest("hex");
const got = (req.headers.get("x-mektup-signature") ?? "").replace("sha256=", "");
if (got.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {
return new Response("bad signature", { status: 401 });
}
const { mailbox, message } = JSON.parse(raw);
await saveTicket(mailbox, message); // your code
return new Response(null, { status: 200 });
}Set the webhook with PUT /v1/domains/:domain/mailboxes/:localPart/webhook and store the secret it returns as MEKTUP_WEBHOOK_SECRET. Answer within 8 seconds.