TUTORIALS 14 min read

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.

By EgoistAI ·
Build an AI Slack Bot That Actually Does Useful Things: Complete Guide

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

Chapter 1: What We're Building

Our Slack bot will:

  1. Respond to direct messages and @mentions with AI-powered answers
  2. Summarize long threads when asked
  3. Answer questions from your company’s documents (RAG)
  4. Maintain conversation context within threads
  5. Handle rate limiting and errors gracefully
  6. 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

Chapter 2: Slack Setup

Create the App

  1. Go to api.slack.com/apps and click “Create New App”
  2. Choose “From scratch”
  3. Name it (e.g., “AI Assistant”) and select your workspace
  4. Navigate to “OAuth & Permissions”

Configure Permissions

Add these Bot Token Scopes:

  • app_mentions:read — detect when someone @mentions the bot
  • channels:history — read messages in public channels
  • channels:read — list channels
  • chat:write — send messages
  • groups:history — read messages in private channels
  • im:history — read direct messages
  • im:read — access DM info
  • im:write — send DMs
  • users:read — get user info

Enable Events

Navigate to “Event Subscriptions,” enable events, and subscribe to:

  • app_mention — when someone @mentions the bot
  • message.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

Chapter 3: 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

Chapter 4: Basic 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

Chapter 5: 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)

Chapter 6: RAG Integration

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

Chapter 7: 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

Chapter 8: Production Hardening

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

Chapter 9: Deployment

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

Chapter 10: Loading Knowledge

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.

Share this article

> Want more like this?

Get the best AI insights delivered weekly.

> Related Articles

Tags

Slack botAI chatbotPythonClaude APIautomationtutorial

> Stay in the loop

Weekly AI tools & insights.