MetorialDocs

PM Slack standup bot

Create an AI-powered bot that collects daily standup responses, analyzes team progress, and posts executive summaries

Build an AI-powered Slack standup bot that posts daily standup prompts to your team channel, collects responses in organized threads, analyzes progress and blockers, and posts executive summaries to leadership channels — tracking recurring blockers and patterns over time.

What you'll learn

  • Configuring the Slack provider
  • Setting up OAuth for Slack workspace access
  • Creating an AI agent that processes team updates
  • Posting and reading Slack messages programmatically

Before you begin

Prerequisites

Before building the standup bot, ensure you have:

  1. Metorial setup:

    • Active Metorial account at platform.metorial.com
    • Project created in your organization
    • Metorial API key (generate in Dashboard → Developer → API Keys)
  2. Slack workspace:

    • Admin access to install apps
    • Channel where standups will be posted
    • Leadership/executive channel for summaries (optional)
  3. AI provider:

    • Anthropic API key (Claude Sonnet 4 or newer recommended for analysis)
  4. Development environment:

    • Node.js 18+ (TypeScript) or Python 3.9+ installed
    • Basic knowledge of async/await patterns

Architecture overview

The standup bot workflow:

  1. Trigger: Bot posts standup prompt to team channel (scheduled or manual)
  2. Collection: Team members reply in thread with their updates
  3. Analysis: AI analyzes all responses for:
    • Individual progress and accomplishments
    • Blockers and dependencies between team members
    • Team sentiment and morale
    • Recurring issues
  4. Summary: Bot generates and posts executive summary to leadership channel
  5. Storage: Optionally stores historical data for trend analysis

Tools used: Slack provider (post messages, read threads) + AI Model (analysis and summarization)

Step 1: Configure Slack provider

Configure the Slack provider from Metorial's catalog to enable your bot to interact with Slack.

Navigate to provider catalog

In the Metorial Dashboard, go to Providers and search for "Slack".

Create a Slack integration

Click the Slack provider, then click Use ProviderIntegration.

Choose the Slack auth method, create or select auth credentials, and review tool filters for message and channel access.

Note your deployment ID

After setup, copy the provider deployment or integration ID shown in the dashboard. You'll need this for OAuth setup and in your bot code.

Info

Save your Slack deployment ID—you'll need it for OAuth setup (Step 2) and in your bot code (Step 3).

Step 2: Set up OAuth authentication

Your standup bot needs permission to post messages and read thread replies in your Slack workspace.

Install dependencies

Install the Metorial SDK and Anthropic:

npm install metorial @metorial/anthropic @anthropic-ai/sdk

Create OAuth session

Run this code to generate the Slack OAuth URL:

import { Metorial } from 'metorial';

const metorial = new Metorial({
  apiKey: "YOUR-METORIAL-API-KEY"
});

async function setupSlackOAuth() {
  const slackSetup = await metorial.providers.setupSessions.create({
    providerId: 'YOUR-SLACK-PROVIDER-ID',
    providerAuthMethodId: 'oauth'
  });

  console.log('Authorize Slack here:', slackSetup.url);

  // Wait for authorization
  const completed = await metorial.waitForSetupSession([slackSetup]);
  console.log('✓ Slack authorized!');

  // Save the auth config ID for future use
  return completed.authConfig.id;
}

setupSlackOAuth();
import asyncio
from metorial import Metorial

async def setup_slack_oauth():
    metorial = Metorial(api_key="YOUR-METORIAL-API-KEY")

    slack_setup = await metorial.providers.setup_sessions.create(
        provider_id="YOUR-SLACK-PROVIDER-ID",
        provider_auth_method_id="oauth"
    )

    print(f"Authorize Slack here: {slack_setup.url}")

    # Wait for authorization
    completed = await metorial.wait_for_setup_session(slack_setup)
    print("✓ Slack authorized!")

    # Save the auth config ID for future use
    return completed.auth_config.id

asyncio.run(setup_slack_oauth())

Authorize in browser

  1. Open the printed OAuth URL in your browser
  2. Sign in to Slack if needed
  3. Review and approve the permissions (the bot needs to post messages and read channels)
  4. You'll be redirected to your callback URL (or see a confirmation page)

Store auth config ID

Save the auth config ID securely. You'll reuse it for all future bot operations without re-authorizing.

For production apps, store auth config IDs in your database or environment variables.

Note

Required OAuth Scopes:

The Slack provider requires these scopes:

  • chat:write - Post messages to channels
  • channels:read - Read public channel information
  • channels:history - Read message history to collect thread replies
  • users:read - Get user information for mentions

The required scopes are automatically requested when you authorize via the OAuth URL.

Step 3: Build the standup bot

Create the main bot that collects standup responses and generates summaries.

import { Metorial } from 'metorial';
import { metorialAnthropic } from '@metorial/anthropic';
import Anthropic from '@anthropic-ai/sdk';

const metorial = new Metorial({
  apiKey: "YOUR-METORIAL-API-KEY"
});

const anthropic = new Anthropic({
  apiKey: "YOUR_ANTHROPIC_API_KEY"
});

// Slack integration credentials
const SLACK_DEPLOYMENT_ID = "YOUR_SLACK_DEPLOYMENT_ID";
const SLACK_AUTH_CONFIG_ID = "YOUR_SLACK_AUTH_CONFIG_ID";

async function runStandup(
  channelId: string,
  summaryChannelId: string,
  collectionTimeMinutes: number = 5
) {
  console.log(`Starting standup in channel ${channelId}`);

  // Post standup prompt
  const standupPrompt = `Good morning team! Time for daily standup. Please reply to this thread with:

1. What you accomplished yesterday
2. What you're working on today
3. Any blockers or help needed

You have ${collectionTimeMinutes} minutes to respond.`;

  await metorial.withProviderSession(
    metorialAnthropic,
    {
      providers: [
        {
          providerDeploymentId: SLACK_DEPLOYMENT_ID,
          providerAuthConfigId: SLACK_AUTH_CONFIG_ID
        }
      ]
    },
    async session => {
      // Post the standup prompt
      const messages: Anthropic.MessageParam[] = [
        {
          role: 'user',
          content: `Post a message to Slack channel ${channelId} with this text:

"${standupPrompt}"

Use the chat_postMessage tool.`
        }
      ];

      console.log('Posting standup prompt...');
      let response = await anthropic.messages.create({
        model: 'claude-sonnet-4-5',
        max_tokens: 4096,
        tools: session.tools,
        messages
      });

      // Handle tool calls
      while (response.stop_reason === 'tool_use') {
        const toolUseBlocks = response.content.filter(
          (block): block is Anthropic.ToolUseBlock => block.type === 'tool_use'
        );

        const toolResults = await session.callTools(toolUseBlocks);

        messages.push({ role: 'assistant', content: response.content });
        messages.push(toolResults);

        response = await anthropic.messages.create({
          model: 'claude-sonnet-4-5',
          max_tokens: 4096,
          messages,
          tools: session.tools
        });
      }

      console.log('Standup prompt posted successfully!');
      console.log(`Waiting ${collectionTimeMinutes} minutes for responses...`);

      // Wait for responses
      await new Promise(resolve => setTimeout(resolve, collectionTimeMinutes * 60 * 1000));

      console.log('Collecting and analyzing responses...');

      messages.push({
        role: 'user',
        content: `Collect standup responses and post summary:

1. List recent messages in channel ${channelId} to find the standup prompt
2. Get thread replies for that message
3. Analyze ONLY actual user responses (skip bot's prompt)
4. Post executive summary to ${summaryChannelId}

Summary format:
- Team Progress: Actual accomplishments mentioned
- Today's Focus: Actual plans shared
- Blockers: Actual issues raised
- Action Items: Specific help requested

CRITICAL: Base summary ONLY on real messages. If no responses, say "No responses received." DO NOT invent content.`
      });

      response = await anthropic.messages.create({
        model: 'claude-sonnet-4-5',
        max_tokens: 8192,
        tools: session.tools,
        messages
      });

      // Agentic loop
      while (response.stop_reason === 'tool_use') {
        const toolUseBlocks = response.content.filter(
          (block): block is Anthropic.ToolUseBlock => block.type === 'tool_use'
        );

        console.log(`Executing ${toolUseBlocks.length} tool(s)...`);

        const toolResults = await session.callTools(toolUseBlocks);

        messages.push({ role: 'assistant', content: response.content });
        messages.push(toolResults);

        response = await anthropic.messages.create({
          model: 'claude-sonnet-4-5',
          max_tokens: 8192,
          messages,
          tools: session.tools
        });
      }

      // Get final summary
      const finalText = response.content
        .filter((block): block is Anthropic.TextBlock => block.type === 'text')
        .map(block => block.text)
        .join('\n');

      console.log(`Standup complete: ${finalText}`);
    }
  );
}

// Example usage
runStandup(
  'C1234567890',  // Your team channel ID
  'C0987654321',  // Your executive channel ID
  5               // Minutes to wait for responses
);

What this code does:

  1. Posts standup prompt to the team channel with clear instructions
  2. Waits for responses (configurable time)
  3. Collects all thread replies using the Slack provider
  4. AI analyzes responses for progress, blockers, and dependencies
  5. Generates executive summary with key insights
  6. Posts summary to leadership channel
  7. Uses agentic workflow - AI decides which Slack tools to call and when

Info

This uses Claude's agentic capabilities—the AI decides when to read the thread, how to analyze the data, and when to post the summary. You don't need to write explicit logic for parsing responses or formatting summaries.

Step 4: Test the bot

Let's test the bot with example standup responses.

Scenario: Run standup in a test channel with your team.

Example responses:

@alice: Yesterday finished the user auth feature. Today working on password reset. No blockers.

@bob: Completed API endpoints for /users. Today starting the /products endpoints. Blocked on database schema approval.

@charlie: Fixed 5 bugs in the dashboard. Today continuing bug fixes. Could use help reviewing PR #234.

Run the bot:

runStandup(
  'C1234567890',  // Team channel ID
  'C0987654321',  // Executive channel ID
  5               // Wait 5 minutes
);

Expected behavior:

  1. Bot posts standup prompt to team channel
  2. Team members reply in thread
  3. After 5 minutes, bot collects all responses
  4. AI analyzes and generates summary:
📊 Daily Standup Summary - [Date]

✅ Team Progress (Yesterday):
• Alice: Completed user authentication feature
• Bob: Finished API endpoints for /users module
• Charlie: Resolved 5 dashboard bugs

🎯 Today's Focus:
• Alice: Password reset functionality
• Bob: /products API endpoints
• Charlie: Continue bug fixing

🚧 Blockers & Dependencies:
• Bob: Waiting on database schema approval (blocking /products work)
• Charlie: Needs code review on PR #234

📈 Team Health: Positive momentum, steady progress

⚠️ Action Items:
1. Expedite database schema approval for Bob
2. Assign reviewer to PR #234 for Charlie
  1. Summary is posted to executive channel

Troubleshooting

Common issues and solutions when building your standup bot:

Note

If you encounter errors not covered here, check the Metorial dashboard logs (Monitoring section) to see detailed tool execution traces and Slack API responses.

Advanced customization

Enhance your standup bot with these customizations:

Custom questions

Customize standup questions for your team's needs (e.g., "What are you learning today?", "Team shoutouts", "Health check: 1-5").

Multi-team support

Run standups across multiple teams with different channels and schedules. Store team configs in a database.

Trend tracking

Store historical standup data to track:

  • Recurring blockers
  • Team velocity trends
  • Common challenges Generate weekly/monthly reports
Reminder system

Send DMs to team members who haven't responded. Use Slack's users.list to track participation rates.

Project integration

Connect with project management tools (Linear, Jira) to:

  • Link updates to specific tasks
  • Auto-update task status
  • Cross-reference blockers with tickets
Sentiment analysis

Track team morale over time by analyzing sentiment in standup responses. Alert leadership to significant drops.

Example: Custom questions

Update the standup prompt:

const standupPrompt = `Good morning team! 🌅 Daily standup time:

1. 🎯 What are you working on today?
2. 🚧 Any blockers?
3. 💡 What are you learning?
4. 🙌 Team shoutouts (optional)
5. ❤️ Health check: 1-5 (1=struggling, 5=great)

Reply in thread, you have ${collectionTimeMinutes} minutes.`;

Production considerations

Before deploying to production:

  1. Scheduling: Set up daily automated runs:

    • Use cron jobs or cloud schedulers (AWS EventBridge, GCP Cloud Scheduler)
    • Typical schedule: 9:00 AM team local time, Monday-Friday
    • Consider time zones for distributed teams
  2. Error Handling: Add robust error handling:

    • Retry failed Slack API calls with exponential backoff
    • Alert admins if standup fails to post or collect responses
    • Handle missing or malformed responses gracefully
  3. Non-Responders: Send reminders:

    • Track who responded vs. who didn't
    • Send DM reminders 2-3 minutes before deadline
    • Include non-responder list in summary for follow-up
  4. Data Storage: Store historical data:

    • Save standup responses and summaries to database
    • Track participation rates over time
    • Enable trend analysis and reporting
  5. Privacy & Security:

    • Store OAuth tokens securely (environment variables, secret managers)
    • Be mindful of sensitive information in standups
    • Consider data retention policies for historical standups
    • Allow team members to edit/delete their responses
  6. Customization per Team:

    • Store team configs (channel IDs, questions, timing)
    • Allow teams to opt-in/opt-out
    • Support different schedules for different teams
  7. Performance:

    • Cache Slack user info to reduce API calls
    • Implement request queuing for multiple teams
    • Monitor token usage and costs
  8. Testing:

    • Test in a sandbox channel first
    • Have a manual override to skip days (holidays, etc.)
    • Implement dry-run mode for testing prompts

Info

Scheduling Tip:

Use a scheduling service to trigger the bot daily:

// Example with node-cron
import cron from 'node-cron';

// Run at 9 AM Monday-Friday
cron.schedule('0 9 * * 1-5', () => {
  runStandup('C1234567890', 'C0987654321', 5);
}, {
  timezone: "America/New_York"
});