Build a Slack bot that triages form submissions where your team already works. When someone submits a form on your website, the bot posts an interactive card to a Slack channel. A reviewer clicks a button to forward the submission via email, edit it before forwarding, or mark it as spam. The whole workflow happens inside Slack, with no separate dashboard or inbox to monitor.
The bot is built with Chat SDK, a TypeScript library for building chatbots that work across Slack, Microsoft Teams, Discord, and other platforms from a single codebase. You write your logic once, and Chat SDK handles the platform-specific details, such as Block Kit on Slack or Adaptive Cards on Teams.
Deploy the template now, or read on for a deeper look at how it at all works.
The bot has three moving parts, including an HTTP endpoint that receives form data, a Redis store that temporarily holds submissions, and a Chat SDK bot that manages Slack interactions.
- An external service (your website, a form provider, a webhook) sends a
POSTrequest to/api/formwith JSON data - The bot generates a unique ID, stores the submission in Redis with a 7-day TTL, and posts an interactive card to a Slack channel
- A reviewer sees the card and takes one of three actions:
- Forward Submission sends a styled HTML email via Resend to a configured recipient, then updates the card to show who forwarded it
- Edit & Forward opens a modal where the reviewer can modify fields before forwarding
- Mark as Spam updates the card and deletes the submission from Redis
Once an action is taken, the card updates in place. The reviewer sees the result immediately in the same channel, without a confirmation page or context switch.
The entire bot is about 200 lines across six files.
The HTTP layer uses Hono, a lightweight web framework. The form endpoint accepts any JSON body, assigns it a UUID, and hands it off to the bot:
You can submit directly from a browser-based form on any origin. The webhook route at /api/webhooks/:platform handles Slack's interaction payloads (button clicks and modal submissions). The :platform parameter is already set up for adding other platforms later.
The bot itself is a Chat SDK instance with a Slack adapter and Redis-backed state:
That's the entire bot setup. Chat SDK handles Slack's signature verification, payload parsing, and response formatting. The state object is a Redis adapter that Chat SDK uses for its internal state management, and the same Redis connection is reused to store form submissions.
Cards are built using Chat SDK's component functions. These are platform-agnostic: on Slack, they render as Block Kit, on Teams, they'd render as Adaptive Cards. Here's the card that appears when a new submission arrives:
The form data is dynamic. Whatever keys and values are in the JSON body become fields on the card. A submission with {"Name": "Jane", "Email": "jane@example.com"} produces a card with two fields. A submission with ten fields produces a card with ten fields. No schema changes needed.
When a reviewer clicks a button, Chat SDK routes the event to the right handler based on the action ID:
The forward handler sends the email, optionally POSTs to a webhook endpoint, updates the Slack card, and cleans up Redis, all in parallel:
After forwarding, the card is replaced with a read-only version showing who forwarded it and where. The submission is deleted from Redis since it's no longer needed.
You'll need accounts with three services:
- Slack for the bot itself. Create a new app at api.slack.com/apps.
- Redis for temporary submission storage. Any Redis provider works. Upstash supports serverless deployments and has a free tier.
- Resend to send forwarded submissions via email. Sign up at resend.com and verify a sending domain.
- 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 two
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.
The template needs these environment variables:
SLACK_CHANNEL_ID is the channel where submission cards will appear. You can find it by right-clicking a channel in Slack and selecting "View channel details" (the ID is at the bottom of the modal).
FORWARD_ENDPOINT is optional. If set, the bot will also POST the form data as JSON to that URL when a submission is forwarded. This is useful for piping approved submissions into a CRM, database, or another service.
Click the deploy button at the top of this guide, or clone the repo and deploy manually:
After deploying, update your Slack app's interactivity request URL to point to your production domain: https://<your-vercel-domain>/api/webhooks/slack.
Send a test submission:
You should see a JSON response in your terminal:
{"status":"received","submissionId":"a1b2c3d4-..."}
A card should appear in your configured Slack channel within a few seconds.
For local development, the template includes a Node.js server entrypoint:
This starts a local server at http://localhost:3000. To receive Slack webhooks locally, use ngrok to create a public tunnel:
Then update your Slack app's request URL to the ngrok URL (e.g. https://abc123.ngrok-free.dev/api/webhooks/slack).
Chat SDK supports multiple platforms from a single codebase. The cards, fields, and buttons you've already defined render natively on each platform: Block Kit on Slack, Adaptive Cards on Teams, Google Chat Cards, and so on.
To add Microsoft Teams or another platform, register an additional adapter:
The existing webhook route in src/index.ts already uses a :platform parameter, so Teams webhooks would be handled at /api/webhooks/teams with no additional routing.
You could also post to multiple platforms at once. For example, you might post form submissions to both a Slack channel and a Teams channel by calling bot.channel() with different platform prefixes:
Modals are currently Slack-only, so the Edit & Forward button only works on Slack. On other platforms, you'd want to either hide that button or replace it with a different editing flow.
See the Chat SDK adapter directory for the full list of supported platforms.