Build an AI Slack Bot That Actually Does Useful Things: Complete Guide
Build a production Slack bot powered by Claude that answers questions, summarizes threads, and automates tasks. Full code, deployment included.
Slack bots powered by AI are the highest-ROI automation you can build for any team. A single bot that answers questions from your docs, summarizes long threads, drafts responses, and automates repetitive tasks saves hours per person per week. And building one is surprisingly straightforward.
This tutorial builds a production-quality AI Slack bot from scratch. Not a toy demo — a real bot with conversation memory, document knowledge, rate limiting, error handling, and one-click deployment. By the end, you’ll have a bot your entire team wants to use.
What We’re Building

Our Slack bot will:
- Respond to direct messages and @mentions with AI-powered answers
- Summarize long threads when asked
- Answer questions from your company’s documents (RAG)
- Maintain conversation context within threads
- Handle rate limiting and errors gracefully
- Deploy to Railway with zero-downtime updates
Tech Stack
- Python 3.11+ with async support
- Slack Bolt for Slack API integration
- Anthropic SDK for Claude API
- ChromaDB for document storage (RAG)
- Railway for deployment
Step 1: Set Up Slack App

Create the App
- Go to api.slack.com/apps and click “Create New App”
- Choose “From scratch”
- Name it (e.g., “AI Assistant”) and select your workspace
- Navigate to “OAuth & Permissions”
Configure Permissions
Add these Bot Token Scopes:
app_mentions:read— detect when someone @mentions the botchannels:history— read messages in public channelschannels:read— list channelschat:write— send messagesgroups:history— read messages in private channelsim:history— read direct messagesim:read— access DM infoim:write— send DMsusers:read— get user info
Enable Events
Navigate to “Event Subscriptions,” enable events, and subscribe to:
app_mention— when someone @mentions the botmessage.im— direct messages to the bot
Install to Workspace
Click “Install to Workspace” and authorize. Save the Bot User OAuth Token (xoxb-...) and the Signing Secret.
Step 2: Project Setup

mkdir ai-slack-bot && cd ai-slack-bot
python -m venv venv
source venv/bin/activate
pip install slack-bolt anthropic chromadb python-dotenv aiohttp
Create .env:
SLACK_BOT_TOKEN=xoxb-your-token
SLACK_SIGNING_SECRET=your-signing-secret
ANTHROPIC_API_KEY=sk-ant-your-key
Step 3: Basic Bot Structure

Create bot.py:
import os
import logging
from dotenv import load_dotenv
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
from anthropic import Anthropic
load_dotenv()
logging.basicConfig(level=logging.INFO)
app = App(token=os.environ["SLACK_BOT_TOKEN"],
signing_secret=os.environ["SLACK_SIGNING_SECRET"])
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
SYSTEM_PROMPT = """You are a helpful AI assistant in a Slack workspace.
Be concise, friendly, and helpful. Use Slack formatting (bold with *text*,
code with `text`, code blocks with ```text```). Keep responses focused
and actionable. If you don't know something, say so."""
@app.event("app_mention")
def handle_mention(event, say, client_slack=None):
thread_ts = event.get("thread_ts", event["ts"])
user_message = event["text"].split(">", 1)[-1].strip()
# Show typing indicator
say("Thinking...", thread_ts=thread_ts)
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": user_message}]
)
say(response.content[0].text, thread_ts=thread_ts)
@app.event("message")
def handle_dm(event, say):
if event.get("channel_type") != "im":
return
if event.get("bot_id"):
return
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": event["text"]}]
)
say(response.content[0].text)
if __name__ == "__main__":
# For Socket Mode (development)
handler = SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
handler.start()
This is the minimal viable bot. It responds to mentions and DMs with Claude-powered answers. But it’s missing conversation memory, document knowledge, and production hardening. Let’s add those.
Step 4: Add Conversation Memory

