Frameworks and agents · Published 2026-10-31 · Updated 2026-10-31 · AgentiSend
Send email with TanStack Start
A TanStack Start server function that sends one form receipt with the Node SDK and replays a retried submit.
To send email with TanStack Start, export a createServerFn({ method: 'POST' }) handler and send from that handler. TanStack Start's server-functions guide defines createServerFn in @tanstack/react-start. The handler receives { data }. The function below checks the address, calls emails.send with an idempotency key derived from the person who submitted the form, and returns the message id or the refusal's code and fix. The suite calls this function against a local AgentiSend server. It does not load the Start package.
A form that the browser submits twice is the case the key budget is for. When the key has spent its ceiling, emails.send throws agent_budget_exceeded and the fix says what to change. POST /limits/keys/{id}/kill stops that one key. Keep the message id: GET /emails/{id} is the delivery record.
Install
pnpm add agentisendRegister the function in a .functions.ts file, which is the split the server-functions guide uses so the handler can be imported from a component:
import { createServerFn } from '@tanstack/react-start';
import { handler } from './send';
export const sendReceipt = createServerFn({ method: 'POST' }).handler(handler);If you define src/start.ts, the same guide says to add createCsrfMiddleware() for server functions. A project that does not define that file gets the middleware automatically.
Environment
| Variable | Required | What it is |
|---|---|---|
AGENTISEND_API_KEY | yes | A key with sending_access. Create one in the console or with POST /api-keys. |
MAIL_FROM | yes | The From address, on a domain you have verified. |
AGENTISEND_BASE_URL | no | Defaults to https://api.agentisend.com. |
The route
/**
* TanStack Start — the function a server function handler calls.
*
* The documented registration is:
*
* import { createServerFn } from '@tanstack/react-start'
* export const sendReceipt = createServerFn({ method: 'POST' }).handler(handler)
*
* `handler` receives `{ data }`. This file is that function. The suite calls
* it on Node and does not load `@tanstack/react-start`.
*
* AGENTISEND_API_KEY, MAIL_FROM, and optionally AGENTISEND_BASE_URL.
*/
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;
}
/** Enough to refuse an obvious typo here; the API checks the address properly. */
const ADDRESS = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export async function handler(args: {
data: { email?: string; note?: string };
}): Promise<{ status: number; body: Record<string, unknown> }> {
const email = String(args.data.email ?? '').trim();
const note = String(args.data.note ?? '');
if (!ADDRESS.test(email)) {
return { status: 400, body: { error: 'email must be one address, like you@example.com' } };
}
try {
const { id } = await agentisend.emails.send(
{
from: mailFrom(),
to: email,
subject: 'Your form was received',
text: `Form note: ${note}`,
},
// The person who submitted the form, not the clock: a retried call replays.
{ idempotencyKey: `form/${email}` },
);
return { status: 200, body: { id } };
} catch (err) {
if (err instanceof AgentiSendError) {
return { status: err.status, body: { code: err.code, fix: err.fix } };
}
return { status: 500, body: { error: 'send failed' } };
}
}Try it
Call sendReceipt({ data: { email, note } }) from the form submit handler. The return value is { status, body }. body.id is the message id when status is the success value the function returns.
What comes back when it goes wrong
Every refused request carries code, message, fix and docs_url. The function returns code and fix with the API's status. An address with no domain is answered here and never reaches POST /emails. The error catalogue lists every code.