npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sendhiiv

v0.1.2

Published

Official Node.js SDK for the Sendhiiv email API — send transactional email with one call.

Readme

Sendhiiv Node.js SDK

Official Node.js client for the Sendhiiv email API. Zero dependencies, works on Node 18+.

npm install sendhiiv

Quickstart

const { Sendhiiv } = require("sendhiiv");
// or: import { Sendhiiv } from "sendhiiv";

const sendhiiv = new Sendhiiv(process.env.SENDHIIV_API_KEY);

const result = await sendhiiv.messages.send({
  from: "Acme <[email protected]>",
  to: "[email protected]",
  subject: "Welcome aboard",
  html: "<p>Hi there, your account is ready.</p>",
});

console.log(result.message); // "1 email(s) queued for delivery"

Get an API key from your Sendhiiv dashboard under Settings → API. The free tier includes 3,000 emails/month. Keys look like sh_live_... — keep them in an environment variable, not in code.

What the package exports

The whole surface is two things:

| Export | What it's for | | --- | --- | | Sendhiiv | The client. Create one and reuse it for the life of your process. | | SendhiivError | Thrown for any non-2xx response, network failure, or timeout. |

The API currently has one endpoint (POST /messages), so sendhiiv.messages.send(params) is the only method you'll call. It returns a promise that resolves with the queue confirmation or rejects with a SendhiivError. When more endpoints ship they'll appear as new resources on the client (sendhiiv.domains, etc.).

Both CommonJS (require) and ES modules (import) work, and TypeScript definitions ship with the package — SendMessageParams, SendMessageResponse, SendhiivConfig, and Attachment are all exported types, so your editor autocompletes every field below.

The constructor takes either the key itself or a config object:

new Sendhiiv("sh_live_...");
new Sendhiiv({
  apiKey: "sh_live_...",
  timeoutMs: 30000, // per-request timeout (default 30s)
  maxRetries: 2,    // 429 retries only (default 2)
  baseUrl: "https://api.sendhiiv.com/api/v1", // default; override for testing
});

send() parameters, field by field

Fields are camelCase in JS; the SDK converts to the API's snake_case for you (sending snake_case directly also works, if you're porting raw-fetch code).

| Field | Required | Notes | | --- | --- | --- | | to | yes | A string, an array, or a comma-separated list. One request with 200 recipients is cheaper than 200 requests. | | subject | usually | Can only be omitted when a message template supplies its own subject. | | html | see below | HTML body. When combined with a layout template, this content is placed inside the layout. | | text | see below | Plain-text body. | | templateKey | see below | Key of a saved layout or message template, e.g. "brand-layout". Template keys are listed on the Templates page of the dashboard. | | from | no | Display sender, e.g. "Acme <[email protected]>". The domain must be verified in your account. Omit it to send from the shared sender. | | replyTo | no | Reply-To address. | | variables | no | Object of values for {{merge}} tags in the subject, body, or template. { firstName: "Ada" } fills {{firstName}}. | | attachments | no | Array of { filename, content, contentType } where content is base64. 10 MB total per message. | | sendMode | no | Set to "drip" to schedule recipients in batches instead of sending all at once. | | batchSize | no | Drip only. Recipients per batch, default 50, max 500. | | batchIntervalMinutes | no | Drip only. Minutes between batches, default 15, max 1440. |

The one rule to remember: every message needs to plus at least one of html, text, or templateKey. The rest is optional.

A fuller example:

await sendhiiv.messages.send({
  from: "Acme Billing <[email protected]>",
  to: ["[email protected]", "[email protected]"],
  subject: "March invoice",
  templateKey: "brand-layout",
  html: "<p>Your invoice is attached.</p>",
  variables: { firstName: "Ada" },
  replyTo: "[email protected]",
  attachments: [
    {
      filename: "invoice.pdf",
      content: pdfBuffer.toString("base64"),
      contentType: "application/pdf",
    },
  ],
});

Drip mode spreads large recipient lists into scheduled batches:

await sendhiiv.messages.send({
  from: "Acme <[email protected]>",
  to: recipients,
  subject: "Product update",
  html,
  sendMode: "drip",
  batchSize: 100,
  batchIntervalMinutes: 30,
});

A complete script from zero

mkdir email-demo && cd email-demo
npm init -y
npm install sendhiiv

