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

@hameddk/slack-user-client

v0.1.0

Published

Slack Web API client for user-token (xoxp-) flows. Send DMs as a real user, look up users by email, open conversations, paginate cursors. Pluggable auth via async token resolver, configurable rate-limit retry. Zero dependencies.

Readme

@hameddk/slack-user-client

Slack Web API client for user-token (xoxp-) flows.

  • Send DMs as a real user, look up users by email, open conversations
  • Pluggable auth via async token resolver (BYO OAuth library)
  • Cursor pagination helper
  • Rate-limit auto-retry honoring Retry-After
  • Explicit ok: false handling — Slack returns HTTP 200 for app errors
  • Zero dependencies, ESM, Node ≥ 18

Status: 0.1.0 — early. Public API is stable for the documented surface.

Why a user-token client (not a bot client)?

Slack has two primary token flavors:

  • Bot tokens (xoxb-) — message appears as your bot. Plenty of mature libraries handle these.
  • User tokens (xoxp-) — message appears as a real user. Useful when you want notifications to land in chat as if a human sent them. Fewer libraries are tuned for this; this package is.

This package is API-only — it does not handle OAuth. Pair it with an OAuth library such as @hameddk/oauth-toolkit to obtain and refresh the user token.

Install

npm install @hameddk/slack-user-client

Quick start

import { createSlackUserClient } from '@hameddk/slack-user-client';

const client = createSlackUserClient({
  // Async token resolver. Re-evaluated on every request.
  getAccessToken: async () => loadUserTokenFromYourStore(),
  options: {
    autoRetryRateLimit: { maxRetries: 3, maxDelayMs: 60_000 },
    onRateLimit: (sec, attempt) =>
      console.warn(`Slack rate limited; sleeping ${sec}s (attempt ${attempt})`),
  },
});

// Verify the token is valid.
const me = await client.testAuth();          // → { ok, user_id, team, url, ... }

// Find a user by email.
const lookup = await client.lookupUserByEmail({ email: '[email protected]' });
const slackUserId = lookup.user.id;          // 'U1234567890'

// Open a DM channel.
const open = await client.openConversation({ users: slackUserId });
const channelId = open.channel.id;           // 'D098...'

// Post the message.
await client.postMessage({
  channel: channelId,
  text: 'Heads up — please review JIRA-1234.',
});

Public API

const client = createSlackUserClient(opts);

await client.testAuth();
await client.lookupUserByEmail({ email });
await client.openConversation({ users });            // single ID or comma-list / array
await client.postMessage(params);                    // full chat.postMessage params
await client.callMethod(method, params, { httpMethod });
await client.paginateAll(method, params, { itemsKey, pageLen, maxTotal });

Generic callMethod

The Slack Web API has hundreds of methods. The four built-in convenience methods cover the user-token DM flow; for everything else use callMethod:

const conv = await client.callMethod('conversations.info', { channel: 'D123' });
const list = await client.callMethod('users.list', { limit: 200 }, { httpMethod: 'GET' });

Choosing httpMethod

callMethod defaults to POST with application/x-www-form-urlencoded — this is what Slack recommends for the majority of methods, including all write operations.

Use { httpMethod: 'GET' } for the small set of read-only methods that expect query-string parameters. As of writing, the most common ones are auth.test and users.lookupByEmail (which is why the built-in convenience methods for those use GET internally).

This is guidance, not a hardcoded whitelist — Slack's API may evolve and new GET endpoints may appear. Check the Slack method docs if in doubt; endpoints that fail with HTTP 405 or not_allowed_token_type when you POST are typical candidates for GET.

Cursor pagination

Slack uses cursor + response_metadata.next_cursor. The paginateAll helper drives the loop and returns a flat array:

const allUsers = await client.paginateAll(
  'users.list',
  { limit: 200 },
  { itemsKey: 'members', maxTotal: 5000 }
);

itemsKey is required because Slack uses different field names per method (members for users.list, channels for conversations.list, etc.).

Errors

import {
  SlackError,           // base
  SlackConfigError,     // missing config
  SlackAuthError,       // not_authed, invalid_auth, token_revoked, ...
  SlackRateLimitError,  // 429 after retries exhausted
  SlackApiError,        // every other ok:false (slackError preserved)
  SlackRateLimitError,  // 429 (or ok:false ratelimited) after retries exhausted
} from '@hameddk/slack-user-client';

try {
  await client.openConversation({ users: 'U123' });
} catch (err) {
  if (err instanceof SlackAuthError) {
    // Token revoked, expired, or missing scope. Send the user through re-auth.
    showReconnectBanner(err.slackError);
  } else if (err instanceof SlackApiError && err.slackError === 'channel_not_found') {
    // Channel was deleted, archived, or never existed.
    showChannelMissingMessage();
  } else if (err instanceof SlackApiError && err.slackError === 'users_not_found') {
    // No Slack user matches that ID/email.
    showUserMissingMessage();
  } else if (err instanceof SlackRateLimitError) {
    // Auto-retry already exhausted; back off and try later.
    scheduleRetry(err.retryAfter);
  } else {
    throw err;
  }
}

SlackApiError.slackError carries the original Slack error code (e.g. users_not_found, channel_not_found, is_archived). The toolkit does not translate these to user-facing strings — that's caller territory.

Rate limiting

Slack returns HTTP 429 with a Retry-After header. The client auto-retries:

options: {
  autoRetryRateLimit: true,                                  // default
  autoRetryRateLimit: { maxRetries: 3, maxDelayMs: 60_000 },
  autoRetryRateLimit: false,                                 // disable
}

After exhausting retries (or when Retry-After exceeds maxDelayMs), the client throws SlackRateLimitError with retryAfter (seconds).

Testing hooks

For testing only:

options: {
  fetch: customFetch,        // override fetch implementation
  sleep: async (ms) => {},   // skip real sleeps in tests
}

What this library does not do

  • Doesn't handle OAuth — pair with @hameddk/oauth-toolkit.
  • Doesn't handle bot tokens (xoxb-) — many mature libraries cover that.
  • Doesn't handle Slack Events API or Webhooks.
  • Doesn't translate Slack error codes to UI strings — caller's responsibility.
  • Doesn't persist tokens — your token resolver does.

License

MIT © 2026 Hamed Sattari