· 7 min read

Building a Practical Telegram + AI Bot: A Developer’s Tutorial

Learn how to wire Telegram updates to an AI model, manage conversation state, and return useful responses without turning your bot into a generic chatbot. This tutorial focuses on a production-friendly implementation you can adapt for support, summarization, and internal automation.

This tutorial shows how to build a Telegram bot that sends user messages to an AI model, returns a useful answer, and keeps enough context to feel coherent without becoming expensive or hard to maintain.

Instead of aiming for a “chat with everything” demo, we’ll build a bot with a clear shape:

  • Telegram receives the message
  • your backend normalizes and stores minimal context
  • an AI model generates a response
  • the bot sends the result back to the user

That architecture is useful for support bots, internal helpers, document Q&A, moderation assistants, and task automation.

What you will build

By the end, you’ll have a bot that can:

  • respond to /start and /help
  • process incoming text messages
  • keep per-chat conversation state
  • call an AI model with a structured prompt
  • send the AI response back to Telegram
  • handle timeouts and failures gracefully

We’ll use:

  • Telegram Bot API for messaging
  • Python for the backend
  • FastAPI for a webhook endpoint
  • OpenAI-style chat completion as the AI layer

You can swap Python for Node.js or any other stack. The important part is the flow and the state management.

Why webhook-based bots are better for AI workflows

For simple bots, long polling is fine. For AI-enabled bots, webhooks are often a better fit because:

  • they reduce latency
  • they scale better under bursty traffic
  • they integrate more naturally with async workers
  • they make it easier to enqueue AI work

If your bot may do more than one model call or may retrieve documents before answering, a webhook-based architecture gives you more room to grow.

High-level architecture

A practical AI bot usually has three layers:

1. Telegram transport

This layer receives updates from Telegram and sends messages back. It should stay thin.

2. Application logic

This layer decides what to do with each message: ignore, answer directly, fetch context, or route to a worker.

3. AI orchestration

This layer builds prompts, injects relevant context, calls the model, and post-processes the result.

A good rule: do not put business logic inside the prompt. Keep prompt content focused on generation, not control flow.

Step 1: Create the bot and set up Telegram

Create a bot with BotFather and save the token as an environment variable.

export TELEGRAM_BOT_TOKEN="123456:abc..."
export OPENAI_API_KEY="sk-..."

You’ll also need a public HTTPS endpoint for the webhook. During development, a tunnel such as ngrok can work, but production should use a real domain and TLS termination.

Step 2: Install dependencies

pip install fastapi uvicorn httpx python-dotenv

If you want a database, add your preferred driver later. For now, we’ll keep state in memory to make the example easy to follow.

Step 3: Build the webhook handler

Create main.py:

import os
import asyncio
from collections import defaultdict, deque

import httpx
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()

TELEGRAM_BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]

TELEGRAM_API = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}"

# Minimal in-memory conversation state.
# Replace with Redis or a database in production.
conversation_store = defaultdict(lambda: deque(maxlen=10))


async def send_message(chat_id: int, text: str):
    payload = {
        "chat_id": chat_id,
        "text": text,
        "parse_mode": "Markdown",
        "disable_web_page_preview": True,
    }
    async with httpx.AsyncClient(timeout=10) as client:
        response = await client.post(f"{TELEGRAM_API}/sendMessage", json=payload)
        response.raise_for_status()


async def call_ai(messages):
    # Replace this with your model provider of choice.
    # The important part is the shape of the messages list.
    headers = {
        "Authorization": f"Bearer {OPENAI_API_KEY}",
        "Content-Type": "application/json",
    }
    payload = {
        "model": "gpt-4.1-mini",
        "messages": messages,
        "temperature": 0.2,
    }

    async with httpx.AsyncClient(timeout=30) as client:
        response = await client.post(
            "https://api.openai.com/v1/chat/completions",
            headers=headers,
            json=payload,
        )
        response.raise_for_status()
        data = response.json()
        return data["choices"][0]["message"]["content"]