send.js:

const { Sendhiiv, SendhiivError } = require("sendhiiv");

const sendhiiv = new Sendhiiv(process.env.SENDHIIV_API_KEY);

async function main() {
  try {
    const result = await sendhiiv.messages.send({
      to: "[email protected]",
      subject: "Hello from the SDK",
      html: "<p>It works.</p>",
    });
    console.log(`${result.status}: ${result.message} (${result.total} recipient(s))`);
  } catch (err) {
    if (err instanceof SendhiivError) {
      console.error(`Send failed (HTTP ${err.status}, code ${err.code}): ${err.message}`);
      process.exitCode = 1;
    } else {
      throw err;
    }
  }
}

main();
SENDHIIV_API_KEY=sh_live_... node send.js

Note there's no from in that example — without a verified domain the message goes out via the shared sender, which is fine for trying things out.

Using it in Express

Create the client once at startup, not inside handlers:

const express = require("express");
const { Sendhiiv, SendhiivError } = require("sendhiiv");

const app = express();
app.use(express.json());

const sendhiiv = new Sendhiiv(process.env.SENDHIIV_API_KEY);

app.post("/signup", async (req, res) => {
  const user = await createUser(req.body);

  try {
    await sendhiiv.messages.send({
      from: "Acme <[email protected]>",
      to: user.email,
      subject: "Welcome to Acme",
      templateKey: "welcome-email",
      variables: { firstName: user.firstName },
    });
  } catch (err) {
    // The account exists either way — log the email failure, don't fail signup.
    console.error("Welcome email failed:", err instanceof SendhiivError ? err.code : err);
  }

  res.status(201).json({ id: user.id });
});

What a successful send returns

The API queues messages and answers 202 Accepted. The resolved value:

{
  "success": true,
  "status": "queued",
  "code": "QUEUED_FOR_DELIVERY",
  "message": "1 email(s) queued for delivery",
  "total": 1,
  "retry": { "automatic": true, "retryable_temporary_failures": true }
}

total is the number of recipients queued; retry describes server-side behavior (Sendhiiv retries temporary delivery failures itself after queueing). Queued means accepted for delivery, not delivered — delivery status shows up in your dashboard's activity log.

Error handling

Every non-2xx response throws a SendhiivError. It carries:

| Property | Meaning | | --- | --- | | status | HTTP status code. 0 for network errors and timeouts. | | code | Machine-readable code such as "QUOTA_EXCEEDED", or null when the API didn't send one. | | compliance | { score, severity, reasons } — only set when code is "CONTENT_COMPLIANCE_BLOCKED". | | body | The full parsed error body, when the server returned JSON. Useful for logging. | | message | The API's error text, or a description of the network failure. |

const { SendhiivError } = require("sendhiiv");

try {
  await sendhiiv.messages.send({ to, subject, html });
} catch (err) {
  if (err instanceof SendhiivError) {
    switch (err.code) {
      case "CONTENT_COMPLIANCE_BLOCKED":
        console.error("Blocked:", err.compliance.reasons);
        break;
      case "ATTACHMENT_TOO_LARGE": // 413 — 10 MB total limit
      case "INVALID_ATTACHMENTS":  // 400
      default:
        console.error(`HTTP ${err.status}:`, err.message);
    }
  }
}

| Status | Meaning | | --- | --- | | 202 | Accepted — message(s) queued for delivery | | 400 | Invalid request (missing to, bad attachments, content blocked by compliance review — check code) | | 401 | Missing, invalid, or revoked API key | | 402 | Pay-as-you-go balance exhausted | | 403 | Plan does not include API access | | 413 | Attachments exceed 10 MB total | | 429 | Rate limit (100 requests/min) or plan quota reached |

Retries and timeouts

The SDK retries only HTTP 429 responses (honoring Retry-After), because the rate limiter runs before anything is queued — a retry can never double-send. Network errors and 5xx responses are not retried automatically, since the message may already have been accepted. Sendhiiv itself retries temporary delivery failures server-side after a message is queued.

const sendhiiv = new Sendhiiv({
  apiKey: process.env.SENDHIIV_API_KEY,
  timeoutMs: 30000, // per-request timeout (default 30s)
  maxRetries: 2,    // 429 retries (default 2)
});

License

MIT