Jev Events

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.

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

What to watch

New messages. Messages in the channels and DMs the app is in. Invite it with /invite @your-app.

A message it would read
Priya · #deploys
Checkout is returning 500s since the 14:05 deploy, can someone roll back?

What to ask

Jev answers every question about each message, in one request.

What to do

Each rule runs when an answer is likely enough. Leave the rules out to print every answer.

Posts what fired, who wrote the message and where, with a link to it.

Reacts to the message with an emoji, by its name without colons.

The code

monitor.ts
import { monitor, recipes } from "jev-events";import { slack } from "@jev-events/slack";const team = monitor({  // Also judges the 5 latest messages each time it starts, so there's something to see right away.  source: slack.messages({ backfill: 5 }),  questions: {    urgent: recipes.team.urgent,    needsAnswer: recipes.team.needsAnswer,  },  // Native actions only log what they would do. Set dryRun: false once the answers look right.  dryRun: true,})  .on("urgent", { min: 0.9 }, slack.post("#incidents"))  .on("needsAnswer", { min: 0.8 }, slack.react("eyes"));await team.start();console.log("Watching for new messages. Stop with Ctrl-C.");

Run it

  1. 1

    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-monitornpm init -y && npm pkg set type=modulenpm i jev-events @jev-events/slack tsx
  2. 2

    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
  3. 3

    Put your TypeSafe API key in .env.

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

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

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

    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.

On this page