For thread-aware conversations, we need to fetch the thread history and pass it to Claude:
from collections import defaultdict
import time
conversation_cache = defaultdict(list)
CACHE_TTL = 3600 # 1 hour
def get_thread_messages(client_slack, channel, thread_ts):
"""Fetch all messages in a thread for context."""
result = client_slack.conversations_replies(
channel=channel, ts=thread_ts, limit=20
)
messages = []
for msg in result["messages"]:
role = "assistant" if msg.get("bot_id") else "user"
text = msg.get("text", "")
if text and text != "Thinking...":
messages.append({"role": role, "content": text})
return messages
@app.event("app_mention")
def handle_mention_with_memory(event, say, client=None):
channel = event["channel"]
thread_ts = event.get("thread_ts", event["ts"])
user_message = event["text"].split(">", 1)[-1].strip()
# Get thread context
messages = get_thread_messages(
app.client, channel, thread_ts
)
# If no thread history, start fresh
if not messages:
messages = [{"role": "user", "content": user_message}]
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=messages
)
say(response.content[0].text, thread_ts=thread_ts)
Now the bot maintains context within Slack threads, giving coherent multi-turn conversations.
Step 5: Add Document Knowledge (RAG)

Create knowledge.py for document ingestion and retrieval:
import chromadb
from chromadb.utils import embedding_functions
import os
# Initialize ChromaDB with default embedding
ef = embedding_functions.DefaultEmbeddingFunction()
chroma_client = chromadb.PersistentClient(path="./chroma_data")
collection = chroma_client.get_or_create_collection(
name="company_docs",
embedding_function=ef
)
def add_documents(texts: list[str], metadatas: list[dict] = None):
"""Add documents to the knowledge base."""
ids = [f"doc_{i}_{hash(t)}" for i, t in enumerate(texts)]
collection.add(documents=texts, ids=ids, metadatas=metadatas)
def search_documents(query: str, n_results: int = 3) -> list[str]:
"""Search for relevant documents."""
results = collection.query(query_texts=[query], n_results=n_results)
return results["documents"][0] if results["documents"] else []
def build_rag_prompt(query: str) -> str:
"""Build a prompt with retrieved context."""
docs = search_documents(query)
if not docs:
return query
context = "\n\n---\n\n".join(docs)
return f"""Based on the following company documentation, answer the question.
If the documentation doesn't contain the answer, say so.
DOCUMENTATION:
{context}
QUESTION: {query}"""
Integrate RAG into the bot by checking if a message starts with a keyword like “docs:” or “knowledge:“:
@app.event("app_mention")
def handle_mention_with_rag(event, say):
user_message = event["text"].split(">", 1)[-1].strip()
thread_ts = event.get("thread_ts", event["ts"])
# Check if this is a knowledge query
if user_message.lower().startswith(("docs:", "knowledge:", "help:")):
query = user_message.split(":", 1)[1].strip()
enhanced_prompt = build_rag_prompt(query)
else:
enhanced_prompt = user_message
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": enhanced_prompt}]
)
say(response.content[0].text, thread_ts=thread_ts)
Step 6: Thread Summarization

Add a slash command for summarizing long threads:
@app.command("/summarize")
def handle_summarize(ack, respond, command):
ack()
channel = command["channel_id"]
# Get recent messages from the channel
result = app.client.conversations_history(
channel=channel, limit=50
)
messages_text = []
for msg in reversed(result["messages"]):
user_info = app.client.users_info(user=msg.get("user", "unknown"))
name = user_info["user"]["real_name"] if user_info["ok"] else "Unknown"
messages_text.append(f"{name}: {msg.get('text', '')}")
conversation = "\n".join(messages_text)
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system="Summarize this Slack conversation concisely. Highlight key decisions, action items, and important points. Use bullet points.",
messages=[{"role": "user", "content": conversation}]
)
respond(response.content[0].text)
Step 7: Rate Limiting and Error Handling

