Intermediate

Paste this guide into Claude Code and it will walk you through every step interactively.

Talk to Your AI from Anywhere

What this gives you: A Telegram group where you message different topics and the right AI agent responds. LinkedIn questions go to your LinkedIn agent. Revenue questions go to your accountant. All from your phone.

Your AI assistant should follow you everywhere. Not sit on a laptop waiting. This guide connects Jarvis to Telegram so you can fire off instructions from your phone, get responses in seconds, and have the right specialist handle each topic automatically.

Why Telegram

Most people reach for WhatsApp or iMessage when they think about messaging. But Telegram has one feature that makes it the right choice for AI assistants: Topics.

Topics let you create thread channels inside a single group. One group, multiple topic threads, each acting like a separate chat. This maps perfectly to a multi-agent setup. Your LinkedIn agent lives in the LinkedIn topic. Your accountant lives in the Accountant topic. Messages automatically route to the right agent based on which topic you post in.

Beyond that: Telegram is free, has a rock-solid Bot API, supports voice messages, works on every platform, and has no message size limits that matter. The API is simple, well-documented, and has excellent TypeScript libraries.

Step 1: Create Your Telegram Bot

Every Telegram bot starts with BotFather. This is Telegram's official bot creation tool.

  1. Open Telegram and search for @BotFather (the verified one with a blue tick)
  2. Send the command /newbot
  3. When prompted, give your bot a display name - something like Jarvis AI
  4. When prompted for a username, it must end in bot - for example jarvis_rahul_bot
  5. BotFather will reply with your bot token. It looks like: 7812345678:AAGxyz...
Important: The bot token is a secret. Treat it like a password. Anyone with this token can control your bot. Store it in your .env file immediately, never in code.

Add the token to your environment file:

# ~/.env or /home/jarvis/.env TELEGRAM_BOT_TOKEN="7812345678:AAGxyz_your_actual_token_here"

Test that the token works by hitting the Telegram API directly:

curl https://api.telegram.org/botYOUR_TOKEN/getMe

You should get back a JSON response with your bot's name and username. If you see "ok":true, the token is valid and the bot is live.

Step 2: Create a Group with Topics

The bot needs a home. That home is a Telegram group with Topics enabled.

  1. In Telegram, tap the pencil icon to start a new chat, then select New Group
  2. Add at least one other contact to create the group (you can remove them later)
  3. Give the group a name - something like Jarvis HQ
  4. Once inside the group, tap the group name at the top to open settings
  5. Tap the edit (pencil) icon, then scroll to find Topics and enable it
  6. Now add your bot as a member: tap the group name again, then Add Members, search for your bot's username
  7. Make the bot an admin: tap the bot in the member list, then Promote to Admin. Enable Post Messages and Manage Topics at minimum

Now create your topic threads. Tap the group name, then the Topics section, and create each of these:

Topic NamePurpose
GeneralCatch-all, admin tasks, anything unclassified
LinkedInPosts, comments, connection messages
YouTubeScripts, titles, thumbnails, analytics
NewsletterEmail content, subject lines, sequences
AccountantRevenue, expenses, invoices, Stripe data
StrategyPlanning, decisions, research, quarterly goals
Tip: Start with 4-5 topics maximum. Adding 10 topics upfront means most will go unused. Add new ones as you naturally find yourself wanting a separate agent for something.

Step 3: Get Your Chat ID

To send messages back to your group, the bot needs the group's chat ID. Here is how to find it.

First, send any message in your Telegram group. Then run this curl command with your bot token:

curl https://api.telegram.org/botYOUR_TOKEN/getUpdates

Look through the JSON response for the chat object. The id field inside it is your chat ID. Group IDs are negative numbers, like -1001234567890.

# Example response (simplified) { "message": { "chat": { "id": -1001234567890, // <-- this is your TELEGRAM_CHAT_ID "title": "Jarvis HQ", "type": "supergroup" }, "message_thread_id": 238 // <-- this is the topic thread ID } }

