# Slack AI bot (Agent Skill)

> A two-way Slack AI bot inside a Next.js app: mentions, DMs and thread follow-ups answered by a model with tools scoped to the asking user, scheduled reports, and Approve and Cancel buttons that run a side effect exactly once.

Page: https://timerise.ai/skills/slack-ai-bot

Agent Skill `slack-ai-bot`: https://github.com/timerise-ai/slack-ai-bot. Category: AI & integrations. Runs on Next.js; backend: Backend-agnostic `BotHost` seam, Postgres/Supabase and in-memory stores.

Latest release: v0.1.5 (2026-09-28).

Install: npx skills add timerise-ai/slack-ai-bot. Timerise builds it on a brief; the price comes from the brief.

Teams already live in Slack. The questions they ask about a system, how many bookings came in today, which invoices are overdue, what this customer's history looks like, should be answerable there, from the system's own data. And some actions, like sending an email or issuing a refund, should be possible from Slack too, but only after a person has approved them.

The chat loop is the easy part. Slack is a stateless, retrying client with a three-second deadline, and the model is an untrusted caller. Every id the model passes into a tool came from whoever was talking in the thread. Every webhook may arrive twice, on a cold instance. A bot that gets this wrong answers twice, forgets the thread it was following, or runs a tool for the wrong user.

This skill gives a coding agent the bot the way we build it: identity resolved from the Slack profile to an app account, tools scoped to that account, a shared state store so follow-ups survive cold starts, raw-body signature verification, an immediate acknowledgement with the work finished afterwards, and Approve and Cancel buttons whose click runs the side effect exactly once.

Build a two-way Slack AI bot inside a Next.js App Router app with the Chat SDK and the AI SDK: people mention or DM the bot and an LLM answers with tools scoped to their app account, the app posts reports into channels on its own, and risky actions wait behind Approve / Cancel buttons whose clicks come back to the app. Use when: (1) adding a Slack assistant that answers questions about a product's data and runs actions for the asking user, (2) the app must push messages to Slack and react to button clicks (human-in-the-loop approval, draft-then-send), (3) auditing an existing Slack bot that misses follow-ups, answers twice, or cannot identify users, (4) the user mentions: Slack bot, AI Slack assistant, two-way Slack, @mention bot, app_mention, thread follow-up, onSubscribedMessage, Slack interactivity, block_actions, Approve button, Block Kit buttons, response_url, Slack OAuth install, users:read.email, x-slack-signature, "bot stops replying in threads", "couldn't match your Slack account", "operation timed out" on a Slack button. Carries the identity bridge from Slack email to app user, user-scoped tools, the single-claim approval that survives double clicks, the 3-second ack rule, serverless state traps, and a Markdown to mrkdwn converter, with 31 tests. Next.js App Router on Vercel or any Node host; stores, auth, model and domain tools are seams. Not a Chat SDK API reference and not a Slack channel reader.

This skill was written by the engineer who has shipped this module. The earlier implementation it was audited against was the Slack assistant of a product on Next.js 16, Vercel and Supabase. The templates hold the properties a two-way bot has to hold: every answer and every tool call scoped to the person who asked, one side effect per approved click however often the button is clicked, both webhooks acknowledged inside Slack's three seconds, a bot token chosen by the workspace the event came from, and a failure the person in the thread can see. The suite in [`references/testing.md`](references/testing.md) states each one; [`references/provenance.md`](references/provenance.md) has the record.

What it builds:
- Mentions, DMs and thread follow-ups answered by a model with tools scoped to the asking user
- App-initiated reports
- Approve and Cancel buttons whose click runs the side effect exactly once
- Raw-body signature verification
- 3-second ack

When to use:
- A product needs a Slack assistant that answers from the user's own data.
- The app must start conversations: reports, alerts, drafts for review.
- An action is too risky to let a model fire: send email, publish, pay, delete.
- An existing bot loses thread follow-ups, double-sends, or fails identity.

When not to use:
- **Learning the Chat SDK (cards, modals, slash commands, other platforms) or the AI SDK (models, streaming, tool calling)**: the `chat-sdk` and `ai-sdk` skills.
- **Reading channel history as a data source**: a separate module.
- **One-way notifications only**: an incoming webhook URL. No bot needed.

Non-negotiables:
- Never put a payload in a button `value`. Everyone in the channel can read what a button carries, and a client can send any value back. The button carries an opaque approval id and the rest is loaded server-side. A test asserts the posted blocks contain the id and not the payload.
- Never authorize a click by channel membership. Anyone who can see a message can click it, so the clicker is resolved to an app user and checked against the resource, every time. A test clicks as an outsider and asserts the request is refused and still pending.
- Never pick a bot token with `limit(1)`. A token belongs to a workspace, not to whoever pressed Install, so `slack_installations` is keyed by `team_id` and the team comes off the event. One row per workspace is the shape the schema enforces, and the OAuth callback writes it: a token is never an environment variable, even for a single workspace.
- Never parse the body before verifying the signature. The HMAC is over the raw bytes, so a re-serialized body never matches. `request.text()` comes first, and the comparison is over byte lengths, so a forged multi-byte signature returns false instead of throwing. Both are in the suite.
- Never let a failure be silent. After the eyes reaction, silence reads as "still working". A thrown handler posts into the thread, and a refused or failed click answers the clicker through `response_url`.

Not this:
- Learning the Chat SDK: cards, modals, other platforms: The `chat-sdk` skill. This skill uses the SDK, it does not document it
- Model choice, streaming internals, tool-calling mechanics: The `ai-sdk` skill
- Reading channel history as a data source: A history module of its own: history scopes, pagination and user-name resolution are not here
- One-way notifications with no reply path: A Slack incoming webhook URL. No bot user, no tokens table, no webhooks to verify
- Slash commands or modals as the main interface: The `chat-sdk` skill; this module is mentions, DMs, threads and buttons

Requirements:
- **Next.js App Router** on a Node runtime, with a way to run work after the response: `after()` from `next/server`, `waitUntil` from `@vercel/functions`, or a queue.
- `chat` and `@chat-adapter/slack`, a chat state adapter, `ai` and `zod`. In production the state adapter must be shared, Redis or Postgres: with in-memory state every cold start forgets which threads the bot follows.
- A Slack app you control, distributed by OAuth, with the scope list in `references/setup.md` requested by the authorize URL rather than only listed on the config page.
- A users table that can be queried by lower-cased email in one indexed lookup. The Slack profile email is the identity bridge, and `references/adaptation.md` spells out the trust decision that implies and what to do instead when it is not acceptable.

Booking features we build with it:
- AI assistant and agent bookings: /features/ai-booking-assistant

What to tell your agent:
- Add a Slack bot to this Next.js app that answers mentions and DMs from the asking user's own data and keeps the thread for follow-up questions.
- Have the bot post a weekly report to a channel, and draft emails that a person approves in Slack before they are sent.
- Our Slack bot sometimes answers twice and loses the thread. Find out why.

Agent eval results (newest per agent and prompt):
- Gemini CLI (gemini-3.8-flash) on v0.1.5, 2026-09-28: pass (Postgres)
- Codex CLI (gpt-6-astra) on v0.1.5, 2026-09-28: pass (Postgres)
- Claude Code (claude-opus-5-5) on v0.1.5, 2026-09-28: pass (Postgres)

Recent releases:
- v0.1.5 (2026-09-28): Wording release, from scoring the prompt-1 agent eval runs against 0.1.4. The templates and the suite are unchanged.
- v0.1.4 (2026-09-28): Security fix release, from scoring the prompt-1 agent eval runs against 0.1.3.
- v0.1.3 (2026-09-28): Fix release, from scoring the prompt-1 agent eval runs against 0.1.2.