from collections import defaultdict
import time
import traceback
# Simple rate limiter
user_requests = defaultdict(list)
RATE_LIMIT = 10 # requests per minute per user
def check_rate_limit(user_id: str) -> bool:
now = time.time()
user_requests[user_id] = [
t for t in user_requests[user_id] if now - t < 60
]
if len(user_requests[user_id]) >= RATE_LIMIT:
return False
user_requests[user_id].append(now)
return True
@app.event("app_mention")
def handle_mention_production(event, say):
user = event["user"]
thread_ts = event.get("thread_ts", event["ts"])
if not check_rate_limit(user):
say("You're sending too many requests. Please wait a moment.",
thread_ts=thread_ts)
return
try:
user_message = event["text"].split(">", 1)[-1].strip()
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": user_message}]
)
say(response.content[0].text, thread_ts=thread_ts)
except Exception as e:
logging.error(f"Error: {traceback.format_exc()}")
say("Sorry, I encountered an error. Please try again.",
thread_ts=thread_ts)
Step 8: Deploy to Railway

Create a Procfile:
web: python bot.py
Create requirements.txt:
slack-bolt==1.18.0
anthropic==0.40.0
chromadb==0.5.0
python-dotenv==1.0.0
aiohttp==3.9.0
Deploy:
# Install Railway CLI
npm i -g @railway/cli
# Login and deploy
railway login
railway init
railway up
# Set environment variables
railway variables set SLACK_BOT_TOKEN=xoxb-...
railway variables set SLACK_SIGNING_SECRET=...
railway variables set ANTHROPIC_API_KEY=sk-ant-...
For Socket Mode (easiest for most deployments), also add your SLACK_APP_TOKEN (generated from the Slack app settings under “Basic Information” > “App-Level Tokens”).
Step 9: Load Your Knowledge Base

Create a script to ingest your company documents:
# ingest.py
import os
import glob
from knowledge import add_documents
def chunk_text(text, chunk_size=500, overlap=50):
words = text.split()
chunks = []
for i in range(0, len(words), chunk_size - overlap):
chunk = " ".join(words[i:i + chunk_size])
chunks.append(chunk)
return chunks
def ingest_directory(path):
files = glob.glob(os.path.join(path, "**/*.md"), recursive=True)
files += glob.glob(os.path.join(path, "**/*.txt"), recursive=True)
for file_path in files:
with open(file_path, "r") as f:
text = f.read()
chunks = chunk_text(text)
metadatas = [{"source": file_path} for _ in chunks]
add_documents(chunks, metadatas)
print(f"Ingested {file_path}: {len(chunks)} chunks")
if __name__ == "__main__":
ingest_directory("./docs")
Run python ingest.py with your company docs in the ./docs folder.
What to Build Next
After the basic bot is running:
- Slash commands for specific tasks (/draft-email, /code-review, /meeting-notes)
- Reactions as triggers (react with a specific emoji to summarize or translate a message)
- Scheduled summaries — daily digest of channel activity
- User preferences — let users customize the AI’s behavior per user
- Analytics — track usage, popular queries, and user satisfaction
The Bottom Line
An AI Slack bot is one of the most impactful things you can build for a team. It’s always available, it doesn’t get annoyed by repeated questions, and it gets better as you add more knowledge to its document base.
The total build time for this tutorial is about 2-3 hours. Deployment costs are minimal — Railway’s free tier handles most small-team usage, and Claude API costs for a typical team are $20-50/month.
Build it, deploy it, and watch your team wonder how they worked without it.
> Want more like this?
Get the best AI insights delivered weekly.
> Related Articles
Web Scraping with AI: Build a Smart Data Extraction Pipeline
Traditional web scraping breaks when websites change layouts. AI-powered scraping understands page structure and extracts data intelligently. Here's how to build one using Python, Beautiful Soup, and Claude.
Create an AI Art Portfolio: From Generation to Gallery in One Weekend
Build a professional AI art portfolio website with curated collections, consistent style, and proper attribution. Covers prompt engineering, style consistency, curation, and deployment.
Build an AI Chrome Extension: Add Claude to Any Webpage in 60 Minutes
Build a Chrome extension that summarizes web pages, answers questions about content, and rewrites selected text — all powered by Claude. Full source code and step-by-step instructions included.
Tags
> Stay in the loop
Weekly AI tools & insights.