Skip to content

danieladarve/standup-bot

Repository files navigation

Standup Bot

Slack bot that automates async standups for distributed teams. Every morning it DMs each team member three questions, collects responses, then uses an LLM (Claude or Ollama/Qwen) to summarise everything into a daily digest posted to a designated channel.

How it works

  1. Morning - EventBridge triggers the prompt handler, which DMs each team member via Slack
  2. Team replies - Members reply in the DM thread with a numbered list (what they did, what they're doing, blockers)
  3. Digest time - EventBridge triggers the digest handler, which collects responses, generates an AI summary, and posts a formatted digest to the team's Slack channel

Architecture

EventBridge (cron)  -->  Lambda: prompt handler  -->  Slack DMs
Slack events        -->  API Gateway  -->  Lambda: event handler  -->  DynamoDB
EventBridge (cron)  -->  Lambda: digest handler  -->  Claude/Ollama  -->  Slack channel

Three Lambda handlers share a single DynamoDB table using a single-table design:

PK SK Data
TEAM#{teamId} CONFIG Team configuration
TEAM#{teamId} RESPONSE#{date}#{userId} Standup response
TEAM#{teamId} DIGEST#{date} Generated digest
THREAD#{threadTs} MAPPING Thread-to-user mapping

A GSI on SK enables efficient team config lookups without full table scans.

Tech stack

  • Runtime: Node.js 24.x on AWS Lambda
  • Language: TypeScript 5.x
  • Slack: @slack/bolt + @slack/web-api
  • AI: Claude Haiku (production) or Ollama/Qwen (local dev)
  • Database: DynamoDB (single table, PAY_PER_REQUEST, GSI on SK)
  • Infra: AWS CDK, EventBridge, API Gateway (with throttling)
  • Secrets: SSM Parameter Store (SecureString)
  • Validation: Zod
  • Testing: Vitest
  • Bundling: esbuild

Project structure

src/
  handlers/
    prompt.ts           # Cron: send standup DMs to team members
    event.ts            # HTTP: receive and store Slack responses
    digest.ts           # Cron: generate and post daily summary
  services/
    slack.ts            # Slack API wrapper
    claude.ts           # Claude summarisation service
    ollama.ts           # Ollama/Qwen summarisation service
    dynamo.ts           # DynamoDB operations
  schemas/
    standup.ts          # Zod schemas for responses, config, digest
    slack-events.ts     # Zod schemas for Slack event payloads
  lib/
    config.ts           # Environment variable validation
    constants.ts        # Shared constants (limits, TTLs, timeouts)
    date.ts             # Timezone-aware date helper
    format.ts           # Slack Block Kit message builders
    llm-parser.ts       # Shared LLM response parser
    logger.ts           # Structured JSON logger
    prompts.ts          # LLM prompt templates
  types/
    index.ts            # Shared type definitions
infra/
  bin/app.ts            # CDK app entry point
  lib/standup-bot-stack.ts  # CDK stack
scripts/
  setup-local-db.ts     # Create local DynamoDB table
  seed.ts               # Seed sample team config and responses
  local-invoke.ts       # Invoke handlers locally
tests/
  handlers/             # Handler unit tests
  services/             # Service unit tests
  fixtures/             # Test data

Setup

Prerequisites

  • Node.js 24.x
  • AWS account with CDK bootstrapped
  • Slack workspace with admin access
  • Docker (for local development)

Install

npm install

Environment variables

Variable Required Default Description
SLACK_BOT_TOKEN Yes Bot OAuth token (xoxb-...)
SLACK_SIGNING_SECRET Yes Slack app signing secret
DYNAMODB_TABLE_NAME Yes Set automatically by CDK
LLM_PROVIDER No claude claude or ollama
ANTHROPIC_API_KEY When claude Claude API key (sk-ant-...)
OLLAMA_BASE_URL When ollama http://localhost:11434 Ollama server URL
OLLAMA_MODEL When ollama qwen3:8b Ollama model name
DYNAMODB_ENDPOINT No Set for local DynamoDB

Slack app setup

  1. Create a new Slack app at https://api.slack.com/apps
  2. Add bot scopes: chat:write, im:write, im:read, im:history, users:read, reactions:write
  3. Subscribe to events: message.im
  4. Set the request URL to your API Gateway endpoint: https://{api-gw-url}/slack/events
  5. Install the app to your workspace
  6. Invite the bot to the digest channel

Deploy

Store secrets in SSM Parameter Store as SecureString:

aws ssm put-parameter --name /standup-bot/slack-bot-token --type SecureString --value "xoxb-..."
aws ssm put-parameter --name /standup-bot/slack-signing-secret --type SecureString --value "..."
aws ssm put-parameter --name /standup-bot/anthropic-api-key --type SecureString --value "sk-ant-..."

Then deploy:

npm run build       # Bundle with esbuild
npx cdk bootstrap   # First time only
npx cdk deploy      # Deploy stack

Local development

Start local services

npm run local:db      # Start DynamoDB Local (Docker) + create table
npm run local:seed    # Seed sample team config and responses

Run handlers locally

npm run local:prompt  # Send standup DMs (needs real Slack token)
npm run local:digest  # Generate digest (needs Slack token + LLM)
npm run local:event   # Simulate a Slack event

Use Ollama for local summarisation

ollama pull qwen3:8b
LLM_PROVIDER=ollama npm run local:digest

Run tests

npm test              # Run tests
npm run test:watch    # Watch mode
npm run test:coverage # Coverage report
npm run synth         # CDK synth (dry run)

Cost estimate

For a small team (5-10 members):

Service Monthly cost
Lambda < $1
DynamoDB < $1
API Gateway < $1
Claude Haiku ~$0.50
Total < $5

DynamoDB records auto-expire after 90 days via TTL.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors