tinysend

Transactional Email — tinysend developers

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 — optional Record<string,string>, stored, passed to the provider, echoed back in webhooks and on GET.
  • Idempotency-Key header — 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.