Unosend
Send transactional emails via the Unosend HTTP API
The Unosend transport is an official Better-Notify package that sends transactional email through the Unosend HTTP API. It uses plain fetch() with zero external dependencies, so it works in Node.js, Bun, Cloudflare Workers, and any runtime with a global fetch.
Install
npm install @betternotify/unosend @betternotify/core @betternotify/emailGetting your API key
- Sign up or log in at unosend.co.
- Go to API Keys in your dashboard.
- Create a new API key and copy it — it starts with
un_.
Store it as an environment variable:
UNOSEND_API_KEY=un_xxxxxxxxxxxxxxxxxxxxxxxxxYou must verify your sending domain before sending emails through Unosend.
Usage
import { createNotify, createClient } from '@betternotify/core';
import { emailChannel } from '@betternotify/email';
import { unosendTransport } from '@betternotify/unosend';
const email = emailChannel({
defaults: { from: { name: 'My App', email: 'noreply@example.com' } },
});
const rpc = createNotify({ channels: { email } });
const catalog = rpc.catalog({
/* routes */
});
const mail = createClient({
catalog,
transportsByChannel: {
email: unosendTransport({
apiKey: process.env.UNOSEND_API_KEY!,
}),
},
});Options
Prop
Type
Error handling
The transport maps Unosend HTTP status codes to Better-Notify error codes:
| HTTP status | Better-Notify code | Retriable |
|---|---|---|
| 400 | VALIDATION | No |
| 401 | CONFIG | No |
| 403 | CONFIG | No |
| 422 | VALIDATION | No |
| 429 | RATE_LIMITED | Yes |
| 500+ | PROVIDER | Yes |
Network failures and unparseable responses are wrapped as PROVIDER errors. Timeouts are wrapped as TIMEOUT errors.
Per-send overrides
Unosend supports priority, scheduled sending, and tracking toggles as per-send overrides via TransportDataMap:
await mail.welcome.send({
to: 'user@example.com',
input: { name: 'Alice' },
transport: {
unosend: {
priority: 'high',
scheduled_for: '2026-06-01T10:00:00Z',
tracking: { open: false, click: true },
},
},
});Prop
Type
Tags
Better-Notify's tags field (a Record<string, string | number | boolean>) is converted to Unosend's Array<{ name, value }> format automatically:
await mail.welcome.send({
to: 'user@example.com',
input: { name: 'Alice' },
tags: { campaign: 'onboarding', version: 2 },
});
// Sent as: [{ name: "campaign", value: "onboarding" }, { name: "version", value: "2" }]Attachments
The transport base64-encodes attachment content before sending:
await mail.invoice.send({
to: 'user@example.com',
input: { orderId: '12345' },
attachments: [
{ filename: 'invoice.pdf', content: pdfBuffer, contentType: 'application/pdf' },
],
});