Find the time everyone is free, right from Slack. /meet Sprint planning posts an availability
poll in the channel. Everyone clicks the times they're free, in their own time zone, without
leaving Slack. The message updates in place with a grid of who is free when and the best times,
and the organizer locks one in with an Add to Google Calendar link.
- Start a poll.
/meet <title>(/mousedoes the same) or the global shortcut opens a modal: date range, working hours, slot length. - One message. The app posts a single poll message in the channel.
- Everyone responds. Each person clicks Add my availability and clicks the times that work in a form in Slack, in their own time zone. Each click saves. A host can also serve the web grid, a page for dragging across the same slots.
- Live results. The channel message re-renders after every response: a grid of who is free when, and the best times.
- Lock it in. The organizer picks a time and the thread gets an Add to Google Calendar link.
Don't want to run it? The hosted version is at meetingmouse.net. Its marketing site is not in this repository; see The hosted service.
You need Node from .nvmrc, pnpm, a Slack workspace where you can create an app, and a
Neon Postgres database (the runtime uses Neon's serverless HTTP driver;
the free tier is enough).
pnpm install # also installs the git hooks
cp .env.example .env.local # then fill in the three variables below
pnpm db:migrate # applies drizzle/*.sql to DATABASE_URL
pnpm dev # http://localhost:3000
pnpm verify # everything CI runs- Create the Slack app at api.slack.com/apps from
manifest.json, with the three URLs pointed at your host or at a tunnel (pnpm dev:tunnelrewrites them for you and restores the file on exit). - Put
SLACK_BOT_TOKEN,SLACK_SIGNING_SECRETandDATABASE_URLin.env.local. - Install the app to your workspace and run
/meetin a channel.
Local development has the details, including how to exercise the route with signed payloads and no Slack workspace at all. Deployment covers Vercel.
Slack sends every command, action and event to one route handler. Bolt verifies the signature,
acks within three seconds, and the listener finishes its work under waitUntil. Every instant
is stored as UTC and rendered in the reader's time zone.
Code is grouped by feature, and the layers are enforced by ESLint rather than by convention:
src/domain is pure functions, renderers never fetch, and UI can never import the database
client or credentials.
src/app/api/slack/events/route.ts the only Slack entry point
src/features/<surface>/ listener · blocks · schema · tests, side by side
src/domain/ slots, tally, calendar links (pure)
src/slack/ Block Kit helpers, limits, surface ids
src/db/ Drizzle schema, client, queries
docs/ living docs, link-checked in CI
Read Architecture for the request lifecycle and the dependency rules, Data model for the tables, and the Feature map for every Slack surface and how to reproduce it.
Stack: Next.js 16 · @slack/bolt + @vercel/slack-bolt · Neon Postgres + Drizzle · Vercel.
The same code builds as a library (pnpm build:lib) for a host that embeds the poll in its own
app; see Embedding in another app.
| Command | What it does |
|---|---|
pnpm verify |
Typecheck, lint, format, tests, docs links, feature map, prose, smoke imports, production build, package |
pnpm test |
Unit tests (vitest; PGlite stands in for Postgres) |
pnpm slack:sign --command /meet |
Sign and POST a payload the way Slack does |
pnpm render:fixture <name> |
Print Block Kit JSON for a fixture poll |
pnpm health |
Check whether a running instance is worth driving |
pnpm db:generate |
Regenerate SQL migrations from src/db/schema.ts |
pnpm pack:check |
Build the library and check the tarball holds only what a host needs |
Issues and pull requests are welcome. Start with CONTRIBUTING.md; it is short. Security reports go through SECURITY.md, not the issue tracker.
MIT.