How to Build a Telegram Bot Powered by Claude in 30 Minutes
Tutorials·5 min read

How to Build a Telegram Bot Powered by Claude in 30 Minutes

I wanted a personal AI assistant I could message from anywhere — not a web app I have to open, not a desktop app I have to switch to, just a chat in Telegram that happens to be powered by Claude. Turns out, building one is surprisingly straightforward.

This is not a toy demo. By the end of this guide, you will have a Telegram bot that maintains conversation history, handles multiple users, and costs pennies to run. The whole thing took me about 30 minutes the first time, and you can probably do it faster since I am giving you the code.

What You Need

  • A Telegram account (obviously)
  • An Anthropic API key (sign up at console.anthropic.com)
  • Node.js 18+ installed
  • About 30 minutes

Step 1: Create Your Telegram Bot

Open Telegram and message @BotFather. This is Telegram's official bot for creating bots — yes, it is bots all the way down.

  1. Send /newbot
  2. Choose a display name (e.g., "My Claude Assistant")
  3. Choose a username ending in "bot" (e.g., "my_claude_assistant_bot")
  4. Copy the API token BotFather gives you

That is it. Your bot exists now. It just does not do anything yet.

Step 2: Set Up the Project

mkdir claude-telegram-bot
cd claude-telegram-bot
npm init -y
npm install telegraf @anthropic-ai/sdk dotenv

Create a .env file:

TELEGRAM_BOT_TOKEN=your-telegram-token-here
ANTHROPIC_API_KEY=your-anthropic-key-here

Step 3: Write the Bot

Here is the complete bot code. I will explain the important parts after:

import 'dotenv/config';
import { Telegraf } from 'telegraf';
import Anthropic from '@anthropic-ai/sdk';

const bot = new Telegraf(process.env.TELEGRAM_BOT_TOKEN);
const anthropic = new Anthropic();

// Simple in-memory conversation store
const conversations = new Map();
const MAX_HISTORY = 20;

function getHistory(chatId) {
  if (!conversations.has(chatId)) {
    conversations.set(chatId, []);
  }
  return conversations.get(chatId);
}

bot.command('start', (ctx) => {
  ctx.reply('Hey! I am your Claude-powered assistant. Just send me a message and I will respond. Use /clear to reset our conversation.');
});

bot.command('clear', (ctx) => {
  conversations.delete(ctx.chat.id);
  ctx.reply('Conversation cleared. Fresh start!');
});

bot.on('text', async (ctx) => {
  const chatId = ctx.chat.id;
  const userMessage = ctx.message.text;
  const history = getHistory(chatId);

  // Add user message to history
  history.push({ role: 'user', content: userMessage });

  // Trim history if too long
  while (history.length > MAX_HISTORY) {
    history.shift();
  }

  try {
    // Show typing indicator
    await ctx.sendChatAction('typing');

    const response = await anthropic.messages.create({
      model: 'claude-sonnet-4-20250514',
      max_tokens: 1024,
      system: 'You are a helpful assistant in a Telegram chat. Keep responses concise but informative. Use plain text formatting since Telegram has limited markdown support.',
      messages: history,
    });

    const assistantMessage = response.content[0].text;

    // Add assistant response to history
    history.push({ role: 'assistant', content: assistantMessage });

    await ctx.reply(assistantMessage);
  } catch (error) {
    console.error('Claude API error:', error);
    await ctx.reply('Sorry, I hit an error. Try again in a moment.');
    // Remove the failed user message from history
    history.pop();
  }
});

bot.launch();
console.log('Bot is running...');

process.once('SIGINT', () => bot.stop('SIGINT'));
process.once('SIGTERM', () => bot.stop('SIGTERM'));

Step 4: Run It

node bot.js

Open Telegram, find your bot, and start chatting. That is literally it. You now have Claude in your pocket.

Making It Production-Ready

The basic version works, but here are upgrades worth adding:

Persistent Conversation Storage

The in-memory Map loses everything when the process restarts. For a personal bot, SQLite is perfect — zero configuration, single file, and fast enough for thousands of conversations:

import Database from 'better-sqlite3';

const db = new Database('conversations.db');
db.exec(`
  CREATE TABLE IF NOT EXISTS messages (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    chat_id INTEGER NOT NULL,
    role TEXT NOT NULL,
    content TEXT NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  )
`);

function getHistory(chatId) {
  return db.prepare(
    'SELECT role, content FROM messages WHERE chat_id = ? ORDER BY id DESC LIMIT ?'
  ).all(chatId, MAX_HISTORY).reverse();
}

function saveMessage(chatId, role, content) {
  db.prepare(
    'INSERT INTO messages (chat_id, role, content) VALUES (?, ?, ?)'
  ).run(chatId, role, content);
}

Rate Limiting

If you share the bot with friends, you will want rate limiting to avoid surprise API bills:

const rateLimits = new Map();
const RATE_LIMIT = 20; // messages per hour
const RATE_WINDOW = 60 * 60 * 1000; // 1 hour

function isRateLimited(chatId) {
  const now = Date.now();
  const userLimits = rateLimits.get(chatId) || [];
  const recent = userLimits.filter(t => now - t < RATE_WINDOW);
  rateLimits.set(chatId, recent);
  if (recent.length >= RATE_LIMIT) return true;
  recent.push(now);
  return false;
}

Image Support

Claude can analyze images, and Telegram makes it easy to send them. Add a photo handler:

bot.on('photo', async (ctx) => {
  const photo = ctx.message.photo[ctx.message.photo.length - 1];
  const file = await ctx.telegram.getFile(photo.file_id);
  const url = `https://api.telegram.org/file/bot${process.env.TELEGRAM_BOT_TOKEN}/${file.file_path}`;

  const imageResponse = await fetch(url);
  const buffer = await imageResponse.arrayBuffer();
  const base64 = Buffer.from(buffer).toString('base64');

  const history = getHistory(ctx.chat.id);
  history.push({
    role: 'user',
    content: [
      { type: 'image', source: { type: 'base64', media_type: 'image/jpeg', data: base64 } },
      { type: 'text', text: ctx.message.caption || 'What do you see in this image?' }
    ]
  });

  // ... rest of the Claude API call
});

Deployment Options

For a personal bot, the cheapest options are:

  • A $5/month VPS (Hetzner, DigitalOcean) — run it with PM2 for process management
  • Railway or Render — free tier works for low-traffic bots
  • Your Raspberry Pi — if you have one sitting around, it is perfect for this

What It Costs

For personal use (maybe 20-30 messages a day), expect about $2-5/month in Anthropic API costs. Claude Sonnet is the sweet spot — smart enough for real conversations, cheap enough to not worry about. If you want to go cheaper, Claude Haiku works for simpler queries at roughly one-tenth the cost.

The whole project is maybe 100 lines of code for the basic version, 200 with the upgrades. No framework, no complex architecture. Just a bot, an API, and a conversation loop. Sometimes the simplest solution is the best one.

Share this article

Related Posts