send16-mail
v0.9.0
Published
Node.js SDK for the Send16 email platform. Send transactional + marketing email from any stack. Get started: npx create-send16
Maintainers
Readme
send16-mail — Email for developers
The official Node.js SDK for Send16 — transactional and marketing email that just works.
Zero runtime dependencies. ~22 KB on disk, no transitive packages, no supply-chain surface. Works in Node.js 18+, Bun, Deno, Cloudflare Workers, and any other runtime that ships fetch. TypeScript-first.
Fastest start
npx create-send16Detects your framework (Next.js / Astro / Remix / SvelteKit / Nuxt / Hono / Express / Vite + React / plain Node), opens your browser to authorize, writes SEND16_API_KEY to .env.local, and drops an idiomatic lib/send16 module plus a working example route. ~60 seconds to first send, no pasted API keys.
Manual install
npm install send16-mail- Create a free account — 1,000 emails/month free.
- Open the dashboard → Developers → API Keys → Create key.
- Set
SEND16_API_KEYin your environment.
Quick start
import { Send16 } from 'send16-mail';
// Reads SEND16_API_KEY from process.env automatically.
// Or pass explicitly: new Send16('sk_live_xxxxx')
const send16 = new Send16();
const { data, error } = await send16.emails.send({
from: 'Acme <[email protected]>',
to: ['[email protected]'],
subject: 'Hello from Send16',
html: '<p>Hello world</p>',
});
if (error) console.error('Failed:', error.message);
else console.log('Sent:', data.id);Sending to multiple people
to, cc, and bcc each accept either a single email or an array of up to 50 addresses. One send() call → one log row → one email thread that lands in every recipient's inbox. Do not loop and call send() once per recipient — that doubles your API usage and breaks the thread.
// All peers — every recipient sees every other recipient in the To: header
await send16.emails.send({
from: 'Acme Ops <[email protected]>',
to: ['[email protected]', '[email protected]'],
subject: 'Production alert',
html: '<p>...</p>',
});to[] vs cc — when to use which. Multiple to recipients are peers (everyone is a primary recipient and sees each other). For a "primary recipient with a copy to someone else" pattern — form-submission notifications, audit mailboxes, account-manager visibility — use to for the primary and cc for the copies. That's what your recipients expect semantically.
// Form notification: firm owns it, internal address gets a copy
await send16.emails.send({
from: 'Contact Form <[email protected]>',
to: '[email protected]',
cc: ['[email protected]'],
subject: 'New enquiry from Jane Doe',
html: '<p>...</p>',
});Use bcc when the copy should be silent — neither the to nor cc recipients see it. Useful for compliance archives or oncall mirrors.
Attachments, CC/BCC, custom headers
import { readFileSync } from 'node:fs';
await send16.emails.send({
from: 'Acme Billing <[email protected]>',
to: '[email protected]',
cc: ['[email protected]'],
bcc: '[email protected]',
subject: 'Your invoice #1234',
html: '<p>Invoice attached.</p>',
attachments: [
{
filename: 'invoice-1234.pdf',
content: readFileSync('./invoice-1234.pdf').toString('base64'),
contentType: 'application/pdf',
},
],
headers: {
'X-Customer-Id': 'cust_8f2a',
'X-Invoice-Id': 'inv_1234',
},
});Limits. Up to 10 attachments per send, 10 MB total decoded (1 MB when scheduledAt is set, to keep the queue row light). Up to 20 custom headers, names match [A-Za-z0-9-]+, no CR/LF in values. Headers Send16 owns (from, to, subject, cc, bcc, reply-to, message-id, date, mime-version, content-*) cannot be overridden — use the dedicated fields instead.
Tasteful HTML in 5 lines — compose()
Don't want to write MJML or React Email? Pass a plain JS object and compose() returns a responsive, mobile-friendly HTML body plus a plain-text fallback. Pure function, runs anywhere, no API call needed for the render itself.
Welcome email
import { compose } from 'send16-mail';
const { html, text } = compose({
product: { name: 'Acme', link: 'https://acme.com' },
body: {
name: 'John',
intro: 'Welcome to Acme — your account is ready.',
action: {
instructions: 'Confirm your email to get started:',
button: { text: 'Confirm email', link: 'https://acme.com/verify?t=abc' },
},
outro: 'Need help? Just reply to this email.',
},
});Password reset
const { html, text } = compose({
product: { name: 'Acme' },
body: {
name: 'John',
intro: 'You requested a password reset for your Acme account.',
action: {
instructions: 'Click the button below to choose a new password:',
button: {
text: 'Reset password',
link: 'https://acme.com/reset?t=abc',
color: '#dc2626',
},
},
outro: 'If you did not request this, you can safely ignore the email.',
},
});Receipt
const { html, text } = compose({
product: { name: 'Acme' },
body: {
name: 'John',
intro: 'Thanks for your purchase! Here is your receipt.',
table: {
data: [
{ Item: 'Pro plan', Quantity: '1', Price: '$29.00' },
{ Item: 'Extra seat', Quantity: '2', Price: '$10.00' },
{ Item: 'Total', Quantity: '', Price: '$39.00' },
],
columns: { customAlignment: { Price: 'right' } },
},
outro: 'Questions about your invoice? Reply and we will help.',
},
});Compose + send in one call
await send16.compose.send({
from: 'Acme <[email protected]>',
to: '[email protected]',
subject: 'Welcome!',
product: { name: 'Acme' },
body: { name: 'John', intro: 'Thanks for signing up.' },
});Coming from mailgen?
send16-mail's compose() takes the same { product, body } shape — drop in your existing payload and you'll get HTML out the other side. The difference: we also send the email through a hosted, deliverability-tuned pipeline (DKIM/SPF/DMARC, dedicated IPs, bounce handling, opens/clicks, automations) instead of leaving you to wire up nodemailer + SES yourself.
// Roughly: replace this
const html = mailGenerator.generate({ body: { name, intro, action } });
await transporter.sendMail({ from, to, subject, html });
// …with this
await send16.compose.send({ from, to, subject, product, body: { name, intro, action } });Why send16-mail
- Zero dependencies — 22 KB SDK, no transitives, runs anywhere
fetchexists - Edge-ready — Cloudflare Workers, Vercel Edge, Deno Deploy out of the box
- Resend-compatible API — easy migration from Resend / SendGrid / Postmark / Mailgun
- Self-hosted option — bring your own server if you want full control
- Transactional + marketing — one platform, one SDK
- Free tier — 1,000 emails/month, no credit card
Configuration
const send16 = new Send16('sk_live_your_key', {
baseUrl: 'https://api.send16.com', // default; override for self-hosted
timeout: 30_000, // ms; default 30s
});Emails
Send
const { data, error } = await send16.emails.send({
from: 'Acme <[email protected]>',
to: ['[email protected]'],
subject: 'Hello',
html: '<p>Hello world</p>',
});All options
await send16.emails.send({
from: 'Acme <[email protected]>',
to: ['[email protected]'],
cc: ['[email protected]'],
bcc: ['[email protected]'],
replyTo: '[email protected]',
subject: 'Invoice #1234',
html: '<p>Your invoice is attached.</p>',
text: 'Your invoice is attached.',
headers: { 'X-Custom-Header': 'value' },
attachments: [
{ filename: 'invoice.pdf', content: '<base64>', contentType: 'application/pdf' },
],
tags: [{ name: 'category', value: 'invoices' }],
scheduledAt: '2026-04-01T09:00:00Z',
});Batch (up to 100)
await send16.emails.batch([
{ from: 'Acme <[email protected]>', to: ['[email protected]'], subject: 'Hi A', html: '<p>Hi!</p>' },
{ from: 'Acme <[email protected]>', to: ['[email protected]'], subject: 'Hi B', html: '<p>Hi!</p>' },
]);Contacts
await send16.contacts.create({ email: '[email protected]', firstName: 'John' });
await send16.contacts.list({ page: 1, limit: 50 });
await send16.contacts.update('id', { firstName: 'Jane' });
await send16.contacts.delete('id');Domains
await send16.domains.list();
await send16.domains.verify('domain-id');Render (compile dashboard email content → HTML)
const { data } = await send16.render.run({
content: emailContent, // EmailContent JSON from the Send16 editor
variables: { firstName: 'John' },
});
console.log(data.html); // exact HTML the API will sendEvents (trigger automations)
await send16.events.send({
name: 'signup_completed',
contact: '[email protected]',
payload: { plan: 'pro' },
});Error handling
The SDK never throws. Every method returns { data, error }:
const { data, error } = await send16.emails.send({ /* ... */ });
if (error) {
console.error(error.code); // "HTTP_422" | "TIMEOUT" | "NETWORK_ERROR" | ...
console.error(error.message);
return;
}
console.log(data.id); // non-null when error is nullTypeScript
Full types are bundled. Common imports:
import type {
SendEmailPayload,
SendEmailResponse,
Contact,
Domain,
ComposeInput,
ComposeResult,
} from 'send16-mail';Companion packages
send16-cli—send16command-line tool: send local.tsx/.mjml/.htmlfiles, manage domains/contacts, fire events.send16-editor— embeddable React block editor your users can compose emails in directly inside your app.
Links
- Dashboard — manage your account
- Create Account — get your free API key
- Documentation — API docs and guides
License
MIT
