A Slack bot that triages form submissions. When a form is submitted via HTTP, the bot posts an interactive card to Slack where reviewers can either forward the submission via email or mark it as spam.
Built with Chat SDK, Hono, Resend, and Redis.
- An external service sends a
POST /api/formrequest with arbitrary JSON form data - The bot stores the submission in Redis (7-day TTL) and posts an interactive card to a Slack channel
- A reviewer clicks one of three buttons:
- Forward Submission — sends a styled HTML email via Resend to a configured recipient (and optionally POSTs to a webhook endpoint), then updates the card to show who forwarded it
- Edit & Forward — opens a modal with pre-filled form fields for editing before forwarding
- Mark as Spam — updates the card to show who marked it, deletes the submission from Redis
pnpm installCreate a .env.local file in the project root:
# Slack
SLACK_BOT_TOKEN=xoxb-...
SLACK_SIGNING_SECRET=...
SLACK_CHANNEL_ID=C... # Channel where submission cards are posted
# Redis
REDIS_URL=redis://localhost:6379
# Resend
RESEND_API_KEY=re_...
RESEND_FROM_ADDRESS=bot@yourdomain.com # Must be a verified Resend domain
RESEND_FROM_NAME=Formbot # Optional, defaults to "Formbot"
# Forwarding
FORWARD_EMAIL=team@yourdomain.com # Where approved submissions are sent
FORWARD_ENDPOINT= # Optional: webhook URL to POST submissions toThe quickest way to get started is to create your app from the included manifest:
- Go to api.slack.com/apps and click Create New App → From a manifest
- Select your workspace, paste the contents of
slack-manifest.json, and create the app - Replace
https://example.comin Interactivity & Shortcuts with your actual domain (or ngrok URL during development) - Install the app to your workspace
- Copy the Bot User OAuth Token from OAuth & Permissions and the Signing Secret from Basic Information into your
.env.local
Manual setup (without manifest)
- Create a new Slack app at api.slack.com/apps
- Under OAuth & Permissions, add the bot token scopes:
chat:write,channels:history,channels:read,groups:history,groups:read,im:history,im:read,mpim:history,mpim:read,users:read - Enable Interactivity & Shortcuts and set the request URL to
https://<your-domain>/api/webhooks/slack - Install the app to your workspace and copy the Bot User OAuth Token
- Copy the Signing Secret from Basic Information
pnpm devStarts a local server at http://localhost:3000 with hot reload via tsx watch.
Slack needs a public URL to deliver webhook events. Use ngrok to tunnel traffic to your local server:
ngrok http 3000Copy the generated URL (e.g. https://abc123.ngrok-free.dev) and set it as the request URL in your Slack app:
- Interactivity & Shortcuts →
https://<ngrok-url>/api/webhooks/slack - Event Subscriptions →
https://<ngrok-url>/api/webhooks/slack
curl -X POST https://<ngrok-url>/api/form \
-H "Content-Type: application/json" \
-d '{"Name": "Jane Doe", "Email": "jane@example.com", "Message": "Hello!"}'Accepts a JSON body with arbitrary key-value pairs. CORS is enabled, so this endpoint can be called directly from browser-based forms on any origin. Returns:
{ "status": "received", "submissionId": "<uuid>" }Receives Slack interaction payloads (button clicks). This is set as the request URL in your Slack app configuration.
The app exports a standard Hono fetch handler, so it can be deployed to any fetch-compatible runtime.
The src/lib/local.ts entrypoint is only used for local development via @hono/node-server.
| Command | Description |
|---|---|
pnpm dev |
Start dev server with hot reload |
pnpm build |
Compile TypeScript to dist/ |
pnpm test |
Run tests with coverage |
pnpm type-check |
Type-check without emitting |
pnpm check |
Lint and format check |
pnpm fix |
Auto-fix lint and format issues |
pnpm validate |
Run check, type-check, and tests |
src/
index.ts Hono app with HTTP routes
bot.ts Chat SDK bot instance and action handlers
lib/
cards.ts Chat SDK card and modal builders
email.ts Resend email forwarding
endpoint.ts Optional webhook endpoint forwarding
submissions.ts Redis CRUD for form submissions
local.ts Local dev server entrypoint
tests/
*.test.ts Unit tests (vitest)
Chat SDK supports multiple platforms. You can extend this bot to post submission cards to Microsoft Teams, Google Chat, Discord, or Telegram by registering additional adapters:
// src/bot.ts
import { createTeamsAdapter } from "@chat-adapter/teams";
import { createGoogleChatAdapter } from "@chat-adapter/gchat";
export const bot = new Chat({
adapters: {
slack: createSlackAdapter(),
teams: createTeamsAdapter(),
gchat: createGoogleChatAdapter(),
},
state,
userName: "form-bot",
});Then add a webhook route for each platform in src/index.ts — the existing /:platform route pattern already supports this.
Cards, fields, and buttons render natively on each platform (Block Kit on Slack, Adaptive Cards on Teams, Google Chat Cards, etc.). Note that modals are currently Slack-only, so the "Edit & Forward" button will only work on Slack.
See the Chat SDK adapter docs for the full list of supported platforms and their feature matrices.
Apache-2.0