def build_messages(chat_id: int, user_text: str):
    history = list(conversation_store[chat_id])

    system_prompt = {
        "role": "system",
        "content": (
            "You are a concise Telegram assistant. "
            "Answer directly, avoid long introductions, and ask a clarifying question only if needed. "
            "If the user asks for code, provide production-oriented examples."
        ),
    }

    messages = [system_prompt]
    messages.extend(history)
    messages.append({"role": "user", "content": user_text})
    return messages


@app.post("/telegram/webhook")
async def telegram_webhook(request: Request):
    update = await request.json()

    message = update.get("message")
    if not message:
        return {"ok": True}

    chat = message.get("chat", {})
    chat_id = chat.get("id")
    text = message.get("text", "")

    if not chat_id or not text:
        return {"ok": True}

    if text == "/start":
        await send_message(chat_id, "Hi. Send me a question and I’ll answer with AI.")
        return {"ok": True}

    if text == "/help":
        await send_message(chat_id, "Try asking for a summary, rewrite, extraction, or code suggestion.")
        return {"ok": True}

    # Store the user message before generating a response.
    conversation_store[chat_id].append({"role": "user", "content": text})

    try:
        messages = build_messages(chat_id, text)
        answer = await call_ai(messages)

        # Store assistant response for future context.
        conversation_store[chat_id].append({"role": "assistant", "content": answer})

        await send_message(chat_id, answer)
    except Exception as e:
        await send_message(chat_id, "Sorry, I couldn’t generate a response right now.")
        raise HTTPException(status_code=500, detail=str(e))

    return {"ok": True}

Step 4: Run the app locally

uvicorn main:app --reload --port 8000

Then expose it:

ngrok http 8000

Set the Telegram webhook to your public HTTPS URL:

curl -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook" \
  -d "url=https://YOUR_PUBLIC_DOMAIN/telegram/webhook"

Step 5: Understand the message flow

When a user sends a message:

  1. Telegram POSTs the update to your webhook
  2. your handler extracts chat_id and message text
  3. your app builds a prompt from recent history
  4. the AI API returns a completion
  5. your bot sends the completion back to Telegram

That flow is simple, but there are a few important details.

Practical implementation details that matter

1. Keep message history short

Do not send the entire chat history on every request. It wastes tokens and increases latency. Keep a small sliding window, like the last 5–10 messages.

For many bots, recent context is enough.

If you need more memory, store summaries rather than raw history.

2. Use a structured system prompt

A good Telegram AI bot is not just “helpful.” It should be optimized for the use case.

Examples:

  • support bot: prioritize troubleshooting steps and ask for error messages
  • internal bot: answer in your team’s style and include links to internal docs
  • summarizer bot: produce bullet points and action items
  • code bot: keep answers implementation-oriented

Treat the system prompt as part of your product.

3. Separate transport errors from AI errors

Telegram delivery can fail independently of model calls.

Handle them separately:

  • if Telegram send fails, retry or log the transport problem
  • if the AI call fails, return a fallback message
  • if the webhook receives invalid payloads, ignore them safely

4. Add typing feedback for long operations

If AI generation takes more than a second or two, send a chat action like typing before the model call.

async def send_chat_action(chat_id: int, action: str = "typing"):
    payload = {"chat_id": chat_id, "action": action}
    async with httpx.AsyncClient(timeout=10) as client:
        await client.post(f"{TELEGRAM_API}/sendChatAction", json=payload)

Then call it before call_ai(...). This makes the bot feel responsive.

5. Watch your webhook time budget

Telegram expects webhook handlers to return quickly. If your model call may take too long, consider:

  • acknowledging the update immediately
  • pushing the AI work into a queue
  • sending the result later

For a first version, direct handling is acceptable. For production, asynchronous job processing is safer.

Upgrading from in-memory state to Redis

The in-memory conversation_store works for a demo, but it resets on restart and fails with multiple workers.

A better pattern is Redis:

  • key by chat_id
  • store a JSON array of recent messages
  • set a TTL if you want the context to expire

