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:
- User sends a message — either a direct message to the bot or an @mention in a group chat.
- Bot receives the update —
go-telegram-bot-apiparses the incoming update and extracts the message text, sender info, and chat context. - Message is passed to the AI pipeline — the backend loads conversation history, retrieves relevant memories, and sends everything to Alfred via the Claude API.
- Alfred processes the request — he may answer directly or delegate to Roos or Elisa depending on the topic.
- 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.
In direct messages, there is no filtering — every message is treated as a request to Alfred. This is the simplest way to have a private conversation with the agent.
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-Afterheader 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")
}