You come back from lunch to 147 unread Slack messages across 6 channels. Most are noise. Some are critical. You spend 20 minutes catching up. Every day.
In this tutorial, weβll build a Slack bot that reads channel history and generates a concise AI summary. Type /summarize in any channel and get the key discussions, decisions, and action items from the last 24 hours.
What weβre building
/summarize
π Channel Summary (last 24 hours)
## Key Discussions
- Team debated switching from REST to GraphQL for the mobile API.
No decision yet β Sarah is writing a proposal.
- Bug in payment processing affecting ~2% of transactions.
Jake deployed a hotfix at 3:15 PM.
## Decisions Made
- Sprint demo moved to Thursday 2 PM (was Friday)
- New hire onboarding starts Monday β Alex is the buddy
## Action Items
- @sarah: GraphQL proposal by Wednesday
- @jake: Monitor payment error rates for 48 hours
- @team: Review updated sprint board before Thursday demo
Prerequisites
- Node.js 20+
- A Slack workspace where you can install apps
- An Anthropic API key (console.anthropic.com)
Step 1: Create the Slack app
Go to api.slack.com/apps β Create New App β From Scratch.
Name it βChannel Summarizerβ and select your workspace.
Under OAuth & Permissions, add these scopes:
channels:historyβ read channel messageschat:writeβ post summariescommandsβ handle slash commands
Install the app to your workspace and copy the Bot User OAuth Token (starts with xoxb-).
Step 2: Set up the project
mkdir slack-summarizer && cd slack-summarizer
npm init -y
npm install @slack/bolt @anthropic-ai/sdk dotenv
Create .env:
SLACK_BOT_TOKEN=xoxb-your-token
SLACK_SIGNING_SECRET=your-signing-secret
ANTHROPIC_API_KEY=sk-ant-your-key
Step 3: Build the bot
// app.js
const { App } = require('@slack/bolt');
const Anthropic = require('@anthropic-ai/sdk');
require('dotenv').config();
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET,
});
const anthropic = new Anthropic();
app.command('/summarize', async ({ command, ack, respond }) => {
await ack();
await respond('β³ Generating summary...');
try {
// Fetch last 24 hours of messages
const now = Math.floor(Date.now() / 1000);
const yesterday = now - 86400;
const result = await app.client.conversations.history({
channel: command.channel_id,
oldest: String(yesterday),
limit: 200,
});
const messages = result.messages
.filter(m => m.type === 'message' && !m.bot_id)
.map(m => m.text)
.reverse()
.join('\n');
if (!messages.length) {
await respond('No messages in the last 24 hours.');
return;
}
// Generate summary with Claude
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 1024,
messages: [{
role: 'user',
content: `Summarize these Slack messages from the last 24 hours.
Extract: key discussions (with context), decisions made, and action items (with @mentions if present).
Be concise. Use bullet points. Skip small talk and reactions.
Messages:
${messages}`
}],
});
await respond(response.content[0].text);
} catch (error) {
await respond(`Error: ${error.message}`);
}
});
(async () => {
await app.start(3000);
console.log('β‘ Slack Summarizer running on port 3000');
})();
Step 4: Register the slash command
In your Slack app settings β Slash Commands β Create New Command:
- Command:
/summarize - Request URL:
https://your-domain.com/slack/events(use ngrok for local testing) - Description: βSummarize the last 24 hours of this channelβ
Step 5: Test it
# Terminal 1: Start the bot
node app.js
# Terminal 2: Expose to internet (for local testing)
npx ngrok http 3000
Update the slash command URL with your ngrok URL. Go to any Slack channel and type /summarize.
Making it better
Scheduled daily summaries
Add a cron job that posts summaries automatically every morning:
const cron = require('node-cron');
// Every weekday at 9 AM
cron.schedule('0 9 * * 1-5', async () => {
const channels = ['C01GENERAL', 'C02ENGINEERING'];
for (const channel of channels) {
// Same summary logic as above
// Post to channel instead of responding to command
await app.client.chat.postMessage({
channel,
text: summary,
});
}
});
Thread-aware summaries
The basic version ignores threads. To include them, fetch thread replies for messages that have reply_count > 0:
for (const msg of messages.filter(m => m.reply_count > 0)) {
const thread = await app.client.conversations.replies({
channel: command.channel_id,
ts: msg.ts,
});
// Append thread messages to context
}
Deploy
The simplest deployment: push to GitHub and deploy on Railway or Render (both have free tiers). Set your environment variables in the platformβs dashboard.
For production, add error handling, rate limiting on the Claude API calls, and a message cache to avoid re-fetching the same history.
Total build time: ~45 minutes. API cost: ~$0.01-0.05 per summary (depending on channel volume).
Previous in series: Build an AI Code Review Bot for GitHub Next in series: Build a CLI That Explains Error Messages With AI