Telegram Bots

Alfred has his own Telegram bot that acts as the primary interface between the DealDome team and the AI agent system. Built with go-telegram-bot-api, the bot listens for messages in group chats and direct messages, routes them through the AI pipeline, and sends back responses.

Overview

The Telegram bot is how the team interacts with Alfred day-to-day. Instead of a custom app or a web chat, everything happens inside Telegram — a platform the team already uses. The bot runs as part of the main Go binary, sharing the same database pool, Shopify clients, and AI agent setup.

When the bot starts, it opens a long-polling connection to the Telegram Bot API and listens for incoming updates. Every relevant message gets passed into the AI pipeline, and the response is sent back to the same chat.


Message flow

Here is what happens when someone sends a message to Alfred:

  1. User sends a message — either a direct message to the bot or an @mention in a group chat.
  2. Bot receives the updatego-telegram-bot-api parses the incoming update and extracts the message text, sender info, and chat context.
  3. Message is passed to the AI pipeline — the backend loads conversation history, retrieves relevant memories, and sends everything to Alfred via the Claude API.
  4. Alfred processes the request — he may answer directly or delegate to Roos or Elisa depending on the topic.
  5. Response is sent back — the bot posts Alfred's reply to the same Telegram chat, maintaining the conversational thread.

Simplified message handler

for update := range bot.ListenForUpdates(ctx) {
    if update.Message == nil {
        continue
    }

    // Skip group messages unless Alfred is mentioned
    if update.Message.Chat.IsGroup() && !isMentioned(update.Message, botUsername) {
        continue
    }

    // Process through AI pipeline
    response, err := agents.HandleMessage(ctx, agents.Input{
        Text:   update.Message.Text,
        ChatID: update.Message.Chat.ID,
        UserID: update.Message.From.ID,
    })
    if err != nil {
        log.Error("agent error", "err", err)
        continue
    }

    // Send response back to Telegram
    bot.Send(tgbotapi.NewMessage(update.Message.Chat.ID, response))
}

Bot features

The bot handles more than just plain text messages:

  • @mentions — responds when someone mentions Alfred by name or @username in a group.
  • Direct messages — responds to every DM without needing an @mention.
  • Media and files — accepts images, documents, and voice messages. Files are downloaded and passed to the AI pipeline as attachments.
  • Inline keyboards — Alfred can send messages with interactive buttons for quick actions (e.g., confirming a scheduled task).
  • Markdown formatting — responses are sent with Telegram's MarkdownV2 formatting for clean, readable output.

Group chat behavior

In group chats, the bot stays quiet unless it is explicitly addressed. This prevents Alfred from jumping into every conversation.

The bot responds in a group when:

  • Someone includes @alfred_bot (or whatever the bot's username is) in their message.
  • Someone mentions "Alfred" by name in the message text.

In all other cases, the bot ignores the message entirely. This keeps group chats clean while still making Alfred available whenever the team needs him.


Configuration

The bot needs two key values to operate, both stored as environment variables or in the settings table:

  • Name
    TELEGRAM_BOT_TOKEN
    Type
    string
    Description

    The bot token from @BotFather. This authenticates the bot with the Telegram API.

  • Name
    TELEGRAM_CHAT_ID
    Type
    string
    Description

    The default chat ID for outbound messages (e.g., scheduled task notifications, alerts). Can be a group or individual chat.

Environment setup

TELEGRAM_BOT_TOKEN=7123456789:AAHfGx...
TELEGRAM_CHAT_ID=-1001234567890

The chat ID is a negative number for group chats and a positive number for individual chats. You can get the chat ID by sending a message to the bot and checking the update payload.


Rate limiting and error handling

Telegram enforces rate limits on the Bot API — roughly 30 messages per second to different chats, and 20 messages per minute to the same group. The bot handles this in a few ways:

  • Retry with backoff — if Telegram returns a 429 (Too Many Requests), the bot waits for the duration specified in the Retry-After header before retrying.
  • Error logging — all Telegram API errors are logged to BetterStack with full context (chat ID, message text, error code).
  • Sentry alerts — persistent failures (e.g., invalid token, banned bot) trigger a Sentry alert so the team gets notified immediately.
  • Graceful degradation — if sending a response fails after retries, the error is logged but the bot keeps running. One failed message does not crash the process.

Retry logic

func (b *Bot) sendWithRetry(msg tgbotapi.Chattable) error {
    for attempt := 0; attempt < 3; attempt++ {
        _, err := b.api.Send(msg)
        if err == nil {
            return nil
        }
        if isRateLimited(err) {
            time.Sleep(getRetryAfter(err))
            continue
        }
        return fmt.Errorf("telegram send failed: %w", err)
    }
    return fmt.Errorf("telegram send failed after 3 attempts")
}

Was this page helpful?