Build a Slack bot that orchestrates your entire deployment lifecycle in the workspace your team already uses daily.
This guide walks you through a Slack bot that orchestrates the entire deploy lifecycle from a single slash command. Type /deploy staging and the bot:
- Dispatches a GitHub Actions workflow
- Polls the run until it completes
- Comments on the relevant PR(s)
- Updates linked Linear issue(s)
- Posts a summary card back to Slack
For production deploys, the bot gates the workflow with an approval step, so the deploy proceeds only after an authorized team member approves it.
The bot is built with Chat SDK and Vercel Workflow. Chat SDK handles the Slack interaction layer (cards, buttons, modals, and slash commands), while Vercel Workflow handles stateful orchestration (pausing for approval, polling GitHub, and resuming when events arrive). You write the deploy pipeline as a single function that pauses and resumes over minutes or hours without a database or state machine.
Deploy the template now, or read on for a deeper look at how it all works.
Chat SDK Deploy Bot Template
Trigger deploys from Slack, gate production with approval, dispatch GitHub Actions, and notify GitHub and Linear when the run finishes.
You'll need accounts with these services:
- Slack for the bot interface. Create a new app at api.slack.com/apps.
- GitHub for workflow dispatch. You'll need a fine-grained personal access token for the target repository.
- Redis for Chat SDK state and Vercel Workflow. Any Redis provider works. Upstash supports serverless deployments and has a free tier.
- Linear (optional) for issue tracking. Set
LINEAR_API_KEYto enable it.
- Create a new Slack app from a manifest at api.slack.com/apps. Use the slack-manifest.json file included in the template repo. Replace the
https://example.comURLs with your production domain (e.g.https://your-app.vercel.app/api/webhooks/slack). - Install the app in your workspace and copy the Bot User OAuth Token.
- Copy the Signing Secret from the Basic Information page.
- Create a fine-grained personal access token for the target repository with these permissions:
- Actions: read and write
- Contents: read
- Issues: write
- Pull requests: read
- Configure the token for a repository that has a workflow triggered with
workflow_dispatch. Here's an example:
The deploy_id input is optional, but including it in run-name helps the bot reliably match the run it dispatched against other concurrent runs.
If you want the bot to comment on GitHub PRs as a thread (with webhook-driven replies):
- Add a repository webhook pointing at
https://<your-domain>/api/webhooks/github - Set the content type to
application/json - Use the same secret as
GITHUB_WEBHOOK_SECRET - Subscribe to
issue_commentandpull_request_review_commentevents
Set LINEAR_API_KEY to enable Linear integration. No separate webhook setup is required.
The bot extracts issue keys from branch names and commit messages using a team prefix (defaults to ENG, configurable via LINEAR_TEAM_PREFIX). On successful deploys, staging deploys comment on linked issues. Production deploys comment and transition issues to the state configured in LINEAR_PRODUCTION_STATE (defaults to Done).
For the bot to know which commits are new in each deploy, your deploy pipeline must maintain four git tags in the target repo:
deploy/staging/previousdeploy/staging/latestdeploy/production/previousdeploy/production/latest
The bot compares previous to latest to find the commit range. It doesn't create or move these tags itself, so your CI pipeline should update them as part of the deploy process. If the tags don't exist, the bot skips Linear updates rather than guessing.
| Variable | Required | Purpose |
|---|---|---|
SLACK_BOT_TOKEN | Yes | Bot User OAuth Token (xoxb-...) |
SLACK_SIGNING_SECRET | Yes | Request verification from the Basic Information page |
GITHUB_TOKEN | Yes | Fine-grained personal access token (github_pat_...) |
GITHUB_WEBHOOK_SECRET | Yes | Secret for verifying GitHub webhook payloads |
GITHUB_REPO_OWNER | Yes | Repository owner or organization |
GITHUB_REPO_NAME | Yes | Repository name |
GITHUB_WORKFLOW_ID | Yes | Workflow filename (e.g. deploy.yml) or numeric ID |
REDIS_URL | Yes | Redis connection string |
LINEAR_API_KEY | No | Enables Linear integration (lin_api_...) |
LINEAR_TEAM_PREFIX | No | Issue key prefix (default: ENG) |
LINEAR_PRODUCTION_STATE | No | State to transition prod issues to (default: Done) |
DEPLOY_PROD_ALLOWED | No | Comma-separated Slack user IDs allowed to trigger prod deploys |
DEPLOY_PROD_APPROVERS | No | Comma-separated Slack user IDs allowed to approve prod deploys |
If DEPLOY_PROD_ALLOWED is empty or unset, nobody can trigger production deploys. If DEPLOY_PROD_APPROVERS is empty or unset, nobody can approve them. Staging deploys are available to everyone.
Deploy the bot with one click, or clone the repo and deploy manually:
After deploying, update your Slack app's request URLs to point to your production domain: https://<your-vercel-domain>/api/webhooks/slack.
Open Slack and type:
The bot should post a deploy card to the channel and dispatch your GitHub Actions workflow. You'll see status updates in the Slack thread as the run progresses, followed by a summary card when it completes.
This starts a Next.js dev server. To receive Slack webhooks locally, use ngrok to create a public tunnel:
Then update your Slack app's request URLs to the ngrok URL (e.g. https://abc123.ngrok-free.dev/api/webhooks/slack).
The bot has three interfaces: Slack for user interaction, GitHub for dispatching and monitoring workflows, and (optionally) Linear for issue tracking. Here's the flow:
- A user types
/deploy staging,/deploy production, or/deploy(which opens a modal with environment and branch options) - For staging deploys, the bot posts a deploy card to Slack and immediately dispatches a GitHub Actions workflow
- For production deploys, the bot adds Approve and Cancel buttons to the card and pauses. The workflow only continues if an authorized approver clicks Approve
- Once dispatched, the bot polls the GitHub Actions run every 5 seconds for up to 60 minutes, updating a status message in Slack as it progresses
- When the run completes, the bot comments on associated GitHub PRs and (if Linear is enabled) comments on linked issues and transitions production issues to your configured done state
- The bot posts a final summary card to Slack with the environment, branch, commit, duration, linked issues, and a link to the workflow run
Vercel Workflow makes this possible. A Vercel Workflow function can suspend itself mid-execution and resume later with full state preserved. The approval gate and the polling loop are both regular code. The function pauses while waiting for a button click, resumes when it arrives, then loops while polling GitHub. No cron jobs, no queues, no external state store.
The template is a Next.js app. The bot logic lives in lib/ (setup, handlers, and integrations) and workflows/ (stateful deploy orchestration).
The bot is a Chat SDK instance with adapters for Slack, GitHub, and optionally Linear, plus Redis-backed state:
Each deploy lives in a Slack thread with typed state (environment, branch, commit SHA, and the Slack user ID of whoever ran /deploy). This state is stored in Redis via Chat SDK's state adapter, so the approval handler and the workflow can coordinate without passing data through button payloads alone.
The bot registers a /deploy slash command with two paths. If the user provides an argument (/deploy staging or /deploy production), the bot deploys immediately on the main branch. If no argument is given, the bot opens a modal where the user can pick an environment and optionally specify a branch:
The bot resolves the HEAD commit for the branch, posts a deploy card to Slack, and starts the Vercel Workflow. Staging deploys are open to everyone. Production deploys are gated by DEPLOY_PROD_ALLOWED (who can trigger) and DEPLOY_PROD_APPROVERS (who can approve). When a permission check fails, the bot sends an ephemeral message visible only to the user who tried.
The deploy workflow is the core of the bot.
It's a single function, marked with "use workflow", that orchestrates the entire deploy lifecycle:
This reads like sequential code, but it may take an hour to finish. Vercel Workflow handles the suspend-and-resume mechanics. When the function calls sleep("5s") during polling, or waits for a hook event during approval, it suspends. When the timer fires or the webhook arrives, it resumes exactly where it left off with all variables intact.
For production deploys, the workflow creates a hook and waits:
createHook registers a listener with a unique token (the workflow run ID). The workflow suspends at the for await loop. When someone clicks Approve in Slack, the action handler calls resumeHook with that same token, and the workflow picks up with event.approved set to true. If they click Cancel, it resumes with false and the workflow exits.
Only the person who triggered the deploy can cancel it. Anyone in the DEPLOY_PROD_APPROVERS list can approve.
The bot dispatches a workflow_dispatch event to your GitHub Actions workflow, then finds the resulting run by matching it against the branch, commit SHA, and a deploy correlation ID:
The dispatch function gracefully degrades if your workflow doesn't accept all the expected inputs. It tries { environment, deploy_id } first, then { environment } alone, then no inputs at all. This makes the bot compatible with most existing deploy workflows without changes.
Once a run is found, the bot polls every 5 seconds until the run completes or 60 minutes pass. Each sleep("5s") call suspends the Vercel Workflow function, and each fetchRunSnapshot is marked with "use step" so it retries automatically if the GitHub API call fails.
On a successful deploy, the bot notifies both Linear and GitHub.
Linear issues are found by comparing deploy tags. The bot looks at the commit range between deploy/{environment}/previous and deploy/{environment}/latest in your repo, extracts Linear issue keys (like ENG-123) from branch names and commit messages, then comments on each issue with the deploy details. For production deploys, it also transitions issues to your configured done state.
GitHub pull requests associated with the deploy commit receive a comment with a summary table linking back to the workflow run.
Both steps are wrapped in "use step" directives, so they're retryable and isolated from each other. If the Linear step fails, the GitHub PR comments still proceed.
When the run completes, the bot posts a final card to the Slack thread with the environment, branch, commit, duration, linked issues, and a link to the GitHub Actions run. If Linear is enabled, the card also includes a table of issue identifiers and titles. If the deploy fails to dispatch or the run can't be matched, the triggerer is notified.
Chat SDK supports multiple platforms from a single codebase. The cards, fields, and buttons you've already defined render natively on each platform, including Block Kit on Slack, Adaptive Cards on Teams, and Google Chat Cards.
To add Microsoft Teams or another platform, register an additional adapter:
The existing webhook route at app/api/webhooks/[platform]/route.ts already uses a dynamic segment, so Teams webhooks would be handled at /api/webhooks/teams with no additional routing code.
Modals are currently Slack-only, so the /deploy command with no arguments (which opens a modal) only works on Slack. On other platforms, require the environment argument.
See the Chat SDK adapter directory for the full list of supported platforms.