telegram
A self-hosted Telegram broadcast, sequence, and bot system. Deploy on Vercel + Supabase in about 20 minutes.
Manage contacts, send direct messages, broadcast to groups, build drip sequences with daily or delay scheduling, and collect new subscribers through a public signup page that drops them straight into Telegram via a deep-link. Built with Next.js 15, Supabase Auth + Postgres + RLS, and the Telegram Bot API.
- Next.js 15 App Router, TypeScript strict
- Supabase (Postgres + Auth + RLS) — no separate backend
- Vercel for hosting + cron
- Zero third-party paid services beyond Telegram (free), Supabase (free tier), and Vercel (free tier)
What's inside
- Contacts — emails, names, tags, per-contact timezone + preferred send time, linked direct + group Telegram chat IDs.
- Direct send — send a message or media (photo/video/audio/voice/document) to a contact, a chat ID, or both direct + group.
- Broadcasts — compose, preview matching recipients, send immediately or schedule for later (fans out per-recipient so each lands at their local time).
- Sequences — multi-step drip campaigns. Each step can be
delay(N minutes after enrollment) ordaily(Day N at HH:MM in the contact's timezone). Auto-delete per step supported. - Public signup — a minimal
/signupform collects email + first name + timezone + preferred time + optional sequence, then redirects to at.me/<bot>?start=<token>deep link. The bot captures the chat ID on/startand enrolls the contact. - Webhook — inbound events:
/start <token>handoff, group JOINED/LEFT notifications to an admin group, welcome messages in a community group, NEW GROUP detection. - Cron — a single endpoint (
/api/cron/process-scheduled) runs every 5 minutes on Vercel, sending due messages, advancing sequence enrollments, and sweeping auto-delete.
First time? Start here
After your first Vercel deploy, visit $APP_URL/setup. That page shows every required environment variable, marks each one ✅ set / ❌ missing, and links to where to grab each value. It doubles as your own deployment's setup checklist.
The public / and /signup pages, and /admin/login, all detect an unconfigured deploy and direct you to /setup rather than crashing. So you can safely deploy first with no env vars and fill them in as you go.
Quick start (local)
git clone <your-fork-url> telegram
cd telegram
npm install
cp .env.example .env.local
# Fill in env vars (see tutorial below)
npm run dev
Open http://localhost:3000 — you'll be redirected to /signup.
Tutorial — deploying your own
1. Create a Supabase project
- Go to https://supabase.com, create a new project (free tier is fine).
- Wait for it to finish provisioning.
- In the SQL Editor, open a new query, paste the contents of
supabase/schema.sql, and run it. - In Project Settings → API, copy:
Project URL→NEXT_PUBLIC_SUPABASE_URLanon publickey →NEXT_PUBLIC_SUPABASE_ANON_KEYservice_rolekey →SUPABASE_SERVICE_ROLE_KEY(keep this secret)
2. Create a Telegram bot
- Open Telegram and message @BotFather.
- Send
/newbot, follow the prompts. Save the bot token →TELEGRAM_BOT_TOKEN. - Save the bot username (without the
@) →TELEGRAM_BOT_USERNAME. - Send
/setprivacy→ choose your bot → Disable. (Required for the bot to see group events.) - Send
/setjoingroups→ choose your bot → Enable.
3. Create admin groups in Telegram
You'll want two groups:
- Notifications — gets pinged when someone joins or leaves a group, or connects via
/start. - Technical — gets pinged when the bot is added to a new group, plus deploy-style notifications.
Optional third: Community — the bot posts a welcome message here when a new member joins.
For each group: create it, add the bot, promote the bot to admin, then grab the chat ID via @getidsbot (or look for "NEW GROUP" in the Technical group once the webhook is live).
Save each chat ID:
TELEGRAM_NOTIFICATIONS_CHAT_IDTELEGRAM_TECHNICAL_CHAT_IDTELEGRAM_COMMUNITY_CHAT_ID(optional)
4. Pick your secrets
Generate two random secrets (any method — openssl rand -hex 32 works):
CRON_SECRET— protects the cron endpoint + the webhook-register endpoint.TELEGRAM_WEBHOOK_SECRET— Telegram sends this in a header so we know the webhook call is genuine.
5. Deploy to Vercel
- Push this repo to GitHub.
- Go to https://vercel.com/new, import the repo.
- Under Environment Variables, paste everything from
.env.example. In particular:- All the Supabase and Telegram vars from steps 1–3.
CRON_SECRETandTELEGRAM_WEBHOOK_SECRETfrom step 4.APP_URL— set this to your final Vercel URL (e.g.https://your-app.vercel.app).ADMIN_EMAILS— comma-separated list of emails allowed to sign into the admin. Example:you@example.com.PUBLIC_ALLOWED_ORIGINS— leave blank unless you'll POST to/api/bot/leadfrom another domain.
- Deploy.
6. Register the Telegram webhook
After the first deploy succeeds:
curl -X POST "$APP_URL/api/webhooks/telegram/register" \
-H "Authorization: Bearer $CRON_SECRET"
You should get { "ok": true, ... }. Or sign in and click Re-register webhook in /admin/settings.
7. Log in
Open $APP_URL/admin/login, enter an email from ADMIN_EMAILS, check your inbox for the magic link. Sign in — you should land on /admin.
8. Send your first message
- In
/admin/contacts, click New contact. Email is enough; if you already know your own Telegram chat ID you can paste it now. If not, skiptelegram_chat_id— step 9 auto-fills it. - In
/admin/settings, use Send test message with your Telegram chat ID. The bot should DM you.
9. Try the public signup
- Open
$APP_URL/signupin incognito. - Fill the form, click Connect with Telegram.
- Click Open Telegram on the next screen, press Start in the bot.
- Back in
/admin/contacts, your contact should now have atelegram_chat_idlinked.
10. Build a sequence
- In
/admin/sequences, click Create. - Open the sequence, add 2–3 steps (mix of
delayanddaily). - Enroll a contact (dropdown at the bottom of the sequence page).
- Wait for the next cron tick (every 5 minutes) — messages should start flowing.
You're done.
Repo layout
telegram/
supabase/schema.sql # run once on a fresh Supabase project
supabase/seed.sql # optional demo sequence
src/app/ # Next.js App Router
admin/* # admin dashboard (auth-gated)
signup/ # public signup form
bot/ # "Open Telegram" confirmation
docs/ # tutorial rendered from README.md
api/
telegram/send # authenticated immediate send
telegram/schedule # create + list scheduled messages
telegram/broadcast # + recipients + log
telegram/sequences/[id]/... # sequence CRUD + steps + enrollments
telegram/media # register a media URL for sending
webhooks/telegram # inbound Telegram updates
webhooks/telegram/register # call once after deploy
bot/lead # public: create lead + bot deep-link
cron/process-scheduled # every 5 min: send + advance + sweep
src/lib/
telegram/send.ts # Bot API wrapper
telegram/sequences.ts # enroll + advance logic (timezone-aware)
supabase/{server,client}.ts
utils/{local-time-to-utc,cors}.ts
validation/schemas.ts # zod
src/components/ui/* # Button, Input, Textarea, Select, Badge
vercel.json # cron: */5 * * * *
src/middleware.ts # auth gate for /admin/* and protected APIs
Environment variables
See .env.example. Required:
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase anon key (client-side) |
SUPABASE_SERVICE_ROLE_KEY |
Supabase service key (server-side; bypasses RLS) |
TELEGRAM_BOT_TOKEN |
From @BotFather |
TELEGRAM_BOT_USERNAME |
Bot username (no @) — used in deep links |
TELEGRAM_WEBHOOK_SECRET |
Shared secret Telegram sends back in a header |
TELEGRAM_NOTIFICATIONS_CHAT_ID |
Admin group for JOINED/LEFT events |
TELEGRAM_TECHNICAL_CHAT_ID |
Admin group for NEW GROUP + system events |
TELEGRAM_COMMUNITY_CHAT_ID |
(optional) Community group that gets welcome messages |
CRON_SECRET |
Secret for /api/cron/* + /api/webhooks/telegram/register |
APP_URL |
Your deployed URL |
ADMIN_EMAILS |
Comma-separated allowlist for magic-link login |
PUBLIC_ALLOWED_ORIGINS |
(optional) Origins allowed to POST /api/bot/lead |
Claude AI (future)
Claude is not wired in v1 — but the project leaves a clean seam for it. To add an "AI Draft" button on the broadcast composer and sequence step editor:
npm i @anthropic-ai/sdk- Add
ANTHROPIC_API_KEYto.env.exampleand Vercel env. - Create
src/app/api/ai/draft/route.tsthat callsanthropic.messages.create({ model: 'claude-sonnet-4-5', ... })with a system prompt like "You write concise Telegram messages (max 400 chars for broadcasts, 800 for sequence steps). Plain text + emoji; no markdown headings." - Add a button in
broadcasts/composer.tsxand sequence step form that POSTs the current draft and replaces it with the AI output.
License
MIT.