REST API

A small API for real email.

All writes are workspace-scoped. API keys can be restricted to one domain and to explicit scopes.

Authentication

Send the API key as a bearer token. The raw key is displayed once and stored only as a SHA-256 hash.

Authorization: Bearer ts_your_key

Send an email

Supply inline content or a template ID. Use an idempotency key for every business event.

curl https://your-tinysend.example/api/v1/emails \
  -H "Authorization: Bearer ts_your_key" \
  -H "Idempotency-Key: receipt-order-123" \
  -H "Content-Type: application/json" \
  -d '{"to":"[email protected]","subject":"Your receipt","html":"<h1>Thank you</h1>"}'

The API returns 202 Accepted. Poll GET /api/v1/emails/:id for status.

Batch sends

POST /api/v1/emails/batch accepts up to 100 items. Each result reports its own accepted or failed status.

Synchronize users

POST /api/v1/contacts/sync upserts one contact or up to 1,000 contacts by normalized email.

curl https://your-tinysend.example/api/v1/contacts/sync \
  -H "Authorization: Bearer ts_your_key" \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","externalId":"user_42","attributes":{"plan":"pro"}}'

Templates

List current versions with GET /api/v1/templates. Dashboard edits create immutable versions, and transactional sends always use the current version.

Broadcasts

Create a draft with POST /api/broadcasts, then send it with POST /api/broadcasts/:id/send. Broadcasts include every subscribed contact, exclude suppressions, and append a signed unsubscribe link.

Errors

Responses use standard HTTP statuses. Validation errors include field-level issues. Every response includes X-Request-Id for logs.

npm SDK

import { Tinysend } from '@tinysend/sdk'

const email = new Tinysend({ apiKey: process.env.TINYSEND_API_KEY })
await email.emails.send({
  to: '[email protected]',
  templateId: 'template-uuid',
  variables: { firstName: 'Ada' }
}, { idempotencyKey: 'welcome-user-42' })