Slack

Judge messages in the channels and DMs your Slack app is in, then reply, react or post.

Jev Events reads each new message in the channels your Slack app is in, asks Jev your questions about it, and runs what you pick on the answers. Choose below, and the code and the steps to run it fill in.

Where it runs

With your own Slack workspace, on your computer. The quickest way to try it.

What to ask
When Jev is at least
What to do
monitor.ts
import { monitor, recipes } from "jev-events";
import { slack } from "@jev-events/slack";

const team = monitor({
  // Also judges the 5 latest messages, so there's something to see right away.
  source: slack.messages({ backfill: 5 }),
  questions: {
    urgent: recipes.team.urgent,
  },
  // Native actions only log what they would do until you set dryRun: false.
  dryRun: true,
})
  .on("urgent", { min: 0.8 }, slack.post("#incidents"));

await team.start();
console.log("Watching for new messages. Stop with Ctrl-C.");

Run it

Make a folder for it, and install Jev Events and tsx, which runs TypeScript. Jev Events needs Node.js 22 or newer.

mkdir my-monitor && cd my-monitor
npm init -y && npm pkg set type=module
npm i jev-events @jev-events/slack tsx

Create your own Slack app and add it to your workspace: the command walks you through it. Then invite the app to a channel with /invite @jev_events.

npx jev-events auth slack

Put your TypeSafe API key in .env.

echo "TYPESAFE_API_KEY=<your key>" >> .env

Save the code as monitor.ts, and run it.

npx tsx --env-file=.env monitor.ts

Native actions log a [dry-run] would … line instead of running. When those look right, set dryRun: false.

Try it without code

To see what Jev says about your own workspace before writing anything, install the integration, set up the app once and watch. It prints an answer for the 5 latest messages, then for each new one:

npm i jev-events @jev-events/slack
npx jev-events auth slack
npx jev-events watch slack

watch slack:general reads one channel. By default it asks whether each message needs an answer, whether it's urgent, and what kind of message it is. Ask your own with --ask "bug=Is this reporting a bug?". See the CLI.

The first time, auth slack prints a link that creates the Slack app with everything filled in. Install it to your workspace, paste the two tokens it asks for, and invite the app to a channel with /invite @jev_events.

The source

slack.messages() emits each new message in the channels, private channels and DMs the app is in.

OptionDefaultWhat it does
channelsEvery conversation the app is inOnly these, by name ("general" or "#general") or ID
backfill0Also emits this many of the latest messages when starting
includeBotsfalseAlso judges messages from bots and integrations

Messages arrive in one of two ways, and the builder sets up the right one:

Socket ModeEvents API
UsedOn your machine, with auth slackIn your web app, for your users
NeedsThe app-level token (xapp-…)The signing secret, and your site at a public URL
Messages arriveOver a WebSocket the process keeps openAs a POST to /api/jev/webhook/slack

A web app can also use Socket Mode for every workspace that added your app, over one connection: pass slack.app({ appToken }) or set SLACK_APP_TOKEN, and run jev.start() in a process that stays up.

Actions

ActionWhat it does
slack.reply(text, { broadcast? })Replies in the message's thread, or straight into a DM. broadcast also posts the reply in the channel.
slack.react(emoji)Adds a reaction, such as slack.react("eyes").
slack.post(channel, text?)Posts in another channel. By default it says what fired and who wrote what where, with a link to the message.

text can be a string or a function of the event, such as (e) => `<@${e.item.author.id}> on it`, and Slack formatting such as *bold* works. When the app isn't in a channel or lacks a scope, the error says what to type or click to fix it. Actions are dry-run until you pass dryRun: false to monitor().

Messages

Handlers receive a SlackMessageItem:

FieldTypeNotes
textstringMentions shown as names, files as [file: name]
author{ id, name, guest, external, bot }external means another company, through Slack Connect
channel{ id, name, kind }kind is channel, private, dm or group-dm
inThreadbooleanA reply in a thread
mentionsYoubooleanIt @-mentions the app
mentionsEveryonebooleanIt uses @here, @channel or @everyone
permalinkstringA link to the message in Slack
files{ name, type? }[]

Jev sees the text and the author's name, plus facts: the channel, and whether it's in a thread, mentions the app or everyone, or comes from a guest, a bot or another company.

Good to know

Skipped before judging. Joins, edits, the app's own posts and other bots are never judged, so they cost nothing.

Slack wants an answer within 3 seconds. In a web app, the runtime answers Slack first and judges the message after, through waitUntil: after from next/server. Keep the route's maxDuration long enough to judge.

Retries. A message that fails for a reason that may pass, such as Slack being down, answers an error so Slack sends it again: 3 times over about 5 minutes. A workspace that removed the app answers OK and is marked needs-sign-in.

Keep token rotation off in your app's settings. The saved bot token has to keep working.

A server with one workspace of your own. slack.fromEnv() builds the connection from SLACK_BOT_TOKEN and SLACK_APP_TOKEN: await team.start({ connections: [slack.fromEnv()] }).

Your own manifest. manifestUrl({ name, requestUrl, redirectUrls }) makes the link that creates the app with everything filled in, and manifest() gives it as an object.

Edit this page on GitHub
NextTwitch →