Slack Developer Kit for Node.js
Go to GitHub

RTM API client

This package includes a RTM client that makes it simple to use the Slack RTM API. Here are some of the goodies you get right out of the box:

  • Event emission - use it like any other Node EventEmitter
  • Message queuing and rate-limit management
  • Ping/Pong management
  • Automatic reconnection
  • Proxy support
  • Message serialization
  • Supports sending typing, message, and presence_sub events.
  • Keeps track of your token
  • Error handling
  • Logging
  • Configurability

An RTM client allows your app to listen for incoming events as well as send outgoing events. It’s a quick solution for many apps – especially for those that may only be about to create outgoing connections due to firewall restrictions, or only ever connect to one workspace.

But the RTM client does not have access to the full power of the Slack platform, including message attachments, buttons, menus, and dialogs. It also does not scale to many instances because it is stateful, and does not scale well for apps that are connected to many workspaces because of Shared Channels. The most robust app building solution is through a combination of the Web API and the Events API.

Here are some of the common recipies for using the RtmClient class.


Initializing and connecting

Your app will interact with the RTM API through the RtmClient object, which is a top level export from this package. At a minimum, you need to instantiate it with a token, usually a bot token.

const { RtmClient, CLIENT_EVENTS } = require('@slack/client');

// An access token (from your Slack app or custom integration - usually xoxb)
const token = process.env.SLACK_TOKEN;

// Cache of data
const appData = {};

// Initialize the RTM client with the recommended settings. Using the defaults for these
// settings is deprecated.
const rtm = new RtmClient(token, {
  dataStore: false,
  useRtmConnect: true,
});

// The client will emit an RTM.AUTHENTICATED event on when the connection data is avaiable
// (before the connection is open)
rtm.on(CLIENT_EVENTS.RTM.AUTHENTICATED, (connectData) => {
  // Cache the data necessary for this app in memory
  appData.selfId = connectData.self.id;
  console.log(`Logged in as ${appData.selfId} of team ${connectData.team.id}`);
});

// The client will emit an RTM.RTM_CONNECTION_OPEN the connection is ready for
// sending and recieving messages
rtm.on(CLIENT_EVENTS.RTM.RTM_CONNECTION_OPEN, () => {
  console.log(`Ready`);
});

// Start the connecting process
rtm.start();

Sending a message

Of course, just starting a bot running doesn’t accomplish much! It would be nice to be able to send messages, right? The RTM client has a method called sendMessage(text, channelId) that will do just that.

The example below shows an example where the RtmClient and WebClient are used together. The web API method channels.list is used to get a current list of all channels and can be iterated over to find one where the bot is a member. Once we have a channel ID, its as easy as calling rtm.sendMessage().

const { RtmClient, CLIENT_EVENTS, WebClient } = require('@slack/client');

const token = process.env.SLACK_TOKEN;

const rtm = new RtmClient(token, {
  dataStore: false,
  useRtmConnect: true,
});
// Need a web client to find a channel where the app can post a message
const web = new WebClient(token);

// Load the current channels list asynchrously
let channelListPromise = web.channels.list();

rtm.on(CLIENT_EVENTS.RTM.RTM_CONNECTION_OPEN, () => {
  console.log(`Ready`);
  // Wait for the channels list response
  channelsListPromise.then((res) => {

    // Take any channel for which the bot is a member
    const channel = res.channels.find(c => c.is_member);

    if (channel) {
      // We now have a channel ID to post a message in!
      // use the `sendMessage()` method to send a simple string to a channel using the channel ID
      rtm.sendMessage('Hello, world!', channel.id)
        // Returns a promise that resolves when the message is sent
        .then(() => console.log(`Message sent to channel ${channel.name}`))
        .catch(console.error);
    } else {
      console.log('This bot does not belong to any channels, invite it to at least one and try again');
    }
  });
});

// Start the connecting process
rtm.start();

Receiving messages

The client will emit message events whenever a new message arrives in a channel in which your bot has access. The client can handle messages using the on(eventType, event) method.

const { RtmClient, CLIENT_EVENTS, RTM_EVENTS, WebClient } = require('@slack/client');
// SNIP: the initialization code shown above is skipped for brevity

rtm.on(RTM_EVENTS.MESSAGE, (message) => {
  // For structure of `message`, see https://api.slack.com/events/message

  // Skip messages that are from a bot or my own user ID
  if ( (message.subtype && message.subtype === 'bot_message') ||
       (!message.subtype && message.user === appData.selfId) ) {
    return;
  }

  // Log the message
  console.log('New message: ', message);
});

Listening for message subtypes

