JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 8
  • Score
    100M100P100Q81664F
  • License MIT

Node.js SDK for the Send16 email platform. Send transactional + marketing email from any stack. Get started: npx create-send16

Package Exports

  • send16-mail
  • send16-mail/openapi

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-send16

Detects 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
  1. Create a free account — 1,000 emails/month free.
  2. Open the dashboard → Developers → API KeysCreate key.
  3. Set SEND16_API_KEY in 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 <hello@yourdomain.com>',
  to: ['user@gmail.com'],
  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 <ops@yourdomain.com>',
  to: ['oncall@acme.com', 'backup-oncall@acme.com'],
  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 <noreply@yourdomain.com>',
  to: 'firm@example.com',
  cc: ['archive+leads@yourdomain.com'],
  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 <billing@yourdomain.com>',
  to: 'customer@example.com',
  cc: ['accounts@example.com'],
  bcc: 'archive@yourdomain.com',
  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 <hello@acme.com>',
  to: 'user@gmail.com',
  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 fetch exists
  • 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 <hello@acme.com>',
  to: ['user@gmail.com'],
  subject: 'Hello',
  html: '<p>Hello world</p>',
});

All options

await send16.emails.send({
  from: 'Acme <hello@acme.com>',
  to: ['user@gmail.com'],
  cc: ['cc@example.com'],
  bcc: ['bcc@example.com'],
  replyTo: 'support@acme.com',
  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 <hi@acme.com>', to: ['a@x.com'], subject: 'Hi A', html: '<p>Hi!</p>' },
  { from: 'Acme <hi@acme.com>', to: ['b@x.com'], subject: 'Hi B', html: '<p>Hi!</p>' },
]);

Contacts

await send16.contacts.create({ email: 'user@test.com', 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 send

Events (trigger automations)

await send16.events.send({
  name: 'signup_completed',
  contact: 'user@gmail.com',
  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 null

TypeScript

Full types are bundled. Common imports:

import type {
  SendEmailPayload,
  SendEmailResponse,
  Contact,
  Domain,
  ComposeInput,
  ComposeResult,
} from 'send16-mail';

Companion packages

  • send16-clisend16 command-line tool: send local .tsx/.mjml/.html files, manage domains/contacts, fire events.
  • send16-editor — embeddable React block editor your users can compose emails in directly inside your app.

License

MIT