Send a single email to one recipient — verification links, password resets, receipts, magic links — with one API call. The address you send from is a mailbox, so replies come back to a real inbox where your automations and webhooks fire. No other transactional provider does that.
curl https://api.tinysend.com/v1/emails \
-H "Authorization: Bearer sk_mbx_..." \
-H "Content-Type: application/json" \
-d '{
"from": "auth@acme.tinysend.com",
"to": "user@example.com",
"subject": "Verify your email",
"html": "<p>Confirm: <a href=\"https://acme.com/v/abc\">click here</a></p>"
}'
import { Tinysend } from 'tinysend';
const ts = new Tinysend('sk_mbx_...');
await ts.emails.send({
from: 'auth@acme.tinysend.com',
to: 'user@example.com',
subject: 'Verify your email',
html: '<p>Confirm: <a href="https://acme.com/v/abc">click here</a></p>',
});
You send from a mailbox
The from address is a mailbox — the core tinysend primitive. Create one in the dashboard or with POST /v1/mailboxes, then send as that address. A mailbox on {slug}.tinysend.com sends immediately with zero DNS setup; bringing your own verified domain is an upgrade, not a prerequisite.
Because the sender is a real mailbox, anything sent back to it is received: a reply to a receipt, a customer answering a notification. Inbound automations run and email.received webhooks fire. Other providers drop replies on the floor — here the loop closes.
Request
POST /v1/emails — Bearer sk_* auth, account-scoped.
from— the mailbox’s tinysend address or its verified custom-domain address. Optional with a mailbox-scoped key (defaults to the mailbox address).to— single recipient. Required.subject— required.html,text— at least one required.reply_to— optional.tag— optional label (e.g.auth.verification), stored and filterable.metadata— optionalRecord<string,string>, stored, passed to the provider, echoed back in webhooks and onGET.Idempotency-Keyheader — optional. Auth flows retry; the same key within 24h returns the original email id and never resends.
Returns 201 with the email id and status. Fetch delivery state later with GET /v1/emails/:id; bounces and opens flow into the same dashboard as the rest of your mail.
Keys
Use a mailbox-scoped key (sk_mbx_..., created on the mailbox’s integrate page) to authorize sending for one address — from then defaults to that mailbox. An account-wide sk_... key also works; from picks any mailbox you own.
better-auth
If you use better-auth, @tinysend/better-auth ships senders adapters so verification, magic-link, and OTP emails go through tinysend with no glue code:
import { betterAuth } from 'better-auth';
import { Tinysend } from 'tinysend';
import { senders } from '@tinysend/better-auth';
const ts = new Tinysend(process.env.TINYSEND_KEY!);
const from = 'auth@acme.tinysend.com';
export const auth = betterAuth({
emailVerification: {
sendVerificationEmail: senders.verification(ts, { from }),
},
emailAndPassword: {
sendResetPassword: senders.reset(ts, { from }),
},
});
senders covers verification, reset, magic link, OTP, two-factor, change-email, delete-account, and invitations — each a drop-in for the matching better-auth hook. The same package also ships a tinysend() audience plugin that syncs sign-ups into a newsletter; see better-auth.
Limits
Transactional sends count against the same email meter as everything else. Over the limit returns 402 with an upgrade link (agents can pay inline). See limits.