Build a Python Slack Bot That Automates Team Updates in 45 Minutes
Every morning at 9 AM, someone on your team has to remember to post the standup reminder in Slack. Then at 5 PM, someone else manually compiles everyone's updates into a summary. It takes 20-30 minutes dailyβtime that could be spent on actual work.
What if a bot handled this automatically? Post reminders, collect responses, generate summaries, and even send them to stakeholdersβall without human intervention.
I'll show you how to build a Python Slack bot that automates team updates from start to finish. No prior bot experience needed. By the end, you'll have a working bot deployed to the cloud running 24/7.
What You'll Build
Your Slack bot will:
- Post daily standup reminders at scheduled times
- Collect responses via threads or DMs
- Generate summary reports using AI (optional)
- Send reports to specific channels or people
- Handle custom commands (e.g.,
/standup status)
Time to build: 45 minutes (30 min coding + 15 min deployment) Cost: Free (using free tiers of Slack, Railway/Render, and Anthropic/OpenAI)
Prerequisites
You'll need:
- Python 3.9+ installed
- Slack workspace where you're an admin (or can install apps)
- Basic Python knowledge (functions, loops, HTTP requests)
- Code editor (VS Code, PyCharm, or similar)
Install required libraries:
1pip install slack-bolt requests schedule python-dotenv
Architecture Overview
Here's how the bot works:
βββββββββββββββββββ
β Slack Event β (User posts update, bot receives webhook)
β (Webhook) β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Python Bot β (Processes event, extracts text)
β (slack-bolt) β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Scheduler β (Runs tasks at specific times)
β (schedule lib) β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Storage β (Store updates temporarily)
β (dict/JSON) β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Report Gen β (Compile and format summaries)
β (Python) β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Slack API β (Post messages back to Slack)
β (Send Message) β
βββββββββββββββββββStep 1: Create Slack App and Get Credentials
1. Create a new Slack App
- Go to api.slack.com/apps
- Click "Create New App" β "From scratch"
- App Name: "Standup Bot"
- Workspace: Select your workspace
2. Configure Bot Token Scopes Go to OAuth & Permissions β Scopes β Add these Bot Token Scopes:
chat:write(send messages)chat:write.public(post to channels without joining)channels:history(read channel messages)channels:read(list channels)groups:history(read private channel messages)im:history(read DMs)im:write(send DMs)users:read(get user info)commands(for slash commands)
3. Install App to Workspace
- Click "Install to Workspace"
- Authorize the permissions
- Copy the Bot User OAuth Token (starts with
xoxb-)
4. Enable Event Subscriptions
- Go to Event Subscriptions β Enable Events
- Request URL:
https://your-app-url.com/slack/events(we'll set this up later) - Subscribe to bot events:
message.channels(messages in channels)message.im(direct messages)app_mention(when bot is @mentioned)
5. Get Signing Secret
- Go to Basic Information β App Credentials
- Copy the Signing Secret
Save both tokensβyou'll need them in your code.
Step 2: Set Up Project Structure
Create project folder and files:
1mkdir standup-bot2cd standup-bot34# Create files5touch app.py6touch config.py7touch scheduler.py8touch report_generator.py9touch .env10touch requirements.txt
File structure:
standup-bot/ βββ app.py # Main bot logic βββ config.py # Configuration settings βββ scheduler.py # Scheduled tasks βββ report_generator.py # Summary generation βββ .env # Environment variables (secrets) βββ requirements.txt # Python dependencies
Step 3: Configure Environment Variables
Create .env file:
1# .env2SLACK_BOT_TOKEN=xoxb-your-bot-token-here3SLACK_SIGNING_SECRET=your-signing-secret-here4STANDUP_CHANNEL_ID=C12345678 # Get from Slack channel details5SUMMARY_CHANNEL_ID=C87654321 # Where summaries are posted
To find Channel ID:
- Right-click on channel β View channel details
- Scroll down to find the Channel ID
Create config.py:
1# config.py2import os3from dotenv import load_dotenv45load_dotenv()67# Slack credentials8SLACK_BOT_TOKEN = os.getenv("SLACK_BOT_TOKEN")9SLACK_SIGNING_SECRET = os.getenv("SLACK_SIGNING_SECRET")1011# Channel configuration12STANDUP_CHANNEL_ID = os.getenv("STANDUP_CHANNEL_ID")13SUMMARY_CHANNEL_ID = os.getenv("SUMMARY_CHANNEL_ID")1415# Schedule times (24-hour format)16REMINDER_TIME = "09:00" # 9 AM daily reminder17SUMMARY_TIME = "17:00" # 5 PM daily summary1819# Team members to track (Slack user IDs)20TEAM_MEMBERS = [21 "U12345678", # Replace with actual user IDs22 "U87654321",23 "U11223344"24]2526# Storage (in-memory for simplicity, use DB for production)27daily_updates = {}2829print("β Configuration loaded")
Step 4: Build Core Bot Logic
Create app.py:
1# app.py2from slack_bolt import App3from slack_bolt.adapter.socket_mode import SocketModeHandler4from config import SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET, STANDUP_CHANNEL_ID, daily_updates5import re6from datetime import datetime78# Initialize bot9app = App(token=SLACK_BOT_TOKEN)1011@app.event("app_mention")12def handle_mention(event, say, client):13 """Respond when bot is @mentioned"""14 user = event["user"]15 text = event["text"].lower()1617 if "help" in text:18 say(19 f"Hi <@{user}>! I'm your Standup Bot. Here's what I can do:\n\n"20 "β’ Post daily standup reminders at 9 AM\n"21 "β’ Collect team updates\n"22 "β’ Generate summary reports at 5 PM\n\n"23 "To submit your update, reply to the standup reminder or DM me!"24 )25 elif "status" in text:26 # Show who has submitted updates today27 submitted = len(daily_updates)28 say(f"π Updates today: {submitted} team members have submitted")29 else:30 say(f"Hey <@{user}>! Use `@Standup Bot help` for commands")3132@app.message(re.compile(r"^standup:?", re.IGNORECASE))33def handle_standup_update(message, say, client):34 """Handle standup updates (messages starting with 'standup')"""35 user_id = message["user"]36 text = message["text"]37 timestamp = message["ts"]3839 # Extract the update (everything after "standup:")40 update_text = re.sub(r"^standup:?\s*", "", text, flags=re.IGNORECASE).strip()4142 if not update_text:43 say("Please provide your standup update after 'standup:'\n\n"44 "Example: `standup: Finished the API integration, starting frontend work today`")45 return4647 # Store the update48 daily_updates[user_id] = {49 "user_id": user_id,50 "text": update_text,51 "timestamp": timestamp,52 "submitted_at": datetime.now().isoformat()53 }5455 # Get user's real name56 user_info = client.users_info(user=user_id)57 user_name = user_info["user"]["real_name"]5859 # Confirm receipt60 say(f"β Thanks <@{user_id}>! Your update has been recorded.\n\n"61 f"_Preview: {update_text[:100]}..._")6263 print(f"β Recorded update from {user_name}")6465@app.command("/standup")66def handle_standup_command(ack, command, say, client):67 """Handle /standup slash command"""68 ack() # Acknowledge command receipt6970 subcommand = command["text"].strip().lower()71 user_id = command["user_id"]7273 if subcommand == "status":74 # Show status75 submitted_count = len(daily_updates)76 say(f"π *Standup Status*\n\n"77 f"Updates submitted: *{submitted_count}* team members")7879 elif subcommand == "summary":80 # Show current summary81 if not daily_updates:82 say("No updates yet today!")83 return8485 summary = generate_summary(client)86 say(summary)8788 else:89 say("*Available commands:*\n"90 "β’ `/standup status` - See who's submitted\n"91 "β’ `/standup summary` - View current summary\n\n"92 "To submit your update, use: `standup: Your update here`")9394def generate_summary(client):95 """Generate formatted summary of all updates"""96 if not daily_updates:97 return "No standup updates today."9899 summary_lines = ["*π Daily Standup Summary*\n"]100 summary_lines.append(f"_{datetime.now().strftime('%B %d, %Y')}_\n")101102 for user_id, update in daily_updates.items():103 # Get user's real name104 user_info = client.users_info(user=user_id)105 user_name = user_info["user"]["real_name"]106107 summary_lines.append(f"*{user_name}*")108 summary_lines.append(f"{update['text']}\n")109110 return "\n".join(summary_lines)111112@app.event("message")113def handle_message_events(body, logger):114 """Catch-all for messages (logged but not responded to)"""115 logger.debug(body)116117# Start the bot118if __name__ == "__main__":119 print("β‘ Standup Bot is starting...")120121 # For local development, use Socket Mode122 # For production, use HTTP mode with a public URL123 handler = SocketModeHandler(app, os.getenv("SLACK_APP_TOKEN"))124 handler.start()125126 print("β Bot is running!")
Step 5: Add Scheduled Tasks
Create scheduler.py:
1# scheduler.py2import schedule3import time4from datetime import datetime5from config import SLACK_BOT_TOKEN, STANDUP_CHANNEL_ID, SUMMARY_CHANNEL_ID, REMINDER_TIME, SUMMARY_TIME, daily_updates, TEAM_MEMBERS6from slack_sdk import WebClient7from app import generate_summary89client = WebClient(token=SLACK_BOT_TOKEN)1011def post_standup_reminder():12 """Post daily standup reminder"""13 print(f"β° Posting standup reminder at {datetime.now()}")1415 message = (16 "βοΈ *Good morning team! Time for standup.*\n\n"17 "Reply to this thread with your update using:\n"18 "`standup: Your update here`\n\n"19 "Include:\n"20 "β’ What you worked on yesterday\n"21 "β’ What you're working on today\n"22 "β’ Any blockers\n\n"23 "_Responses due by 5 PM for the daily summary._"24 )2526 try:27 response = client.chat_postMessage(28 channel=STANDUP_CHANNEL_ID,29 text=message,30 unfurl_links=False31 )32 print(f"β Reminder posted: {response['ts']}")33 except Exception as e:34 print(f"β Error posting reminder: {e}")3536def post_daily_summary():37 """Post end-of-day summary"""38 print(f"π Generating daily summary at {datetime.now()}")3940 if not daily_updates:41 print("β οΈ No updates to summarize")42 return4344 summary = generate_summary(client)4546 # Add completion stats47 submitted_count = len(daily_updates)48 total_team = len(TEAM_MEMBERS)49 completion_rate = (submitted_count / total_team * 100) if total_team > 0 else 05051 summary += f"\n\n_Participation: {submitted_count}/{total_team} team members ({completion_rate:.0f}%)_"5253 try:54 response = client.chat_postMessage(55 channel=SUMMARY_CHANNEL_ID,56 text=summary,57 unfurl_links=False58 )59 print(f"β Summary posted: {response['ts']}")6061 # Clear daily updates for next day62 daily_updates.clear()63 print("β Daily updates cleared")6465 except Exception as e:66 print(f"β Error posting summary: {e}")6768def start_scheduler():69 """Start the task scheduler"""70 print("\nβ° Starting scheduler...")7172 # Schedule tasks73 schedule.every().day.at(REMINDER_TIME).do(post_standup_reminder)74 schedule.every().day.at(SUMMARY_TIME).do(post_daily_summary)7576 print(f"β Scheduled: Reminder at {REMINDER_TIME}")77 print(f"β Scheduled: Summary at {SUMMARY_TIME}\n")7879 # Run scheduler loop80 while True:81 schedule.run_pending()82 time.sleep(60) # Check every minute8384if __name__ == "__main__":85 start_scheduler()
Step 6: Test Your Bot Locally
1. Start the bot:
1python app.py
2. Test interactions in Slack:
- Mention the bot:
@Standup Bot help - Submit an update:
standup: Finished API integration, working on frontend today - Check status:
/standup status - View summary:
/standup summary
3. Test scheduled tasks (for quick testing, modify times in config.py to trigger soon):
1REMINDER_TIME = "14:30" # Set to a few minutes from now2SUMMARY_TIME = "14:32"
Run scheduler:
1python scheduler.py
Step 7: Deploy to Cloud (Free)
To run 24/7, deploy to a cloud service. We'll use Railway (free tier).
Option A: Deploy to Railway
1. Prepare for deployment:
Create requirements.txt:
slack-bolt==1.18.0 slack-sdk==3.23.0 python-dotenv==1.0.0 schedule==1.2.0
Create Procfile:
web: python app.py worker: python scheduler.py
2. Deploy to Railway:
- Go to railway.app
- Sign up with GitHub
- Click "New Project" β "Deploy from GitHub repo"
- Select your repo
- Add environment variables in Railway dashboard:
SLACK_BOT_TOKENSLACK_SIGNING_SECRETSTANDUP_CHANNEL_IDSUMMARY_CHANNEL_ID
- Railway auto-deploys your bot
3. Update Slack Event URL:
- Get your Railway app URL (e.g.,
https://your-app.up.railway.app) - Go to Slack App settings β Event Subscriptions
- Update Request URL:
https://your-app.up.railway.app/slack/events - Verify the URL
Option B: Deploy to Render
Similar process:
- Sign up at render.com
- Create new Web Service from GitHub repo
- Add environment variables
- Deploy
Step 8: Advanced Features
Feature 1: AI-Powered Summaries
Use ChatGPT or Claude to generate more insightful summaries:
1# report_generator.py2import anthropic3import os45def generate_ai_summary(updates):6 """Generate AI-powered summary using Claude"""7 client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))89 # Format updates for AI10 updates_text = "\n\n".join([11 f"{update['user_name']}: {update['text']}"12 for update in updates.values()13 ])1415 prompt = f"""Analyze these standup updates and create a concise executive summary:1617{updates_text}1819Provide:201. Key themes and progress (2-3 sentences)212. Potential blockers or risks223. Notable achievements234. Suggested follow-ups2425Keep it under 150 words, professional but conversational."""2627 message = client.messages.create(28 model="claude-3-5-sonnet-20241022",29 max_tokens=500,30 messages=[{"role": "user", "content": prompt}]31 )3233 return message.content[0].text
Feature 2: Remind Individuals
Send DMs to people who haven't submitted:
1def remind_missing_updates():2 """DM team members who haven't submitted"""3 submitted_users = set(daily_updates.keys())4 missing_users = set(TEAM_MEMBERS) - submitted_users56 for user_id in missing_users:7 client.chat_postMessage(8 channel=user_id, # DM the user9 text="π Friendly reminder: Please submit your standup update!\n\n"10 "Reply with: `standup: Your update here`"11 )
Schedule this for 3 PM (before summary time):
1schedule.every().day.at("15:00").do(remind_missing_updates)
Feature 3: Weekly Digest
Generate weekly aggregated report:
1def post_weekly_digest():2 """Post weekly summary on Fridays"""3 # Pull updates from the week (you'd need to store these in a database)45 digest = (6 "*π Weekly Team Digest*\n\n"7 f"Week of {datetime.now().strftime('%B %d, %Y')}\n\n"8 "*Key Accomplishments:*\n"9 "β’ [AI-generated highlights]\n\n"10 "*Team Velocity:*\n"11 "β’ Average daily updates: 4.2/5 team members\n"12 "β’ On-time submission rate: 87%\n\n"13 "*Looking Ahead:*\n"14 "β’ [Upcoming priorities]"15 )1617 client.chat_postMessage(18 channel=SUMMARY_CHANNEL_ID,19 text=digest20 )2122# Schedule for Fridays at 5 PM23schedule.every().friday.at("17:00").do(post_weekly_digest)
Troubleshooting
Issue 1: Bot doesn't respond to messages
- Check Event Subscriptions are enabled
- Verify bot has correct permissions
- Ensure Request URL is verified and accessible
Issue 2: Scheduled tasks not running
- Check server time zone matches your expected times
- Verify scheduler.py process is running
- Add logging to confirm schedule times
Issue 3: "Not in channel" error
- Invite bot to channel:
/invite @Standup Bot - Or use
chat:write.publicscope to post without joining
Issue 4: Free tier limits
- Railway: 500 hours/month (plenty for one bot)
- Slack API: 1+ requests per second (sufficient)
- Optimize by reducing unnecessary API calls
Real-World Impact
Before automation:
- Manual reminder posts: 5 min/day
- Compiling summaries: 25 min/day
- Following up with team: 10 min/day
- Total: 40 min/day = 3.3 hours/week
After automation:
- Bot handles everything automatically
- Team spends time just submitting updates (2 min each)
- Manager reviews summary (5 min)
- Total saved: 3+ hours/week
Additional benefits:
- 100% consistency (bot never forgets)
- Searchable history of all standups
- Better visibility for remote team members
- Data for productivity insights
Frequently Asked Questions
Can this work for multiple teams? Yes. Set up separate channel IDs and team member lists for each team in config.py. Run multiple instances or use team-specific commands.
How do I handle weekends/holidays? Add date checks in scheduler:
1if datetime.now().weekday() >= 5: # Saturday or Sunday2 return # Skip weekend
Or use a calendar API to check holidays.
Can I integrate with other tools (Jira, Asana)? Yes. Add API calls in the summary function to create tickets, update tasks, or log time based on standup content.
What about data privacy? Slack data stays in Slack. If using AI summaries, check your AI provider's data policy. Consider enterprise tiers with data protection guarantees for sensitive content.
Can I customize the message format? Absolutely. All messages are simple strings in the code. Modify them to match your team's style and needs.
Related articles: Automate Slack Workflows with Python, Python Email Automation Guide
Sponsored Content
Interested in advertising? Reach automation professionals through our platform.
