# Expense Approval Bot Slack reaction-driven expense approval router. Replaces the Pipedream `expenses_pipeline` workflow. ## Flow A user reacts ✅ to a message in an expense channel. The bot: 1. Maps the source channel to the next stage's channel. 2. Copies the message text (stripped of any prior "react to advance" hint) into the next channel, with a permalink back to the original on the first advance. 3. Replies in-thread on the original message with `➡️ Advanced to {Stage}`. Channel chain: Submitted → Processed → Authorized → Matched. ## Architecture - **Receiver Lambda** (`src/receiver/`) — fronted by API Gateway HTTP API. Verifies the Slack request signature, handles the URL-verification handshake, and async-invokes the processor. Returns 200 within Slack's 3-second window to prevent retries. - **Processor Lambda** (`src/processor/`) — does the 5-step sequence of Slack API calls. No inbound HTTP; invoked only by the receiver. - **Secrets** — Slack bot token in AWS Secrets Manager. Signing secret passed as a SAM template parameter (`NoEcho`). Runtime: Python 3.12 on arm64, no third-party dependencies (stdlib `urllib` for HTTP, `boto3` from the Lambda runtime). ## Setup 1. Store the Slack bot token: ```bash aws secretsmanager create-secret \ --name expense-bot-slack-token \ --secret-string "xoxb-..." ``` 2. Copy the sample SAM config and fill in real values: ```bash cp samconfig.toml.example samconfig.toml ``` - `SlackBotTokenSecretArn` — ARN from step 1 - `SlackSigningSecret` — Slack app → Basic Information → Signing Secret 3. Build and deploy: ```bash sam build && sam deploy ``` 4. After the first deploy, take the `SlackEventsUrl` output and paste it into the Slack app's **Event Subscriptions → Request URL**. Subscribe the bot to the `reaction_added` event. ## Configuration The channel → stage mapping lives in `src/processor/app.py` (`STAGES`). Update there if channels change. The "on first advance, include a permalink" check is keyed on the Processed-stage channel ID — update that too if the first-stage channel changes. ## Manual testing Invoke the processor directly with a synthetic Slack reaction event: ```bash aws lambda invoke \ --function-name expense-approval-processor \ --payload '{"reaction":"white_check_mark","item":{"channel":"C0AQ2AWLNEN","ts":"1700000000.000000"}}' \ /dev/stdout ```