Public
Telegram Bot that receives and responds to messages
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

Telegram Bot Starter — Claude Code setup guide

This template gets a scheduled Telegram bot from "I have a bot token" to "messages are landing in my chat" in under a minute, driven by Claude Code.

The template's index.ts ships as an HTTP webhook responder — useful if you want a chat bot that replies. The guide below is for the more common case: a scheduled push bot that sends a recurring message (daily weather, daily reminder, daily news digest, anything).


The choreography

  1. Human creates a bot via @BotFather on Telegram and copies the token.
  2. Human tells Claude Code: "Use templates/telegramBotStarter to build a [topic] bot for this token: "
  3. Claude does everything below, then pauses and asks the human to DM the bot once.
  4. Human DMs the bot ("hi" / /start) and says "go".
  5. Claude grabs the chat ID, fires a test, confirms the message landed.

Steps for Claude Code

1. Remix the template

remix_val(val="templates/telegramBotStarter", name="<descriptive-name>")

Pick a name that describes the bot's job, e.g. daily-nyc-weather, morning-stretch-reminder.

2. Replace index.ts with a scheduled-push handler

Overwrite the file with an interval-type handler that reads two env vars and sends one message.

Minimal skeleton:

import { Bot } from "https://deno.land/x/grammy@v1.35.0/mod.ts"; const TOKEN = Deno.env.get("TELEGRAM_BOT_TOKEN"); const CHAT_ID = Deno.env.get("TELEGRAM_CHAT_ID"); if (!TOKEN) throw new Error("TELEGRAM_BOT_TOKEN is not set"); if (!CHAT_ID) throw new Error("TELEGRAM_CHAT_ID is not set"); export default async function () { // Fetch whatever the message needs (weather API, RSS, etc.) const message = "Hello from your bot"; const bot = new Bot(TOKEN); await bot.api.sendMessage(CHAT_ID, message, { parse_mode: "Markdown" }); }

For a weather bot, Open-Meteo is a no-API-key source. RSS feeds, public JSON endpoints, and any HTTPS API work too.

3. Switch the file type to interval and set the cron

Use set_file_type to change index.ts from http to interval, then write_interval_settings to set a cron.

Example daily cron: 0 11 * * *. Cron expressions run in UTC. Pick the UTC time that matches the user's desired local time for the destination's timezone (e.g. 0 11 * * * is 7am EDT / 6am EST).

4. Store the bot token

Verify the token first by making a GET request to Telegram's getMe endpoint at api.telegram.org/bot{token}/getMe. A response with is_bot: true confirms the token.

Then add it as an env var on the val:

  • Key: TELEGRAM_BOT_TOKEN
  • Value: the token from the user
  • Description: the bot's @username

Env var naming convention: use TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID. These exact names.

5. Parallelize where possible

Steps 2, 3, and 4 (update_file, set_file_type, write_interval_settings, add_env_var) have no dependencies on each other. Issue them in a single parallel tool batch.

6. Check in with the human

Return control with a message like:

Bot is wired up and the cron is armed. DM your bot once (any message, or tap Start) so Telegram registers the chat. Tell me when done and I'll grab your chat ID and fire a test message.

Do not attempt to drive Telegram via UI automation or screenshots — the DM is a one-tap user action.

7. After the human confirms — get the chat ID

Make a GET request to Telegram's getUpdates endpoint at api.telegram.org/bot{token}/getUpdates. Parse result[0].message.chat.id (use the most recent update if there are several).

8. Store the chat ID

Add another env var on the val:

  • Key: TELEGRAM_CHAT_ID
  • Value: the chat ID from step 7
  • Description: who/where the message goes

9. Fire the test

Use run_file on index.ts. A return with no error means the message has been sent. Tell the human it should now be in their Telegram.


Conventions used by this guide

ItemValue
Telegram clientgrammy from deno.land/x
Weather sourceapi.open-meteo.com (no API key)
Token env varTELEGRAM_BOT_TOKEN
Chat env varTELEGRAM_CHAT_ID
Cron timezoneUTC (Val Town's interval scheduler runs in UTC)
Message formatMarkdown via parse_mode: "Markdown"

Notes

  • For a private DM chat, the chat ID is the user's Telegram user ID — same across every bot that user has DM'd. If you've already discovered it for one bot, you can reuse it for the next.
  • For a group chat, add the bot to the group, send any message, and the chat ID will be a negative number in getUpdates.
  • For a channel, add the bot as an admin and post a message; the chat ID will appear in getUpdates as well.
  • Open-Meteo accepts a timezone= parameter — set it to the destination's IANA timezone so "today" in the daily forecast matches the human's local day.