Skip to content

hameddk/slack-user-client

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@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: 'alice@example.com' });
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

About

Slack Web API client for user-token (xoxp) flows. Lookup users, open conversations, post messages, with explicit error classification, rate-limit retry, and cursor-based pagination. Zero dependencies.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors