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.
- Morning - EventBridge triggers the prompt handler, which DMs each team member via Slack
- Team replies - Members reply in the DM thread with a numbered list (what they did, what they're doing, blockers)
- 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
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.
- 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
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
- Node.js 24.x
- AWS account with CDK bootstrapped
- Slack workspace with admin access
- Docker (for local development)
npm install| 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 |
- Create a new Slack app at https://api.slack.com/apps
- Add bot scopes:
chat:write,im:write,im:read,im:history,users:read,reactions:write - Subscribe to events:
message.im - Set the request URL to your API Gateway endpoint:
https://{api-gw-url}/slack/events - Install the app to your workspace
- Invite the bot to the digest channel
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 stacknpm run local:db # Start DynamoDB Local (Docker) + create table
npm run local:seed # Seed sample team config and responsesnpm 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 eventollama pull qwen3:8b
LLM_PROVIDER=ollama npm run local:digestnpm test # Run tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage report
npm run synth # CDK synth (dry run)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.