← library
promptexample · tested💬 Chatbots & RAGv1 · published 2026-10-15 · updated 2026-11-03

Slack and Teams agent bot

One TypeScript bot for Slack and Microsoft Teams with Chat SDK, streamed AI replies, and thread context.

By Uplift Duo

Run it as a prompt

Paste this into any AI agent, or fetch it: curl -s https://uplift.page/api/v1/prompts/slack-teams-agent-bot/raw

prompt.md
# Slack and Teams agent bot

Answer @mentions in Slack and Teams threads with the first streamed token under 2 s, keep thread context for follow-ups, acknowledge webhooks fast enough that Slack sends 0 retries in 50 test mentions, and handle 20 concurrent threads.

## Recommended stack

- **Chat SDK 4 (npm chat) + @chat-adapter/slack and @chat-adapter/teams** - One handler set for both platforms. onNewMention, thread.subscribe, and thread.post work the same across Slack, Teams, Google Chat, Discord, and others.
- **@chat-adapter/state-redis** - Subscriptions and dedupe across instances. The memory adapter loses subscriptions on restart or with more than one instance.
- **AI SDK 7** - Streaming model replies and tools. streamText output passes straight to thread.post as an async iterable.
- **Next.js 16 route handlers** - Webhook endpoints. after() lets the route acknowledge immediately and finish the reply in the background.

## Build steps

1. Create the Slack app with bot scopes (app_mentions:read, chat:write, channels:history, groups:history, im:history, reactions:write, assistant:write, users:read) and events pointed at /api/webhooks/slack; register the Teams bot for /api/webhooks/teams. Subscribe to bot events app_mention, message.channels, message.groups and message.im (follow-ups in subscribed threads need the message.* events).
2. In lib/bot.ts create `new Chat({ userName, adapters: { slack: createSlackAdapter(), teams: createTeamsAdapter() }, state: createRedisState() })`; credentials come from env vars.
3. bot.onNewMention: subscribe the thread, react to show it is working, load the last 20 thread messages as context, and stream the reply with thread.post(asyncIterable).
4. bot.onSubscribedMessage: answer follow-ups in subscribed threads without a new mention; stop on "stop" or after 30 minutes idle.
5. Webhook routes return `bot.webhooks.slack(request, { waitUntil: task => after(() => task) })` (same for Teams).
6. Give the bot 2-3 read-only tools (search docs, look up a ticket); any write action posts a confirm card and runs only after a button click.
7. Test locally with Slack socket mode: createSlackAdapter({ mode: "socket" }) with SLACK_APP_TOKEN (xapp-, connections:write), then deploy in webhook mode with REDIS_URL set (or @chat-adapter/state-pg with createPostgresState() and POSTGRES_URL).

## Watch out for

- Doing model work before acknowledging the webhook makes Slack retry and the bot answers twice. Acknowledge first and dedupe by event id.
- Memory state in a multi-instance deploy drops subscriptions. Use Redis.
- Slack native streams expire after about 5 minutes; keep answers short and let the adapter start a new message.
- Replying to its own or other bots' messages creates loops. Ignore bot authors.

## Definition of done

- An @mention in Slack streams a reply into the thread, and a follow-up without a mention gets a context-aware answer.
- The same works in a Teams channel.
- After restarting the app, the next message in a subscribed thread is still answered.
- 50 test mentions show 0 Slack retry deliveries in the logs.
- 20 concurrent mentions in different threads each get one reply; logs show no request with an X-Slack-Retry-Num header.

The full prompt

Answer @mentions in Slack and Teams threads with the first streamed token under 2 s, keep thread context for follow-ups, acknowledge webhooks fast enough that Slack sends 0 retries in 50 test mentions, and handle 20 concurrent threads.

Recommended stack

  • Chat SDK 4 (npm chat) + @chat-adapter/slack and @chat-adapter/teams - One handler set for both platforms. onNewMention, thread.subscribe, and thread.post work the same across Slack, Teams, Google Chat, Discord, and others.
  • @chat-adapter/state-redis - Subscriptions and dedupe across instances. The memory adapter loses subscriptions on restart or with more than one instance.
  • AI SDK 7 - Streaming model replies and tools. streamText output passes straight to thread.post as an async iterable.
  • Next.js 16 route handlers - Webhook endpoints. after() lets the route acknowledge immediately and finish the reply in the background.

Build steps

  1. Create the Slack app with bot scopes (app_mentions:read, chat:write, channels:history, groups:history, im:history, reactions:write, assistant:write, users:read) and events pointed at /api/webhooks/slack; register the Teams bot for /api/webhooks/teams. Subscribe to bot events app_mention, message.channels, message.groups and message.im (follow-ups in subscribed threads need the message.* events).
  2. In lib/bot.ts create new Chat({ userName, adapters: { slack: createSlackAdapter(), teams: createTeamsAdapter() }, state: createRedisState() }); credentials come from env vars.
  3. bot.onNewMention: subscribe the thread, react to show it is working, load the last 20 thread messages as context, and stream the reply with thread.post(asyncIterable).
  4. bot.onSubscribedMessage: answer follow-ups in subscribed threads without a new mention; stop on "stop" or after 30 minutes idle.
  5. Webhook routes return bot.webhooks.slack(request, { waitUntil: task => after(() => task) }) (same for Teams).
  6. Give the bot 2-3 read-only tools (search docs, look up a ticket); any write action posts a confirm card and runs only after a button click.
  7. Test locally with Slack socket mode: createSlackAdapter({ mode: "socket" }) with SLACK_APP_TOKEN (xapp-, connections:write), then deploy in webhook mode with REDIS_URL set (or @chat-adapter/state-pg with createPostgresState() and POSTGRES_URL).

Watch out for

  • Doing model work before acknowledging the webhook makes Slack retry and the bot answers twice. Acknowledge first and dedupe by event id.
  • Memory state in a multi-instance deploy drops subscriptions. Use Redis.
  • Slack native streams expire after about 5 minutes; keep answers short and let the adapter start a new message.
  • Replying to its own or other bots' messages creates loops. Ignore bot authors.

Definition of done

  • An @mention in Slack streams a reply into the thread, and a follow-up without a mention gets a context-aware answer.
  • The same works in a Teams channel.
  • After restarting the app, the next message in a subscribed thread is still answered.
  • 50 test mentions show 0 Slack retry deliveries in the logs.
  • 20 concurrent mentions in different threads each get one reply; logs show no request with an X-Slack-Retry-Num header.

Served from the uplift.page library and refreshed within 5 minutes of every update.