This gives you:

  • persistence across restarts
  • shared state across processes
  • easy cleanup

Example conceptually:

# Pseudocode
history_key = f"chat:{chat_id}:history"
redis.lpush(history_key, message_json)
redis.ltrim(history_key, 0, 9)
redis.expire(history_key, 86400)

If your bot serves many chats, Redis is a strong default.

Handling commands cleanly

In real bots, Telegram commands are not just UI sugar. They are a control surface.

Useful commands include:

  • /reset to clear context
  • /mode to switch behavior
  • /summary to summarize the current thread
  • /export to produce a structured report

For example, implement /reset like this:

if text == "/reset":
    conversation_store.pop(chat_id, None)
    await send_message(chat_id, "Context cleared.")
    return {"ok": True}

This is especially useful because AI contexts can drift.

Prompting for Telegram-specific output

Telegram has its own formatting and UX constraints.

A useful bot should output:

  • short paragraphs
  • bullet points when appropriate
  • limited markdown
  • no massive blocks of text unless requested

Example system prompt upgrade:

system_prompt = {
    "role": "system",
    "content": (
        "You are a Telegram assistant. Format responses for mobile screens. "
        "Prefer short paragraphs and bullets. Avoid unnecessary verbosity. "
        "When giving code, keep it focused and valid."
    ),
}

This is better than asking the model to be “helpful.”

Common failure modes and how to fix them

Model answers are too verbose

Fix:

  • lower temperature
  • tighten the system prompt
  • ask for bullet points or direct answers

Bot repeats itself

Fix:

  • reduce stored history
  • deduplicate user messages
  • ensure you are not appending the same text twice

Context becomes irrelevant

Fix:

  • summarize older turns
  • reset context on command
  • scope the bot to one task

Telegram messages break formatting

Fix:

  • escape Markdown carefully or use HTML mode
  • avoid passing raw model output directly if it may contain broken markup

Bot feels slow

Fix:

  • send typing indicators
  • shorten prompts
  • cache retrieval results
  • move model calls to background workers

A more production-ready pattern

If you want to move beyond the tutorial version, here is a better architecture:

  • webhook handler receives update
  • validate and persist the event
  • enqueue a job
  • worker builds prompt and calls model
  • worker stores result
  • sender process posts result to Telegram

That separation gives you:

  • retries
  • observability
  • easier scaling
  • less risk of webhook timeouts

For many Telegram AI bots, this is the architecture that survives real traffic.

Security and abuse prevention

Telegram bots that call AI models can be abused quickly if you skip controls.

Add these protections:

  • verify webhook source or use a secret path
  • rate-limit per user or per chat
  • reject oversized messages
  • avoid sending sensitive user data to the model unless necessary
  • keep API keys in environment variables or secret storage

If your bot handles internal data, be explicit about what is allowed to leave your system.

Example use cases

Here are a few practical bots you can build with this pattern:

Support triage bot

User sends a problem report, AI extracts:

  • product area
  • error details
  • urgency
  • likely next action

Meeting summary bot

Forward a discussion thread and generate:

  • key decisions
  • action items
  • open questions

Internal knowledge bot

Search documents first, then let AI answer with citations.

Moderation assistant

Classify messages, flag spam, and propose moderation actions.

Each of these is more useful than a generic chat interface because the output is constrained and operational.

Final advice

The biggest mistake developers make with Telegram + AI is building a vague chatbot. A good Telegram AI integration has a job, a prompt style, and a narrow set of behaviors.

Start with a small, reliable workflow:

  • one message in
  • one useful answer out
  • short memory
  • clear fallback behavior

Once that works, add retrieval, summaries, queues, and commands.

If you treat Telegram as the transport layer and the model as a specialized reasoning component, you’ll end up with a bot that is fast enough, cheap enough, and actually useful.

Next steps

If you want to extend this tutorial, the best additions are:

  • Redis-backed conversation history
  • job queue for AI requests
  • document retrieval before generation
  • command routing by intent
  • structured outputs with JSON schemas

Those upgrades turn a basic bot into a real assistant.

← All posts