Send emails with Hono
Transactional and marketing email from a Hono handler. It is one standard fetch call, so it runs wherever Hono does.
✓Free to start. Unlimited contacts on every tier.
One request, complete trace
Request
POST /api/v1/emails
Authorization: Bearer aem_…
Idempotency-Key: welcome-8f21
{ "to": "[email protected]", "template": "welcome" }
202 Accepted
{ "id": "msg_7ca31", "status": "queued" }
[ 01 / 05 ]Quickstart
How do I send email from Hono?
Create a key, keep it on the server, and call one endpoint from a Hono handler.
Get an API key
Open Settings → API & MCP and create a key with the Send + manage permission (the default). It starts with aem_ and is shown once.
Store it as a server-side environment variable
On Cloudflare Workers, store the key as a secret. Hono hands it to each request as c.env.AEM_KEY. On Node, use process.env.AEM_KEY instead.
Terminalnpx wrangler secret put AEM_KEYAdd a send helper
A Worker has no global environment, so the helper takes the key as an argument.
src/email.tsexport function sendEmail(apiKey: string, idempotencyKey: string, body: object) { return fetch("https://sendandretain.com/api/v1/emails", { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", "Idempotency-Key": idempotencyKey, }, body: JSON.stringify(body), }); }Send the email
The handler passes c.env.AEM_KEY to the helper and returns the API's status and body. A retry with the same Idempotency-Key won't send a second email.
src/index.tsimport { Hono } from "hono"; import { sendEmail } from "./email"; const app = new Hono<{ Bindings: { AEM_KEY: string } }>(); app.post("/welcome", async (c) => { const { email, userId } = await c.req.json(); const res = await sendEmail(c.env.AEM_KEY, `welcome-${userId}`, { to: email, template: "welcome", }); return Response.json(await res.json(), { status: res.status }); }); export default app;Check the response
A 202 means queued, not delivered. Keep the id, then follow delivery in the message log or through a signed webhook.
202 Accepted{ "id": "msg_7ca31", "status": "queued" }
[ 02 / 05 ]Where it goes
Where should email code live in your Hono app?
A typed Hono handler
A plain fetch call keeps the send portable. Your runtime decides where the secret lives.
Events from the handler
Send product events from the handler that checks and saves the action, not from client tracking.
The raw request for webhooks
c.req.raw is the untouched Request, which is exactly what the signature check needs.
[ 03 / 05 ]Marketing email
Can I send marketing emails from Hono too?
Yes. Send an event from the same Hono handler when a user signs up, starts a trial, or goes quiet, and an automation sends the right email. Automations start paused, and every email goes to one person: no newsletter blasts.
- Transactional and marketing email share one contact record and one unsubscribe list.
- Unsubscribes, bounces, and complaints are suppressed for you before every send.
- Templates are react.email components; published versions never change under you.
[ 04 / 05 ]Example prompts
What can you ask your coding agent to do in Hono?
Claude Code, Cursor, and Codex can do these over the Send & Retain MCP server, with your repo open. Automations they build stay paused until you turn them on.
Welcome email
“Add a welcome email to our Hono signup route using Send & Retain, with an idempotency key.”
Workers secret
“Wire Send & Retain into our Hono app on Cloudflare Workers, with the API key stored as a Worker secret.”
Webhook
“Add a Hono route that verifies Send & Retain delivery webhooks from c.req.raw and flags bounced users.”
Trial automation
“Send a trial_started event from our Hono API and build a paused trial-conversion automation for it.”
[ 05 / 05 ]Related
Keep building with Hono
Questions people ask us
Send your first email from Hono today.
Create a key, paste the code into a Hono handler, and watch the email land in the message log.