Claude Slack Bot Python: Build an AI Slack Assistant

Build a Slack bot powered by Claude using Python Bolt SDK. Mentions, DMs, thread-aware conversation history, slash commands, and streaming message updates — all with working code.

💥 50p impulse-buy: Power Prompts PDF (first 10 buyers) 30 battle-tested Claude Code prompts · 8-page PDF · paste into CLAUDE.md and never re-type a prompt again · 50p impulse-buy, no commitment

A Slack bot powered by Claude can handle support tickets, answer internal questions, review code snippets, and summarize threads — all within the tools your team already uses. This guide uses Slack Bolt for Python and the Anthropic SDK.

1. Setup

pip install slack-bolt anthropic

Create a Slack App at api.slack.com/apps. Enable:

# .env
SLACK_BOT_TOKEN=xoxb-...
SLACK_APP_TOKEN=xapp-...   # only for Socket Mode
ANTHROPIC_API_KEY=sk-ant-...

2. Minimal bot — respond to @mentions

import os
import anthropic
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler

app = App(token=os.environ["SLACK_BOT_TOKEN"])
claude = anthropic.Anthropic()

@app.event("app_mention")
def handle_mention(event, say, client):
    # Strip the @BotName mention from the text
    text = event["text"]
    # Remove <@BOTID> from the beginning
    import re
    user_message = re.sub(r"<@[A-Z0-9]+>", "", text).strip()
    if not user_message:
        say("Hi! Ask me anything.")
        return

    response = claude.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        system="You are a helpful Slack assistant. Keep replies concise and use Slack markdown formatting.",
        messages=[{"role": "user", "content": user_message}],
    )
    say(response.content[0].text)

if __name__ == "__main__":
    SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()

3. Thread-aware conversation history

@app.event("app_mention")
def handle_mention_with_history(event, say, client):
    import re
    channel = event["channel"]
    thread_ts = event.get("thread_ts", event["ts"])

    # Fetch thread history
    result = client.conversations_replies(channel=channel, ts=thread_ts, limit=20)
    bot_user_id = client.auth_test()["user_id"]

    history = []
    for msg in result["messages"]:
        # Skip bot_message subtypes and system messages
        if msg.get("subtype"):
            continue
        text = re.sub(r"<@[A-Z0-9]+>", "", msg.get("text", "")).strip()
        if not text:
            continue
        role = "assistant" if msg.get("user") == bot_user_id else "user"
        history.append({"role": role, "content": text})

    # Ensure last message is the current user turn
    if not history or history[-1]["role"] != "user":
        return

    response = claude.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        system="You are a helpful Slack assistant. Be concise. Use Slack markdown (*bold*, _italic_, `code`).",
        messages=history,
    )
    say(text=response.content[0].text, thread_ts=thread_ts)

4. Slash command

@app.command("/ask")
def handle_ask_command(ack, respond, command):
    ack()  # acknowledge within 3 seconds
    user_question = command["text"].strip()
    if not user_question:
        respond("Usage: /ask ")
        return

    respond({"response_type": "in_channel", "text": "Thinking…"})

    response = claude.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=2048,
        system="You are a helpful assistant. Answer concisely.",
        messages=[{"role": "user", "content": user_question}],
    )
    respond(response.content[0].text)

5. Post-then-update pattern (simulate streaming)

@app.event("message")
def handle_dm(event, say, client):
    # Only handle DMs; ignore bot messages
    if event.get("channel_type") != "im" or event.get("bot_id"):
        return

    user_message = event.get("text", "").strip()
    if not user_message:
        return

    # Post a placeholder immediately so the user sees feedback
    result = say("_Thinking…_")
    placeholder_ts = result["ts"]
    channel = event["channel"]

    # Get full streamed response
    full_reply = ""
    with claude.messages.stream(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        system="You are a helpful Slack DM assistant. Be concise.",
        messages=[{"role": "user", "content": user_message}],
    ) as stream:
        for text in stream.text_stream:
            full_reply += text

    # Replace placeholder with full response
    client.chat_update(channel=channel, ts=placeholder_ts, text=full_reply)

Claude Slack bot vs alternatives

ApproachSetup timeCustomizabilityCost
Claude + Bolt (this guide)~30 minFull — any system prompt, any toolClaude API usage only
Slack AI (built-in)MinutesNone — fixed features$10/user/month
Zapier/Make automation~15 minLow — template-basedZapier subscription + Claude
OpenAI + Bolt~30 minFull — same patternOpenAI API usage

For cost estimates before deploying your Slack bot, use the Claude API Cost Calculator. For multi-turn conversation patterns, see the chatbot guide.

Frequently asked questions

Which Slack SDK should I use with Claude?
Use Slack Bolt for Python (`pip install slack-bolt`). Bolt handles OAuth, event routing, retry logic, and socket mode (no public URL needed for development). Pair it with the `anthropic` SDK for Claude calls.
How do I keep conversation context in Slack threads?
Use Slack's `client.conversations_replies()` to fetch all messages in a thread, filter for user/bot turns, and reconstruct the message history to pass to Claude. Store the thread_ts as the conversation ID.
Can Claude stream responses into Slack?
Slack doesn't support SSE/streaming. The standard pattern is to post an initial 'Thinking…' message, then stream Claude's response internally, and call `client.chat_update()` once streaming completes to replace the placeholder with the full response. This gives near-instant perceived response.
How do I prevent the bot from responding to its own messages?
Check `event.get('bot_id')` or `message.get('subtype') == 'bot_message'` and return early. Also compare the bot's user ID (available from `app.client.auth_test()`) against the message's `user` field.
What's the Slack API rate limit for a bot?
Slack allows ~1 message per second per channel (Tier 3). For higher-volume bots, use Slack's Bolt concurrency options and implement a queue. Claude's API is the more likely bottleneck for high-traffic bots — Sonnet has a default 50 RPM limit on new accounts.

Free tools

Cost Calculator → API Cookbook → Diff Summarizer → Skills Browser →

More examples

Claude API Python QuickstartClaude API Node.js / TypeScript QuickstartClaude API Streaming in PythonClaude API Streaming in Node.js / TypeScriptClaude API Tool Use in PythonClaude API Tool Use in Node.js / TypeScript