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.


What's inside


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

  1. Go to https://supabase.com, create a new project (free tier is fine).
  2. Wait for it to finish provisioning.
  3. In the SQL Editor, open a new query, paste the contents of supabase/schema.sql, and run it.
  4. In Project Settings → API, copy:
    • Project URLNEXT_PUBLIC_SUPABASE_URL
    • anon public key → NEXT_PUBLIC_SUPABASE_ANON_KEY
    • service_role key → SUPABASE_SERVICE_ROLE_KEY (keep this secret)

2. Create a Telegram bot

  1. Open Telegram and message @BotFather.
  2. Send /newbot, follow the prompts. Save the bot tokenTELEGRAM_BOT_TOKEN.
  3. Save the bot username (without the @) → TELEGRAM_BOT_USERNAME.
  4. Send /setprivacy → choose your bot → Disable. (Required for the bot to see group events.)
  5. Send /setjoingroups → choose your bot → Enable.

3. Create admin groups in Telegram

You'll want two groups:

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:

4. Pick your secrets

Generate two random secrets (any method — openssl rand -hex 32 works):

5. Deploy to Vercel

  1. Push this repo to GitHub.
  2. Go to https://vercel.com/new, import the repo.
  3. Under Environment Variables, paste everything from .env.example. In particular:
    • All the Supabase and Telegram vars from steps 1–3.
    • CRON_SECRET and TELEGRAM_WEBHOOK_SECRET from 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/lead from another domain.
  4. 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

  1. 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, skip telegram_chat_id — step 9 auto-fills it.
  2. In /admin/settings, use Send test message with your Telegram chat ID. The bot should DM you.

9. Try the public signup

  1. Open $APP_URL/signup in incognito.
  2. Fill the form, click Connect with Telegram.
  3. Click Open Telegram on the next screen, press Start in the bot.
  4. Back in /admin/contacts, your contact should now have a telegram_chat_id linked.

10. Build a sequence

  1. In /admin/sequences, click Create.
  2. Open the sequence, add 2–3 steps (mix of delay and daily).
  3. Enroll a contact (dropdown at the bottom of the sequence page).
  4. 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:

  1. npm i @anthropic-ai/sdk
  2. Add ANTHROPIC_API_KEY to .env.example and Vercel env.
  3. Create src/app/api/ai/draft/route.ts that calls anthropic.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."
  4. Add a button in broadcasts/composer.tsx and sequence step form that POSTs the current draft and replaces it with the AI output.

License

MIT.