Send email with Next.js
Prerequisites
Before you start, you need:
- A mails.ai API key. A test key (
mk_test_…) runs everything on this page and sends no real email. - A Next.js app that uses the App Router.
Guide
Install
Get the mails.ai TypeScript SDK.
npm install @mailsai/sdkAdd your API key
Next.js loads
.env.localintoprocess.envon the server, and the SDK readsMAILS_API_KEYfrom there. Leave off theNEXT_PUBLIC_prefix: the key must never reach the browser.MAILS_API_KEY=mk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxSend from a route handler
fromnames the agent that sends the message: an agent with that name is used if it exists, and created if it does not when the key may create agents (themanagescope) and your plan has room for another; withoutmanagethe send answers403 insufficient_scope.mailsreads the key when it is first used, so importing it is safe in a build that has no key.import { mails, MailsError } from "@mailsai/sdk"; export async function POST() { try { const sent = await mails.send({ from: "hello", to: "reply@test.mails.ai", subject: "Welcome aboard", body: "Thanks for signing up. Reply any time and I will read it.", }); return Response.json({ id: sent.id, status: sent.status, from: sent.from }); } catch (error) { if (error instanceof MailsError) { return Response.json({ code: error.code, message: error.message }, { status: error.status }); } throw error; } }Start the app and call the route:
curl -X POST http://localhost:3000/api/sendreply@test.mails.aiis an address of ours that answers within about a second, so there is already a reply waiting for the next step. How the test address works.Receive replies on a webhook
mails.ai signs every event it posts to your endpoint with an
X-Mails-Signatureheader.mails.webhooks.verifychecks that header against the exact bytes that arrived and returns the event, ornullwhen the check fails. For 24 hours after you rotate the signing secret the header carries twov1signatures, soverifyEventchecks each one. Read the body withrequest.text(): a parsed and re-serialized body no longer matches the signature.import { mails } from "@mailsai/sdk"; // Accepts the event when any v1 signature in the header matches the secret. function verifyEvent(body: string, header: string, secret: string) { const parts = header.split(","); const t = parts.find((p) => p.startsWith("t=")) ?? ""; for (const v1 of parts.filter((p) => p.startsWith("v1="))) { const event = mails.webhooks.verify(body, `${t},${v1}`, secret); if (event) return event; } return null; } export async function POST(request: Request) { const body = await request.text(); const signature = request.headers.get("x-mails-signature") ?? ""; const event = verifyEvent(body, signature, process.env.MAILS_WEBHOOK_SECRET ?? ""); if (!event) { return new Response("bad signature", { status: 400 }); } if (event.type === "reply.received" && !event.quarantined) { const reply = event.data; console.log(reply.subject, reply.body_text_excerpt, event.injection_score); // Hand the reply to your agent here. } return new Response(null, { status: 200 }); }quarantinedistruewhen the message scored as a likely prompt injection. Skip those, and answer with a2xxwithin 5 seconds: any other answer, or none, counts as a failed delivery and is tried again, 3 attempts in all.Register the webhook
Deploy the app, then tell mails.ai where the route lives. The address must be
https://and reachable from the internet. This call needs a key with themanagescope.curl https://api.mails.ai/v1/webhooks \ -H "Authorization: Bearer $MAILS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.com/api/webhooks/mails", "event_types": ["reply.received", "message.received"] }'The answer carries a
signing_secretthat starts withwhsec_and is shown once. Save it asMAILS_WEBHOOK_SECRETin your host’s environment settings.
GET /v1/events?event_type=reply.received returns the same events, and Events & streaming shows how to keep a live connection open.Next steps
Was this page helpful?