Docs

Frameworks and agents · Updated 2026-09-22

Send email from Hono

One POST route that sends a transactional email, the same file on Cloudflare Workers, Deno, Bun and Node.

To send email from a Hono app, add one POST route that calls AgentiSend and returns the message id. The SDK is plain fetch with no Node built-ins, so the same file runs on Cloudflare Workers, Deno, Bun and Node. It is executed against a real API on every build of this site's repository.

Install

pnpm add hono agentisend

Environment

VariableRequiredWhat it is
AGENTISEND_API_KEYyesA key with sending_access.
MAIL_FROMyesThe From address, on a domain you have verified.
AGENTISEND_BASE_URLnoDefaults to https://api.agentisend.com.

On Workers, read the key from the binding rather than the process environment and pass it to the constructor: new AgentiSend(c.env.AGENTISEND_API_KEY).

The route

/**
 * Hono — one POST route that sends a transactional email.
 *
 * Hono runs on Workers, Deno, Bun and Node, and the AgentiSend SDK is plain
 * fetch with no Node built-ins, so this file is the same on all of them.
 */
import { Hono } from 'hono';
import { AgentiSend, AgentiSendError } from 'agentisend';

const agentisend = new AgentiSend();

function mailFrom(): string {
  const from = process.env.MAIL_FROM;
  if (!from) throw new Error('Set MAIL_FROM to an address on a domain you have verified.');
  return from;
}

export const app = new Hono();

app.post('/send', async (c) => {
  const { email, subject, body } = await c.req.json<{
    email?: string;
    subject?: string;
    body?: string;
  }>();
  if (!email) return c.json({ error: 'email is required' }, 400);

  try {
    const { id } = await agentisend.emails.send(
      {
        from: mailFrom(),
        to: email,
        subject: subject ?? 'Your receipt',
        text: body ?? 'Thanks — your receipt is attached to your account.',
      },
      { idempotencyKey: `receipt/${email}` },
    );
    return c.json({ id });
  } catch (err) {
    if (err instanceof AgentiSendError) {
      // `fix` names the endpoint that repairs the call, so a client can act on
      // the failure without a human reading prose.
      return c.json({ code: err.code, fix: err.fix }, 502);
    }
    throw err;
  }
});

export default app;
examples/hono/app.ts, executed against the live API on every build

Try it

curl -X POST localhost:8787/send \
  -H 'content-type: application/json' \
  -d '{"email":"you@example.com"}'

What comes back when it goes wrong

Every refused request carries code, message, fix and docs_url. The route forwards code and fix, so a caller can act on a refusal without a human reading prose. The error catalogue lists every code.

Next