← Guides
Playbook In Run operations intermediate · 18 min read · Updated Apr 23, 2026

Connect Claude to Telegram Business — supervised AI replies

A working architecture for routing your Telegram Business inbox through Claude, with the user kept firmly in the loop. Code, prompts, costs, and what to do when Claude is wrong.

Telegram Business announcement banner
Telegram Business — the personal account becomes a CRM, with bot-assisted replies as the killer add-on. Source: telegram.org press kit.

The killer feature of Telegram Business in 2026 isn’t the opening hours widget or the away message. It’s the bot you connect to your personal account — and the moment that bot starts drafting good replies, you’re running a one-person operation that scales like a five-person team.

This guide builds that bot, properly. Claude as the brain. You as the editor. No auto-send.

What you’ll build

A bot connected to your Business account that:

  1. Watches every incoming message to your account.
  2. Asks Claude to draft a reply in your voice.
  3. Posts the draft into a private channel you control (“Drafts”).
  4. Lets you tap Send or Edit in that channel; the bot then sends from your account.

Claude never speaks directly. You always sign off.

Architecture

[Customer DM] → [Your Telegram account] → [Telegram Business webhook]

                                        [Your bot server]
                                              ↓                ↓
                                        [Claude API]    [Conversation history DB]

                                       [Drafts channel] ← you approve

                                        [Bot sends as you] → [Customer]

Three components: a webhook, an LLM call with context, and an approval loop. We’ll build each in turn.

01

Provision the bot and connect it to your account

  1. Create a bot via @BotFather (/newbot).
  2. In Telegram, Settings → Telegram Business → Chatbots. Paste your bot’s @username. Grant Read incoming messages + Reply on your behalf.
  3. In your bot code, listen for business_connection updates to know when you’ve been connected, and store the resulting business_connection_id — you’ll need it on every outgoing reply.
// pseudo-code; pick your favorite framework
bot.on("business_connection", async (update) => {
  await db.businessConnection.upsert({
    where: { userId: update.user.id },
    update: { id: update.id, can_reply: update.can_reply },
    create: { id: update.id, userId: update.user.id, can_reply: update.can_reply },
  });
});
core.telegram.org Business Connection reference
02

Capture incoming messages

Telegram sends a business_message update when someone DMs your account. The shape is identical to a regular message, with an extra business_connection_id.

bot.on("business_message", async (update) => {
  const msg = update.business_message;
  await db.message.create({
    data: {
      direction: "in",
      chatId: msg.chat.id,
      fromUserId: msg.from.id,
      text: msg.text,
      receivedAt: new Date(msg.date * 1000),
      businessConnectionId: update.business_connection_id,
    },
  });
  await draftReply(msg);
});

The chat.id is the customer’s user id. Storing the conversation thread keyed on it is the simplest model.

03

Build the prompt

Claude needs three things to draft well: who you are, what you’ve already said, and what just came in.

async function draftReply(incomingMsg) {
  const history = await db.message.findMany({
    where: { chatId: incomingMsg.chat.id },
    orderBy: { receivedAt: "desc" },
    take: 20,
  });

  const conversation = history.reverse().map((m) => ({
    role: m.direction === "in" ? "user" : "assistant",
    content: m.text,
  }));

  const draft = await anthropic.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 600,
    system: SYSTEM_PROMPT,
    messages: [
      ...conversation,
      { role: "user", content: incomingMsg.text },
    ],
  });

  return draft.content[0].text;
}

The system prompt is where most of the work happens. Keep it short, specific, and firmly in your voice:

You draft Telegram DM replies on behalf of {your_name}, a {your_role}.
Voice: warm, concise, never gushing. Lowercase i for "I" only when the
human you're replying to does first.

Rules:
- Reply in the same language as the incoming message.
- If the message asks for a price, link them to /pricing rather than
  quoting (the human will adjust).
- Never promise calendar times. Suggest "later this week" and let
  the human commit.
- If the message is unclear, ask one clarifying question.
- If the message is hostile or spammy, draft a polite one-liner
  closing the thread.
- Maximum 4 sentences unless the user asks for detail.

Iterate on this prompt for two weeks. It’s the difference between drafts you ship 90% of the time vs 30%.

04

