---
title: "Add Slack"
description: "Put the dispatcher in Slack with slackChannel and Vercel Connect, no SLACK_BOT_TOKEN, no tool changes. One more front door on the same agent."
canonical_url: "https://vercel.com/academy/building-agents-with-eve/add-slack"
md_url: "https://vercel.com/academy/building-agents-with-eve/add-slack.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-08-29T02:28:26.233Z"
content_type: "lesson"
course: "building-agents-with-eve"
course_title: "Building Agents with eve"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Add Slack

# Add Slack

The shop's mechanics live in Slack all day. Making them open a browser tab to ask the dispatcher anything seems rude. So let's meet them where they already are.

Adding Slack should leave the tools, skill, state, and approval logic untouched. A channel normalizes incoming messages, tracks how to resume the conversation, and sends replies. One file in `channels/` makes Slack another client of the dispatcher.

Credentials are the new concern here. Vercel Connect manages them, so your code never handles a `SLACK_BOT_TOKEN`.

## Outcome

The dispatcher answers `@mentions` in Slack, in threads, with no changes to any tool, skill, or state.

## Hands-on exercise

**Add the Slack channel.** Slack delivers events to a public URL and needs a verified bot token to reply. Vercel Connect brokers both, so there's no signing secret or bot token in your code. eve 0.41.0 guides the whole setup:

```bash
npx eve add channel/slack
```

The command links or creates a Vercel project, creates or reuses a Slack connector, opens Slack authorization in your browser, registers `/eve/v1/slack` as the trigger destination, installs `@vercel/connect`, and writes `agent/channels/slack.ts`. Follow the prompts and finish the workspace authorization in the browser.

\*\*Warning: Use the current command shape\*\*

The old `eve channels add slack` command was removed. The registry form is `eve add channel/slack`. Let the guided setup reuse an existing connector when it finds one; repeatedly creating connectors can leave duplicate Slack apps in the workspace.

\*\*Warning: Re-running \`create\` installs a new Slack app each time\*\*

Every `vercel connect create slack` installs a *fresh* Slack app into your workspace, and `vercel connect remove` deletes the connector on Vercel's side but does **not** uninstall that app from Slack. So if you recreate the connector a few times while debugging, you'll end up with several identical bots in the `@`-mention list. Before re-creating, uninstall the stale ones in Slack under **Manage apps** (`https://app.slack.com/manage`), so you're only ever mentioning the live one.

\*\*Note: Connect is a beta on all plans\*\*

`vercel connect` ships with the Vercel CLI, no feature flag needed. If your CLI reports `connect` as an unknown subcommand, update it (`npm i -g vercel@latest`) and retry; the command surface is documented under [vercel connect](https://vercel.com/docs/cli/connect).

**Inspect and extend the channel.** The generated `agent/channels/slack.ts` defines where credentials come from, when to dispatch a turn, and how to deliver the reply. Replace it with the version in the solution below to make the dispatch and final-thread delivery explicit.

- **Credentials:** `connectSlackCredentials(process.env.SLACK_CONNECTOR ?? "slack/spoke-and-mirror")` returns the bot token and webhook verifier, both managed by Connect. Reading the uid from `SLACK_CONNECTOR` keeps it out of code; the fallback matches the connector you just named, so it works without setting the variable.
- **Dispatch:** `onAppMention` decides whether a mention becomes a turn. Use `defaultSlackAuth` to stamp trusted Slack identity and ignore bot chatter.
- **Delivery:** on `message.completed`, post the final reply to the thread, skipping interim tool-call narration.

\*\*Note: The thread maps to the session\*\*

You don't manage Slack threading by hand. The channel maps a thread to a durable session, so a follow-up mention in the same thread resumes the conversation, just as posting to the session ID did in 1.3.

\*\*Note: Slack identity is not a shop membership tier\*\*

`defaultSlackAuth` stamps Slack attributes such as user, team, channel, and thread IDs. It does not know the customer's Spoke & Mirror membership, so the dynamic playbook uses the plain desk on Slack. A production extension can map the trusted Slack user ID to a server-side customer record and add `attributes.tier`; never derive the tier from message text.

Because Slack delivers over the public internet, you can't exercise this one on `localhost`. You'll deploy to get a URL. We cover deployment properly in Section 5; for now, ship it with `npx eve deploy`, which wraps `vercel deploy --prod`, installs dependencies, and pulls your environment:

```bash
npx eve deploy
```

## Try It

In a Slack workspace where the app is installed, mention the bot in a channel:

```text
@dispatcher my commuter's front brake is rubbing, what's that cost to fix?
```

The bot replies in a thread, runs `lookup_service`, and quotes the catalog price. A reply in the thread continues the session. The approval gate also carries over: ask it to book the Full Overhaul and Slack renders the approve/deny prompt as buttons.

Bot shows up in Slack but never replies? Re-run `npx eve add channel/slack`; it inspects the current project, connector, Slack installation, and trigger destination without blindly creating another app. Confirm the destination is `/eve/v1/slack`. By default, the channel gives the model the triggering mention rather than the earlier thread backlog. Opt into thread context if you need that history.

## Done-When

- [ ] A Connect Slack client is attached with trigger path `/eve/v1/slack`.
- [ ] `agent/channels/slack.ts` exports `slackChannel` with `connectSlackCredentials`.
- [ ] `@mentioning` the bot returns a real, tool-backed answer in a thread.
- [ ] An expensive booking renders approve/deny as Slack buttons.

## Solution

```ts title="agent/channels/slack.ts"
import { connectSlackCredentials } from "@vercel/connect/eve";
import { defaultSlackAuth, slackChannel } from "eve/channels/slack";

export default slackChannel({
  // The connector uid lives in SLACK_CONNECTOR (set it on the project, or leave
  // it unset). The fallback matches the connector you named with `vercel connect
  // create slack --name spoke-and-mirror`, so this works out of the box.
  credentials: connectSlackCredentials(
    process.env.SLACK_CONNECTOR ?? "slack/spoke-and-mirror",
  ),

  // Answer @mentions from a real user; ignore bot chatter. defaultSlackAuth
  // stamps Slack identity, but does not invent a shop membership tier.
  onAppMention: (ctx, message) =>
    message.author ? { auth: defaultSlackAuth(message, ctx) } : null,

  events: {
    // Post the final reply to the thread, skipping interim tool-call narration.
    // Event handlers receive (eventData, channel, ctx); Slack handles live on `channel`.
    "message.completed"(eventData, channel, ctx) {
      if (eventData.finishReason === "tool-calls") return;
      if (eventData.message) channel.thread.post(eventData.message);
    },
  },
});
```

The same agent now has web and Slack entrypoints. Before shipping, we'll replace the test identity with authenticated customer data.


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
