A smart serverless weather bot that automatically sends Slack messages when weather conditions are favorable for outdoor lunch meetups. Built with TypeScript, AWS Lambda, and deployed with OpenTofu.
- Automatic Weather Monitoring: Checks weather conditions every weekday at 10 AM CEST
- Smart Weather Logic: Only sends messages when weather is good (>14Β°C, sunny/cloudy)
- Weather Warnings: Optional notifications when weather is poor (opt-in feature)
- Rate Limiting: Maximum 2 messages per week per message type (tracked in DynamoDB)
- Reply API Endpoint: Team members can confirm lunch meetings and manage notification preferences
- Manual Testing: Trigger from AWS Console with customizable parameters
- Secure Storage: Webhook URL stored in AWS Secrets Manager for security
- Multiple Teams: Support for multiple deployments with different configurations
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β EventBridge βββββΆβ Weather Check βββββΆβ Slack Webhook β
β (Scheduler) β β Lambda β β β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β DynamoDB β βββββββββββββββββββ
β (Message Store) ββββββ Team Members β
β + Preferences β β (Reply API) β
βββββββββββββββββββ βββββββββββββββββββ
β² β
β βΌ
βββββββββββββββββββ β
β Reply Lambda βββββββββββββββ
β (Confirm + β βββββββββββββββββββ
β Preferences) ββββββ API Gateway β
βββββββββββββββββββ β (/reply) β
βββββββββββββββββββ
β
βββββββββββββββββββ
β Secrets Manager β
β (Webhook URL) β
βββββββββββββββββββ
- Node.js 22.x or higher
- AWS CLI configured with appropriate permissions
- OpenTofu >= 1.0
- A Slack workspace with webhook URL
git clone <repository-url>
cd lunch-slack-bot
npm installUse the automated setup script:
cd terraform
chmod +x setup-bot.sh
./setup-bot.shThe script will guide you through:
- β
Creating
terraform.tfvarswith your configuration - β Setting up remote state backend (S3 + DynamoDB)
- β Building and deploying the application
- β Creating AWS Secrets Manager secret for webhook URL
- β Providing testing instructions
If you prefer manual setup:
-
Configure OpenTofu variables:
cd terraform cp terraform.tfvars.example terraform.tfvars # Edit terraform.tfvars with your configuration
-
Set up terraform.tfvars:
# Location settings location_name = "Munich" location_lat = 48.1351 location_lon = 11.5820 # Weather settings min_temperature = 14 # Minimum temperature for good weather (Β°C) # Slack settings slack_channel = "#general" # Channel identifier for personalized messages # AWS settings aws_region = "eu-central-1" # Optional: for multiple deployments deployment_suffix = "team-alpha"
-
Deploy infrastructure:
# Build the application (from project root) npm run build # Deploy to AWS (from terraform directory) cd terraform tofu init tofu plan tofu apply
-
Set up Slack webhook secret:
# After deployment, set the webhook URL in Secrets Manager aws secretsmanager put-secret-value \ --secret-id "lunch-bot/slack-webhook" \ --secret-string '{"webhook_url": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"}'
β οΈ Security Note: The Slack webhook URL is now stored in AWS Secrets Manager for enhanced security. Never commit webhook URLs to version control!
To deploy multiple instances for different teams or channels:
Using the setup script (recommended):
# For each team, create a new workspace
tofu workspace new team-alpha
./setup-bot.sh # Script will prompt for team-specific settings
tofu workspace new team-beta
./setup-bot.sh # Configure for team betaManual approach:
# Team Alpha configuration
deployment_suffix = "team-alpha"
location_name = "Munich"
location_lat = 48.1351
location_lon = 11.5820
# Team Beta configuration (different workspace)
tofu workspace new team-beta
deployment_suffix = "team-beta"
location_name = "Berlin"
location_lat = 52.5200
location_lon = 13.4050This creates separate resources for each team:
- Lambda functions:
lunch-weather-bot-team-alpha-weather-check - DynamoDB tables:
lunch-weather-bot-team-alpha-message-tracking - Secrets:
lunch-bot-team-alpha/slack-webhook
The bot provides a comprehensive Reply API that allows team members to interact with the system and manage their notification preferences.
After deployment, OpenTofu outputs the API URL:
https://[api-id].execute-api.[region].amazonaws.com/prod/reply
Confirm that your team is meeting for lunch, which stops further weather reminders for the current week:
# Basic lunch confirmation
curl -X POST https://[api-id].execute-api.eu-central-1.amazonaws.com/prod/reply \
-H "Content-Type: application/json" \
-d '{"action": "confirm-lunch"}'
# Confirm lunch for specific location
curl -X POST https://[api-id].execute-api.eu-central-1.amazonaws.com/prod/reply \
-H "Content-Type: application/json" \
-d '{"action": "confirm-lunch", "location": "Berlin"}'
# GET request alternative
curl "https://[api-id].execute-api.eu-central-1.amazonaws.com/prod/reply?action=confirm-lunch&location=Munich"Receive notifications when the weather is poor for outdoor lunch:
curl -X POST https://[api-id].execute-api.eu-central-1.amazonaws.com/prod/reply \
-H "Content-Type: application/json" \
-d '{"action": "opt-in-warnings", "location": "Munich"}'Stop receiving bad weather notifications:
curl -X POST https://[api-id].execute-api.eu-central-1.amazonaws.com/prod/reply \
-H "Content-Type: application/json" \
-d '{"action": "opt-out-warnings", "location": "Munich"}'Lunch Confirmation (New):
{
"message": "Thanks for confirming! Lunch confirmed for this week. No more weather reminders will be sent.",
"action": "confirm-lunch",
"location": "Munich",
"confirmed": true,
"config": {
"locationName": "Munich",
"minTemperature": 14,
"awsRegion": "eu-central-1",
"slackWebhookUrl": "[REDACTED]"
}
}Already Confirmed:
{
"message": "Lunch already confirmed this week! No more weather reminders will be sent.",
"location": "Munich",
"alreadyConfirmed": true
}Weather Warnings Opt-in:
{
"message": "Successfully opted in to weather warnings. You will now receive notifications when the weather is not suitable for outdoor lunch.",
"action": "opt-in-warnings",
"location": "Munich",
"optedIn": true
}Weather Warnings Opt-out:
{
"message": "Successfully opted out of weather warnings. You will no longer receive notifications about bad weather.",
"action": "opt-out-warnings",
"location": "Munich",
"optedIn": false
}- Slack Slash Command: Create a Slack app that calls this endpoint
- Simple Web Form: Build a basic HTML form for team members
- Mobile App: Integrate into your team's mobile app
- Scheduled Call: Automatically confirm if calendar shows a lunch meeting
The bot considers weather "good" when:
- Temperature > 14Β°C at specified hour (default: noon)
- Conditions are clear or partly cloudy (not overcast or heavily cloudy)
- No rain, thunderstorms, or snow
Weather warnings are sent when:
- Temperature β€ 14Β°C OR bad weather conditions
- Location has opted in to receive warnings
- Weekly message limits haven't been exceeded
The minimum temperature threshold is now configurable through:
-
Terraform variable (recommended for deployment):
min_temperature = 16 # Set in terraform.tfvars
-
Runtime overrides (for testing):
{ "overrides": { "minTemperature": 18 } } -
Default: 14Β°C (if not specified)
Override other weather settings at runtime through event parameters:
// Available weather configuration overrides
goodWeatherConditions: overrides?.goodWeatherConditions ?? ['clear', 'partly-cloudy'],
badWeatherConditions: overrides?.badWeatherConditions ?? ['rain', 'drizzle', 'thunderstorm', 'snow'],
weatherCheckHour: overrides?.weatherCheckHour ?? 12, // Default to noon- Weather Reminders: Maximum 2 per week (when weather is good)
- Weather Warnings: Maximum 2 per week (when weather is poor, opt-in only)
- Tracking: DynamoDB table with automatic cleanup after 30 days
- Logic: Separate limits for each message type and location
Default schedule: Weekdays at 10 AM CEST (8 AM UTC)
Modify in terraform/main.tf:
resource "aws_cloudwatch_event_rule" "weather_check_schedule" {
schedule_expression = "cron(0 8 ? * MON-FRI *)"
}You can manually trigger the lambda function from the AWS Console with customizable parameters:
- Go to AWS Console β Lambda β
lunch-weather-bot-weather-check - Click "Test" button
- Create a test event with optional parameter overrides
All parameters are optional and fall back to defaults if not provided:
{
"overrides": {
"locationName": "Berlin",
"locationLat": 52.52,
"locationLon": 13.405,
"dynamodbTableName": "my-custom-table",
"minTemperature": 15,
"goodWeatherConditions": ["clear", "partly-cloudy"],
"badWeatherConditions": ["rain", "drizzle", "snow"],
"weatherCheckHour": 14
}
}Test different location:
{
"overrides": {
"locationName": "Paris",
"locationLat": 48.8566,
"locationLon": 2.3522
}
}Test with relaxed weather criteria:
{
"overrides": {
"minTemperature": 8,
"goodWeatherConditions": ["clear", "partly-cloudy", "rain"],
"badWeatherConditions": ["thunderstorm", "snow"]
}
}Test for evening weather (6 PM):
{
"overrides": {
"weatherCheckHour": 18
}
}Use all defaults:
{}- Go to Slack API
- Create a new app or use existing one
- Go to "Incoming Webhooks" and activate it
- Click "Add New Webhook to Workspace"
- Choose your channel and copy the webhook URL
- Store the webhook URL in AWS Secrets Manager (see deployment instructions)
- AWS Secrets Manager: Webhook URL stored securely, not in configuration files
- IAM Policies: Least-privilege access for Lambda functions
- Encrypted Storage: DynamoDB encryption at rest
- VPC Support: Optional VPC deployment for enhanced network security
The webhook URL is stored in AWS Secrets Manager with the following structure:
{
"webhook_url": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
}Setting the secret:
aws secretsmanager put-secret-value \
--secret-id "lunch-bot/slack-webhook" \
--secret-string '{"webhook_url": "https://hooks.slack.com/services/T123/B456/xyz"}'For multiple teams:
# Team Alpha
aws secretsmanager put-secret-value \
--secret-id "lunch-bot-team-alpha/slack-webhook" \
--secret-string '{"webhook_url": "https://hooks.slack.com/services/T123/B456/alpha"}'
# Team Beta
aws secretsmanager put-secret-value \
--secret-id "lunch-bot-team-beta/slack-webhook" \
--secret-string '{"webhook_url": "https://hooks.slack.com/services/T123/B789/beta"}'- Weather Check:
/aws/lambda/lunch-weather-bot-weather-check - Reply API:
/aws/lambda/lunch-weather-bot-reply
- Message Tracking:
lunch-weather-bot-message-tracking - Stores: Message history, lunch confirmations, weather warning preferences
- Rule:
lunch-weather-bot-schedule - Target: Weather check Lambda function
- Name:
lunch-weather-bot-api - Endpoint:
/reply(POST, GET, OPTIONS)
- Lambda execution duration and errors
- DynamoDB read/write capacity utilization
- API Gateway request count and latency
- Weather API call success rate
# Install dependencies
npm install
# Run tests with watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverage
# Build for deployment
npm run build
# Development build with watch mode
npm run build:dev
# Code quality checks
npm run cq # Run all quality checks
npm run cq:lint # ESLint
npm run cq:format # Prettier
npm run cq:type-check # TypeScriptsrc/
βββ handlers/ # Lambda function handlers
β βββ weather-check.ts # Main weather checking logic
β βββ reply.ts # Reply API handler (lunch + preferences)
β βββ *.spec.ts # Test files
βββ implementations/ # Concrete implementations
β βββ dynamodb-storage.ts # DynamoDB operations
β βββ webhook-slack.ts # Slack webhook integration
β βββ openmeteo-api.ts # Weather API client
β βββ secrets-manager-client.ts # AWS Secrets Manager client
β βββ fetch-http-client.ts # HTTP client implementation
βββ interfaces/ # TypeScript interfaces
β βββ storage.interface.ts # Storage operations
β βββ weather-api.interface.ts # Weather API contract
β βββ webhook-slack.interface.ts # Slack webhook contract
β βββ http-client.interface.ts # HTTP client contract
βββ schemas/ # Zod validation schemas
β βββ weather.schema.ts # Weather data validation
β βββ openmeteo.schema.ts # OpenMeteo API response validation
βββ services/ # Business logic
β βββ weather.service.ts # Weather processing logic
βββ types/ # TypeScript type definitions
β βββ index.ts # Common types
βββ utils/ # Utility functions
βββ env.ts # Environment configuration
βββ constants.ts # Application constants
βββ coordinates.ts # Location utilities
terraform/ # Infrastructure as Code
βββ main.tf # Main resources
βββ lambda.tf # Lambda functions
βββ api-gateway.tf # API Gateway configuration
βββ dynamodb.tf # DynamoDB table
βββ secrets.tf # AWS Secrets Manager
βββ variables.tf # Input variables
βββ outputs.tf # Output values
βββ setup-bot.sh # Complete setup script
βββ setup-state-backend.md # Manual backend setup docs
| Variable | Description | Default |
|---|---|---|
SLACK_WEBHOOK_SECRET_ARN |
Secrets Manager ARN | Auto-set |
LOCATION_NAME |
Location name | Munich |
LOCATION_LAT |
Latitude | 48.1351 |
LOCATION_LON |
Longitude | 11.5820 |
AWS_REGION |
AWS region | eu-central-1 |
ENABLE_WEATHER_WARNINGS |
Enable weather warnings | false |
DYNAMODB_TABLE_NAME |
DynamoDB table name | Auto-set |
REPLY_API_URL |
Reply API endpoint URL | Auto-set |
SLACK_CHANNEL |
Slack channel for messages | "#general" |
| Variable | Description | Default |
|---|---|---|
location_name |
Location name for weather checks | Munich |
location_lat |
Latitude coordinate | 48.1351 |
location_lon |
Longitude coordinate | 11.5820 |
min_temperature |
Minimum temperature for good weather (Β°C) | 14 |
enable_weather_warnings |
Enable weather warning messages | false |
aws_region |
AWS region for deployment | eu-central-1 |
deployment_suffix |
Suffix for resource names | "" (empty) |
environment |
Environment name | prod |
lambda_timeout |
Lambda timeout in seconds | 60 |
lambda_memory |
Lambda memory in MB | 256 |
log_retention_days |
CloudWatch log retention | 14 |
GPL-3.0 License - see LICENSE file for details.
