Telegram bot + Mini App monorepo template. grammY + Effect TS + Hono serverless on Vercel, React + Radix UI frontend, MongoDB.
- Node 22 (see
.nvmrc) - pnpm (package manager)
Open BotFather and create two bots:
- Staging bot — for
developbranch deploys - Production bot — for production deploys
BotFather will ask for a name and a username:
- Name (display name) — 1–64 characters, no restrictions on format. Overwritten on each deploy from
bot-settings.ts, so use any placeholder. - Username (the
@handle) — 5–32 characters, onlya-z,0-9,_, must end withbot. Permanent, cannot be changed later.
Generate a random staging username in the browser console:
console.log(`yourprefix_stag_${crypto.randomUUID().slice(0,5)}_bot`)Save both tokens.
For each bot, create a Mini App via /newapp. BotFather will ask for:
- Title and description — any placeholder, not used at runtime
- Photo — upload
./assets/web-app-photo-640x360.jpgfrom the repo - Web App URL — use any placeholder URL, overwritten on each deploy by
bot-settings.ts - Short name — 3–30 characters, only
a-z,A-Z,0-9,_. Used int.me/bot_username/short_namelinks. Permanent.
Generate a random staging short name in the browser console:
console.log(`stag_${crypto.randomUUID().slice(0,5)}`)- Create a cluster (e.g. free M0 at mongodb.com/atlas)
- Allow
0.0.0.0/0in Network Access (required for Vercel serverless) - Create two databases and two users with Read/Write access respectively (staging + production) and copy connection strings
- Create a new project at vercel.com/new and import the repo
- Set Framework Preset to Other
- Disable git-based deployments (handled by GitHub Actions via
vercel.json) - Copy Project ID and Org ID from Project Settings → General
- Get a deploy token from vercel.com/account/tokens (one token works for all projects)
- Disable Deployment Protection (Settings → Deployment Protection → set to Off) so Telegram webhook requests can reach the API endpoints
- Add environment variables for both Preview (Staging) and Production scopes (see Vercel secrets)
Staging in GitHub corresponds to Preview scope in Vercel.
| Purpose | Environment | Vercel | GitHub |
|---|---|---|---|
GitHub PAT (contents: write) for Release Prepare |
all | GH_PAT |
|
| Vercel deploy token | all | VERCEL_TOKEN |
|
| Vercel org ID | all | VERCEL_ORG_ID |
|
| Vercel project ID | all | VERCEL_PROJECT_ID |
|
| Telegram bot token | staging | BOT_TOKEN |
STAGING_BOT_TOKEN |
| Webhook secret | staging | WEBHOOK_SECRET |
STAGING_WEBHOOK_SECRET |
| MongoDB connection string | staging | MONGODB_URI |
STAGING_MONGODB_URI |
| Telegram bot token | production | BOT_TOKEN |
PROD_BOT_TOKEN |
| Webhook secret | production | WEBHOOK_SECRET |
PROD_WEBHOOK_SECRET |
| MongoDB connection string | production | MONGODB_URI |
PROD_MONGODB_URI |
| Frontend environment flag | staging | VITE_ENV = preview (by default) |
|
| Frontend environment flag | production | VITE_ENV = production |
pnpm test # all tests
pnpm run test:integration # vitest integration only
pnpm run test:e2e # cucumber E2E only
pnpm run test:watch # vitest watch modeAll tests use mongodb-memory-server — no Docker or external DB required.
Add the e2e label to run e2e tests on PR
- Push to
develop→ deploys to Vercel Preview automatically - PRs → add the
deploy-staginglabel to deploy PR to Vercel Preview - Release Prepare (manual) → bumps version, updates changelog, creates
vX.Y.Ztag ondevelop - Deploy Production (manual, on a
vX.Y.Ztag) → runs CI then deploys to Vercel Production