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

@maildesk/sdk

v0.1.2

Published

Official TypeScript SDK for the Maildesk API

Downloads

10

Readme

@maildesk/sdk

Official TypeScript SDK for the Maildesk API. Typed, Node 18+.

Covers:

  • Contacts — list, get, create, bulk create (with partial-failure reporting), update, delete, auto-paginate
  • Tags — list, get, create, update, delete, auto-paginate
  • Webhooks — HMAC-SHA256 signature verification + typed event payloads

Install

npm install @maildesk/sdk
# or
yarn add @maildesk/sdk
# or
pnpm add @maildesk/sdk

Quick start

import { Maildesk } from "@maildesk/sdk";

const client = new Maildesk({
  apiKey: process.env.MAILDESK_API_KEY!,
  baseUrl: "https://api.maildesk.io",
});

const contact = await client.contacts.create({
  email: "[email protected]",
  firstName: "Jane",
  lastName: "Doe",
  isConfirmed: true,
  tags: ["01HQZTAG00000000000000"],
});

console.log(contact.id);

Authentication

All requests are authenticated with an API secret key sent as a Bearer token. Generate one from the Maildesk dashboard under Developer → API keys.

const client = new Maildesk({ apiKey: "sk_..." });

Configuration

| Option | Type | Default | Notes | | ----------- | ------ | -------------------------- | ------------------------------------------ | | apiKey | string | — | Required. Your Maildesk secret key. | | baseUrl | string | http://localhost:3000 | Point at your Maildesk deployment. | | timeoutMs | number | 30000 | Per-request timeout in milliseconds. |

Contacts

// List (paginated)
const { subscribers, total, page, limit } = await client.contacts.list({ page: 1, limit: 50 });

// Get one
const contact = await client.contacts.get("01ARZ3NDEKTSV4RRFFQ69G5FAV");

// Create
const created = await client.contacts.create({
  email: "[email protected]",
  firstName: "New",
  lastName: "Person",
  isConfirmed: true,
  tags: ["01HQZTAG00000000000000"],
});

// Update (partial)
const updated = await client.contacts.update(created.id, { firstName: "Renamed" });

// Delete
await client.contacts.delete(created.id);

// Auto-paginate every contact
for await (const c of client.contacts.listAll({ limit: 100 })) {
  console.log(c.email);
}

Bulk create — partial failures

bulkCreate accepts up to 100 contacts per call and returns a failed array with per-item reasons. A fully successful batch returns { failed: [] }.

import { BulkContactFailureReason } from "@maildesk/sdk";

const { failed } = await client.contacts.bulkCreate([
  { email: "[email protected]", firstName: "A", lastName: "Z" },
  { email: "[email protected]", firstName: "D", lastName: "U" },
]);

for (const f of failed) {
  if (f.reason === BulkContactFailureReason.EMAIL_ALREADY_EXISTS) {
    // already in the database — ignore or re-sync
  } else if (f.reason === BulkContactFailureReason.DUPLICATE_IN_REQUEST) {
    // two rows in the same batch had the same email
  } else {
    // INTERNAL_ERROR — retry
  }
}

Tags

const { tags } = await client.tags.list({ page: 1, limit: 50 });

const tag = await client.tags.create({ name: "Newsletter", description: "Weekly list" });
await client.tags.update(tag.id, { description: "Updated" });
await client.tags.delete(tag.id);

for await (const t of client.tags.listAll()) {
  console.log(t.name);
}

Tag names are unique per business — attempting to create a duplicate throws ConflictError.

Webhooks

Maildesk signs every outbound webhook with HMAC-SHA256 using your API secret. The SDK ships a verifyWebhook helper that validates the signature, enforces a timestamp tolerance, and returns a typed event.

Event types:

  • subscriber.created
  • subscriber.confirmed
  • subscriber.unsubscribed
  • subscriber.updated

Express

import express from "express";
import { verifyWebhook, InvalidSignatureError, StaleWebhookError } from "@maildesk/sdk";

const app = express();

// IMPORTANT: use a raw-body parser for this route — signature verification
// is computed over the exact bytes on the wire, not a re-serialized JSON string.
app.post(
  "/webhooks/maildesk",
  express.raw({ type: "application/json" }),
  (req, res) => {
    try {
      const event = verifyWebhook({
        rawBody: req.body, // Buffer
        signatureHeader: req.header("X-Maildesk-Signature"),
        secret: process.env.MAILDESK_WEBHOOK_SECRET!,
      });

      switch (event.type) {
        case "subscriber.created":
          // ...
          break;
        case "subscriber.unsubscribed":
          // ...
          break;
      }

      res.status(200).send("ok");
    } catch (err) {
      if (err instanceof InvalidSignatureError || err instanceof StaleWebhookError) {
        res.status(400).send(err.message);
        return;
      }
      throw err;
    }
  },
);

Options

verifyWebhook({
  rawBody,          // string | Buffer — raw request body
  signatureHeader,  // value of X-Maildesk-Signature header
  secret,           // your API secret key
  toleranceSeconds: 300, // default — reject timestamps older than 5 minutes. 0 disables.
});

Idempotency

Every event has a unique eventId. Retries (e.g. on 5xx responses from your endpoint) reuse the same eventId, so persist it and skip duplicates:

const event = verifyWebhook({ ... });
if (await seenEventIds.has(event.eventId)) return;
await seenEventIds.add(event.eventId);
// process event

Error handling

All errors extend MaildeskError and carry statusCode, body, and an optional requestId.

| Error | Trigger | | ---------------------- | -------------------------------------------- | | AuthenticationError | 401 — missing/invalid API key | | NotFoundError | 404 — resource not found | | ConflictError | 409 — unique-constraint violation | | ValidationError | 400 / 422 — request body or query invalid | | RateLimitError | 429 — includes .retryAfter (seconds) | | ServerError | 5xx | | NetworkError | transport-level failure (DNS, timeout, etc.) | | InvalidSignatureError| webhook signature didn't verify | | StaleWebhookError | webhook timestamp outside tolerance |

import { RateLimitError } from "@maildesk/sdk";

try {
  await client.contacts.create({ ... });
} catch (err) {
  if (err instanceof RateLimitError && err.retryAfter) {
    await new Promise(r => setTimeout(r, err.retryAfter * 1000));
    // retry
  }
  throw err;
}

The server enforces 120 requests per 60 seconds per API key. Back off on 429.

Pagination

List endpoints default to page=1, limit=50. Use listAll() for an async iterator that walks every page:

for await (const contact of client.contacts.listAll()) {
  // ...
}

Development

npm install
npm run build     # bundles CJS + ESM + .d.ts into dist/
npm test          # runs jest with nock-mocked HTTP
npm run typecheck # tsc --noEmit

License

MIT