Note the message_thread_id for each topic. Send a message in each topic thread and run getUpdates again to collect all the IDs. You will need these for the router in Step 6.

# Add to .env TELEGRAM_CHAT_ID="-1001234567890"

Step 4: Install Grammy

Grammy is the TypeScript framework for building Telegram bots. It handles the connection to Telegram's API, message parsing, and sending replies. Clean, well-maintained, and works perfectly with Bun.

bun add grammy

Create the entry point for your bot:

// gateway/index.ts import { Bot } from 'grammy' import { setupRouter } from './router' const token = process.env.TELEGRAM_BOT_TOKEN if (!token) throw new Error('TELEGRAM_BOT_TOKEN not set') const bot = new Bot(token) setupRouter(bot) bot.catch((err) => { console.error('Bot error:', err) }) bot.start() console.log('Jarvis gateway running...')
Tip: Grammy uses long polling by default, which is fine for a self-hosted setup. It continuously asks Telegram for new messages. No public URL or SSL certificate needed - the bot reaches out, Telegram does not reach in.

Step 5: Map Your Topic Thread IDs

Before writing the router, you need to collect the thread ID for every topic. Send a test message in each topic, run getUpdates, and record the message_thread_id values.

// gateway/topics.ts // Replace these numbers with your actual thread IDs from getUpdates export const TOPIC_ROUTES: Record<number, string> = { 238: 'linkedin', 239: 'youtube', 240: 'newsletter', 241: 'accountant', 242: 'strategy', // General topic has no thread ID - it uses thread 0 or no thread }
Note: The General topic (created by default when you enable Topics) often uses message_thread_id: 1 or appears without a thread ID. Test this by sending a message in General and checking getUpdates to confirm.

Step 6: Route Messages by Topic

The router is the core of the gateway. It listens for every message in the group, checks which topic thread it came from, and calls the matching agent.

// gateway/router.ts import { Bot } from 'grammy' import { runAgent } from './agent-runner' import { TOPIC_ROUTES } from './topics' export function setupRouter(bot: Bot) { bot.on('message', async (ctx) => { const topicId = ctx.message.message_thread_id const text = ctx.message.text // Ignore messages with no text (handle voice separately) if (!text) return // Pick the agent based on topic, fall back to general const agentName = topicId ? (TOPIC_ROUTES[topicId] ?? 'general') : 'general' // Show typing indicator while agent processes await ctx.replyWithChatAction('typing') try { const response = await runAgent(agentName, text) await ctx.reply(response, { message_thread_id: topicId, parse_mode: 'Markdown' }) } catch (err) { console.error(`Agent error (${agentName}):`, err) await ctx.reply('Something went wrong. Check pm2 logs for details.', { message_thread_id: topicId }) } }) }

Step 7: Connect to Claude

The agent runner spawns a claude -p process with the right system prompt for each agent. Each agent's system prompt lives in its own markdown file.

// gateway/agent-runner.ts import { execFile } from 'child_process' import { promisify } from 'util' import { readFileSync } from 'fs' const exec = promisify(execFile) export async function runAgent(name: string, message: string): Promise<string> { // Load agent-specific system prompt const agentPrompt = readFileSync( `/home/jarvis/agents/${name}.md`, 'utf-8' ) // Load shared context injected into every agent const sharedContext = readFileSync( '/home/jarvis/context/MEMORY.md', 'utf-8' ) const systemPrompt = `${sharedContext}\n\n---\n\n${agentPrompt}` const { stdout } = await exec( 'claude', [ '-p', message, '--system', systemPrompt, '--model', 'claude-sonnet-4-5' ], { env: { ...process.env }, timeout: 120000 // 2 minutes } ) return stdout.trim() }

Create the agent prompt files. Here is a minimal example to get you started:

