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

sendheron

v0.2.0

Published

TypeScript SDK for the SendHeron email API: typed send outcomes, automatic retries, and idempotency by default.

Downloads

293

Readme

sendheron

TypeScript SDK for the SendHeron email API: typed send outcomes, automatic retries, and idempotency by default.

npm install sendheron

Quickstart

import { SendHeron } from 'sendheron';

const sendheron = new SendHeron(process.env.SENDHERON_API_KEY);

const { data, error } = await sendheron.emails.sendTemplate({
  to: '[email protected]',
  templateId: '550e8400-e29b-41d4-a716-446655440000',
  variables: { orderId: '42' },
});

if (error) throw error;
console.log(data.id, data.status, data.providerMessageId);

The one thing to know: a 201 is an outcome, not proof of dispatch

SendHeron records why mail did not go out instead of pretending it did. The SDK types that contract so you cannot misread it:

const { data, error } = await sendheron.emails.send({
  to: '[email protected]',
  subject: 'Your receipt',
  html: '<p>…</p>',
  attachments: [
    { content: base64Pdf, filename: 'receipt-42.pdf', type: 'application/pdf' },
  ],
});

if (error) {
  // 4xx: a request bug (error.code is a stable key). 429/5xx: the SDK
  // already retried per its policy before handing you this.
  throw error;
}

switch (data.status) {
  case 'sent':
    // Accepted by the provider. Persist data.id (readable back via
    // emails.get) and data.providerMessageId.
    break;
  case 'suppressed':
    // The compliance gate refused: data.errorMessage is a stable reason
    // (e.g. 'HARD_SUPPRESSED'). NEVER retry these; surface them.
    break;
}

SEND_BLOCK_REASONS exports every suppression reason as a typed list.

Retries and idempotency: on by default

  • 429 retries on every method (a rate-limited request was never processed), waiting per Retry-After. 5xx, timeouts and network failures retry with exponential backoff, but only on replay-safe requests: reads, and email sends carrying an idempotency key. Two retries by default; maxRetries: 0 disables (per call too, via options).

  • Every email send gets an idempotency key automatically and reuses it across the SDK's internal retries: a timeout can never double-send, even if you have never heard of the header. A concurrent-duplicate 409 requestInProgress is also retried until the server replays the original response. Pass your own key for business-level dedup across your retries:

    await sendheron.emails.sendTemplate(payload, { idempotencyKey: `receipt-${orderId}` });

    If the SDK exhausts its retries, the key it used is on error.idempotencyKey: resume the SAME logical send with it instead of minting a new one. Attachment bytes are excluded from the server's fingerprint, so a retried job that regenerated the same PDF replays cleanly.

  • API failures are returned, never thrown: every call resolves to { data, error }.

Resources

// Send
sendheron.emails.send(payload)              // raw HTML
sendheron.emails.sendTemplate(payload)      // templated; sendAt schedules it
sendheron.emails.sendBulk(payload)          // marketing blast to contacts
sendheron.emails.get(id)                    // read one send back: delivery
                                            // lifecycle, opens/clicks
sendheron.emails.cancelScheduled(id)

// Templates
sendheron.templates.list({ emailType: 'TRANSACTIONAL' })
sendheron.templates.create({ ..., emailType: 'TRANSACTIONAL' }) // type is required
sendheron.templates.update(id, { emailType: 'MARKETING', confirmEmailTypeChange: true })
sendheron.templates.preview({ bodyHtml, emailType, sampleData })
sendheron.templates.validate(document)

// Suppressions
sendheron.suppressions.check('[email protected]') // per-stream verdict, same
                                                 // gate the send paths run
sendheron.suppressions.list({ tier: 'HARD' })
sendheron.suppressions.add({ email, note })
sendheron.suppressions.remove(email, { confirmHardTier: true })

// Usage
sendheron.usage.get() // pool position + rate ceilings: monitor
                      // monthlySends.transactionalRemaining

Configuration

new SendHeron(apiKey, {
  baseUrl: 'https://api.sendheron.com', // default
  maxRetries: 2,                        // default; 0 disables retries
  timeout: 60_000,                      // per-attempt, in ms
});

The key falls back to the SENDHERON_API_KEY environment variable. Constructing without any key throws; nothing else ever does.

Coverage

v0 wraps the transactional surface: emails, templates, suppressions, usage. The remaining API resources (contacts, tags, sequences, sending domains, analytics) arrive as minor releases; until then they are one documented HTTP call away.

Requirements

Node.js >= 20 (native fetch, ES2022 output). CI exercises Node 22 and 24, the maintained lines. Zero runtime dependencies. Ships ESM and CJS with full type declarations.

Contract drift protection

spec/openapi.json is a snapshot of the live API contract. CI re-fetches the (public) live document and fails when they differ, so an API change becomes a failing build here: never a surprise in your integration.

License

MIT