Post the draft for approval

Create a private channel in your account (“Drafts”). Add your bot as admin. Every draft posts there as a message with two inline buttons: Send and Edit.

async function postDraftForApproval(incomingMsg, draftText) {
  await bot.api.sendMessage(DRAFTS_CHANNEL_ID, formatPreview(incomingMsg, draftText), {
    reply_markup: {
      inline_keyboard: [[
        { text: "✓ Send", callback_data: `send:${incomingMsg.chat.id}:${draftId}` },
        { text: "✎ Edit", callback_data: `edit:${incomingMsg.chat.id}:${draftId}` },
      ]],
    },
  });
}

function formatPreview(incomingMsg, draftText) {
  return `*From* ${incomingMsg.from.first_name} (@${incomingMsg.from.username || "—"})

> ${incomingMsg.text}

---
*Draft:*

${draftText}`;
}

The Drafts channel becomes your morning queue. Skim, tap Send on what’s right, Edit on what’s not.

05

Send the approved reply as you

On callback, your bot uses the stored business_connection_id to send the reply on your behalf:

bot.on("callback_query", async (cq) => {
  const [action, chatId, draftId] = cq.data.split(":");
  const draft = await db.draft.findUnique({ where: { id: draftId } });
  const conn = await db.businessConnection.findFirst();

  if (action === "send") {
    await bot.api.sendMessage(Number(chatId), draft.text, {
      business_connection_id: conn.id,
    });
    await db.message.create({
      data: { direction: "out", chatId: Number(chatId), text: draft.text, sentAt: new Date() },
    });
    await bot.api.editMessageText(DRAFTS_CHANNEL_ID, cq.message.message_id, "✓ Sent: " + draft.text);
  }

  await bot.api.answerCallbackQuery(cq.id);
});

business_connection_id is what tells Telegram “this message comes from the user’s account, not the bot’s.” Without it, the customer sees the reply from your bot, not from you — different relationship entirely.

06

Handle Edit gracefully

Tap Edit → bot sends the draft text back to the Drafts channel as a regular message with a force_reply so you can edit and reply. Your reply becomes the new draft. Tap Send on the edited version.

A two-tap flow for the common case (it’s good), three taps for the edit case (it’s not perfect, fix it).

Cost economics

Per draft, with Claude Sonnet 4.6 at current pricing:

  • Input tokens: ~800 (system + 20-msg history + new message): $0.0024
  • Output tokens: ~250 (the draft): $0.0038
  • Per-draft cost: ~$0.006

100 drafts per day = $0.60/day = $18/month. For a one-person business this is ~30 minutes of saved typing time per day.

If you stay on Sonnet 4.6 for one year: $216. If you migrate to Haiku 4.5 for routine drafts and only invoke Sonnet on flagged messages, halve that.

What goes wrong

  • Claude is too verbose. Add Maximum 4 sentences to system prompt. Add Sentences must average under 16 words. Iterate.
  • Claude makes up facts about your offering. Move pricing/calendar/policy into a Quick Reply, not into Claude’s context. Have Claude link rather than quote.
  • Claude can’t see attachments. Telegram delivers media + caption. Pass the caption to Claude with a note “[user sent image]”. For real image understanding, route via Claude’s vision endpoints.
  • Two customers DM you the same thing simultaneously. Lock per-chat in your handler. Otherwise both drafts post to the channel at once and you’ll mis-tap.
  • The Drafts channel gets noisy. Add a topic filter (“only post drafts for messages from non-contacts” or “only when conversation is older than 24 hours”) to keep volume sane.

Privacy: tell people

Your /welcome Quick Reply should disclose: “An assistant drafts my replies for me to review before sending.” Some users will appreciate the transparency. The ones who don’t were never going to convert anyway.

When to graduate to auto-send

Track approval rate per category (using Claude itself to tag messages: pricing, scheduling, support, sales, hostile, other). When a category is at >95% approval rate over 100 messages, you can flip auto-send for that category — leave a manual review trigger for outliers.

The two categories that auto-send safely first: greeting acknowledgements and link-only replies (“here’s the pricing page: …”). The two that almost never do: pricing negotiation and refunds.

Stay in the loop

One short email when something useful ships. No tracking pixels, no upsell.