There’s a shortcut syntax for listening to specific message subtypes, just format the event type as message::${message_subtype}. See the example below.

const { RtmClient, CLIENT_EVENTS, RTM_EVENTS, RTM_MESSAGE_SUBTYPES, WebClient } = require('@slack/client');
// SNIP: the initialization code shown above is skipped for brevity

rtm.on(`${RTM_EVENTS.MESSAGE}::${RTM_MESSAGE_SUBTYPES.CHANNEL_NAME}`, (message) => {
  // For structure of `message`, see https://api.slack.com/events/message/channel_name
  console.log(`A channel was renamed from ${message.old_name} to ${message.name}`);
});

If you rename a channel in which the bot is a member, you should see the handler get triggered.


Handling other events

Anything that happens in a Slack workspace, and that is visible to the bot (i.e. happens in a channel to which the bot belongs) is communicated as an event as well. For a complete list of other events, see https://api.slack.com/rtm#events. The event type values are available in a dictionary in a top level export called RTM_EVENTS. The naming follows a convention of RTM_EVENTS.EVENT_NAME_UPPERCASED. For example, we can listen for emoji reactions being added to messages by electing to be notified on RTM_EVENTS.REACTION_ADDED:

const { RtmClient, CLIENT_EVENTS, RTM_EVENTS, RTM_MESSAGE_SUBTYPES, WebClient } = require('@slack/client');
// SNIP: the initialization code shown above is skipped for brevity

rtm.on(RTM_EVENTS.REACTION_ADDED, (event) => {
  // For structure of `event`, see https://api.slack.com/events/reaction_added
  console.log(`User ${event.user} reacted with ${event.reaction}`);
});

Data stores

The SlackDataStore (and its implementaion SlackMemoryDataStore) has been deprecated since v3.15.0. Initializing the RtmClient with the option dataStore: false disables the data store, and that is recommended.

The guide has been removed, but the reference documentation in the source is still available.

The maintainers highly recommend preparing your app to migrate if your app is using the data store.


Workspace cache on connect

The useRtmConnect option on RtmClient initialization can give you control of how much workspace state you recieve when you connect and recieve the CLIENT_EVENTS.RTM.AUTHENTICATED event.

If you set the value to false (not recommended for large teams), you’ll recieve a cache the relevent client state for your app. See the response in the rtm.start reference documentation for a description of the workspace cache.

This should be treated as a cache because this information can go stale quickly if you aren’t listening for every event to update the state. The maintainers recommend instead storing only the state your app needs to operate in a variable, and then updating that variable in each event that might mutate that state. A simpler alternative is to fetch the state you need from the Web API whenever you need it.


Changing the retry configuration

The RtmClient will retry sending any message that fails for a recoverable error. The policy is configurable, but the default is retrying forever with an exponential backoff, capped at thirty minutes but with some randomization. You can use the retryConfig option to customize that policy. The value is an options object as described in the following library: https://github.com/tim-kos/node-retry.

const { RtmClient } = require('@slack/client');
const token = process.env.SLACK_TOKEN;
const rtm = new RtmClient(token, {
  retryConfig: {
    // This would turn the retrying feature off
    retries: 0,
  },
});

Customizing the logger

The RtmClient also logs interesting events as it operates. By default, the log level is set to INFO and it should not log anything as long as nothing goes wrong. You can adjust the log level using the logLevel option and use any of the npm log levels.

You can also capture the logs without writing them to stdout by setting the logger option. It should be set to a function that takes fn(level: string, message: string).

const fs = require('fs');
const { RtmClient } = require('@slack/client');

// open a file to write logs
// TODO: make sure to call `logStream.end()` when the app is shutting down
const logStream = fs.createWriteStream('/tmp/app.log');

const token = process.env.SLACK_TOKEN;
logStream.on('open', () => {
  const rtm = new RtmClient(token, {
    // increased logging, great for debugging
    logLevel: 'debug',
    logger: (level, message) => {
      // write to disk
      logStream.write(`[${level}]: ${message}`);
    }
  });
});

Proxy settings

If you need to send all your HTTP requests through a proxy, the RtmClient class allows for you to do this with the transport option.

const { RtmClient } = require('@slack/client');
const { proxiedRequestTransport } = require('@slack/client/lib/clients/transports/request');
const { factory: wsTransportFactory } = require('@slack/client/lib/clients/transports/ws');

const wsTransport = factory(console.log.bind(console));
const rtm = new RtmClient(token, {
  transport: proxiedRequestTransport('your proxy url'),
  socketFn: function(socketURL) {
    return wsTransport(socketURL, { proxyURL: 'your proxy url' });
  }
});