Node.js

The honk-me package is a TypeScript client with no runtime dependencies, for Node 18 and later, Bun and Deno. Use it in back-end code, functions and scripts.

Package
honk-me Published
Source
github.com/honk-me/honk-node · MIT License
Requirements
Node 18 or later (global fetch), Bun or Deno.

Install

Then create a project and an ingestion key in the Honk web app.

Terminal
npm install honk-me

Anyone with an ingestion key (honk_…) can post to its project. Keep it on servers, in jobs and in CI secrets, never in a browser, mobile or desktop app.

Send an event

Honk.fromEnv() reads HONK_URL, HONK_KEY and the optional HONK_SOURCE, HONK_ENVIRONMENT and HONK_CHANNEL defaults. In CommonJS, require('honk-me') works too.

import { Honk } from 'honk-me';

const honk = new Honk({ url: process.env.HONK_URL, key: process.env.HONK_KEY });
await honk.beep('Backup finished', 'nightly pg_dump took 42 s');

Recipe: a customer request on your phone

One group per request and a stable idempotency key: two customers never fold into one notification, and a retried webhook never buzzes twice. The promise is not awaited, so a slow network never slows the customer’s request.

import { Honk } from 'honk-me';

const honk = Honk.fromEnv(); // create once, reuse (keep-alive)

export async function onCustomerRequest(req) {
  // ...save the request first, then notify without blocking the response:
  honk
    .send(
      {
        title: `New request: ${req.subject}`.slice(0, 160),
        message: `${req.name} (${req.company}) asked: ${req.body}`.slice(0, 2000),
        priority: 'high', // push right away
        category: 'customers',
        channel: 'requests',
        groupKey: `requests/${req.id}`, // one group per request
        url: `https://shop.example.com/admin/requests/${req.id}`, // https only, shown as "Open link"
        metadata: { request_id: String(req.id) },
      },
      { idempotencyKey: `request-${req.id}` }, // same request, same key: never a duplicate
    )
    .catch((err) => console.warn('honk:', err.message)); // never fail the customer's request
}

The Honk scale and incidents

One helper per level. problem and recovery need a group key; the recovery closes the episode the problem opened.

await honk.loud('Disk 91%', '/var on app-01', { groupKey: 'disk/app-01/var' });
await honk.light('Deploy started', 'v4.2.0 to production', { channel: 'deploys' });
await honk.problem('db/backup', 'Backup failed', 'pg_dump exited with 1');      // a long honk by default
await honk.recovery('db/backup', 'Backup OK', 'pg_dump finished in 41 s');      // a beep by default

Errors

Every error is a HonkError with a kind and retryable. Validation errors are bugs; retryable ones can go back to your queue with the same key.

import { HonkError, HonkValidationError } from 'honk-me';

try {
  await honk.send(msg, { idempotencyKey: `order-${order.id}-failed` });
} catch (err) {
  if (err instanceof HonkValidationError) logger.error(err.fields);   // a bug: don't retry
  else if (err instanceof HonkError && err.retryable) queue.retryLater(err.idempotencyKey, err.retryAfter);
  else throw err;
}

Bun and Deno

Bun: bun add honk-me and use it as in Node. Deno imports it from npm:

import { Honk } from 'npm:honk-me'; // Deno
const honk = new Honk({ url: Deno.env.get('HONK_URL')!, key: Deno.env.get('HONK_KEY')! });

Retries that never send twice

  • Every send carries an Idempotency-Key: yours, or a fresh UUIDv7. The same key is reused on every retry, and within 24 hours Honk answers a replay with the original id and duplicate: true, so a lost response never turns into a second message.
  • Only network errors, timeouts, 429 and 5xx are retried, with exponential backoff and full jitter, never sooner than the server’s Retry-After. Other 4xx responses are never retried; fix the request instead.
  • Each attempt times out after 5 seconds and everything stops after 30. A wait that would cross that deadline, like a daily quota that resets at midnight, fails right away and tells you when to retry.
  • Fields are validated before sending, and all invalid fields are reported together.