# /home/jarvis/agents/linkedin.md You are Rahul's LinkedIn specialist. Your responsibilities: - Write and edit LinkedIn posts (thought leadership, case studies, hooks) - Draft connection request messages - Suggest content based on recent business activity - Analyse what is performing well Content rules: - No rhetorical questions - No em dashes - use hyphens or commas instead - Hooks must be specific, not generic - First line must stop the scroll without being clickbait - Write how Rahul talks: direct, punchy, no fluff
# /home/jarvis/agents/accountant.md You are Rahul's AI accountant. Your responsibilities: - Summarise revenue and expenses when asked - Track MRR, new clients, and churn - Flag unusual patterns in financial data - Help with invoice queries and pricing decisions When financial data is provided in the message context, analyse it directly. Ask clarifying questions if the query is ambiguous.

Step 8: Deploy with pm2

pm2 keeps your bot running permanently. If it crashes, pm2 restarts it automatically. If the VPS reboots, pm2 brings everything back up.

# Start the bot pm2 start bun --name jarvis-gateway -- run gateway/index.ts # Save the process list so it survives reboots pm2 save # Generate and run the startup command pm2 gives you pm2 startup

Common pm2 commands you will use daily:

# Check status of all processes pm2 status # View live logs from the gateway pm2 logs jarvis-gateway # View last 100 lines of logs pm2 logs jarvis-gateway --lines 100 # Restart after making code changes pm2 restart jarvis-gateway
Tip: Run pm2 logs in one terminal while you test the bot in another. You will see exactly what the bot receives and what Claude returns, which makes debugging straightforward.

Voice Messages

Typing on mobile is slow. Voice messages let you speak instructions and have them transcribed and passed to the right agent. Grammy receives the voice file, you transcribe it with OpenAI Whisper, then pass the text through the same routing pipeline.

// Add to gateway/router.ts, inside setupRouter() bot.on('message:voice', async (ctx) => { const topicId = ctx.message.message_thread_id const agentName = topicId ? (TOPIC_ROUTES[topicId] ?? 'general') : 'general' await ctx.replyWithChatAction('typing') // Download the voice file from Telegram const file = await ctx.getFile() const fileUrl = `https://api.telegram.org/file/bot${process.env.TELEGRAM_BOT_TOKEN}/${file.file_path}` const audioResponse = await fetch(fileUrl) const audioBuffer = Buffer.from(await audioResponse.arrayBuffer()) // Transcribe with Whisper const formData = new FormData() formData.append('file', new Blob([audioBuffer], { type: 'audio/ogg' }), 'voice.ogg') formData.append('model', 'whisper-1') const whisperRes = await fetch('https://api.openai.com/v1/audio/transcriptions', { method: 'POST', headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` }, body: formData }) const { text: transcript } = await whisperRes.json() if (!transcript) { await ctx.reply('Could not transcribe voice message.', { message_thread_id: topicId }) return } // Run through the same agent pipeline as text const response = await runAgent(agentName, transcript) await ctx.reply(`_Transcribed: "${transcript}"_\n\n${response}`, { message_thread_id: topicId, parse_mode: 'Markdown' }) })

Add the OpenAI key to your environment file:

# .env OPENAI_API_KEY="sk-..."
Tip: Voice messages in Telegram are .ogg format. Whisper handles this natively, so no conversion needed. If you are on a slow connection, the transcription adds about 1-2 seconds to response time - barely noticeable in practice.

Testing

Work through each topic in order. For each one, send a simple test message and confirm the bot replies in the same topic thread.

TopicTest messageExpected response
LinkedIn"Write me a one-line hook about AI for service businesses"A single sharp hook line
YouTube"Give me 3 title ideas for a video about Telegram bots"3 formatted title ideas
Accountant"What should I track for MRR?"A list of MRR metrics
Strategy"What is the 80/20 for a solo agency owner?"A focused strategic answer

Common Issues

Bot does not reply at all

Bot replies but in the wrong topic

Bot is not admin - messages are received but cannot reply

Topics not showing up in the group

Claude command not found

Want me to set this up for you?

I can configure your entire Telegram bot and agent system in one session.

Book a Call
Next Guide
AI Thumbnail Generator