TelegramBotEngine
A mountable Rails engine that adds subscriber management, authorization, broadcasting, and an admin UI on top of the telegram-bot gem (v0.16.x).
The telegram-bot gem handles all Telegram protocol concerns (API client, webhook ingestion, controller/command routing, callback queries, session, async delivery, and testing). This engine adds the persistence and management layer that telegram-bot deliberately doesn't provide.
Installation
Add to your Gemfile:
gem "telegram_bot_engine"
Install migrations:
bin/rails telegram_bot_engine:install:migrations
bin/rails db:migrate
Configuration
Bot token
Following telegram-bot's convention, configure via Rails credentials:
# config/credentials.yml.enc
telegram:
bot:
token: "YOUR_BOT_TOKEN"
username: "your_bot"
Engine configuration
# config/initializers/telegram_bot_engine.rb
TelegramBotEngine.configure do |config|
# Authorization: only these Telegram usernames can /start and subscribe.
config.allowed_usernames = %w[alice bob charlie]
# OR dynamic:
# config.allowed_usernames = -> { MyModel.pluck(:telegram_username) }
# OR managed via admin UI:
# config.allowed_usernames = :database
# OR open access (no allowlist):
# config.allowed_usernames = nil
# Optional: disable admin UI
# config.admin_enabled = false
# Optional: custom messages
# config.unauthorized_message = "Sorry, you're not authorized to use this bot."
# config.welcome_message = "Welcome %{username}! Available commands:\n%{commands}"
# Event logging — logs commands, deliveries, auth failures to the database
# config.event_logging = true # default: true
# config.event_retention_days = 30 # default: 30, auto-purges older events
end
Webhook controller
Create a controller inheriting from telegram-bot's UpdatesController and include the engine's concern:
# app/controllers/telegram_webhook_controller.rb
class TelegramWebhookController < Telegram::Bot::UpdatesController
include TelegramBotEngine::SubscriberCommands
# Provides: start!, stop!, help! with authorization and subscription management.
# Add your own commands:
def status!(*)
respond_with :message, text: "All systems operational"
end
end
Routes
# config/routes.rb
Rails.application.routes.draw do
telegram_webhook TelegramWebhookController
# Mount admin UI (protect with your own authentication)
authenticate :user, ->(u) { u.admin? } do
mount TelegramBotEngine::Engine, at: "/telegram/admin"
end
end
Running more than one bot? Replace
telegram_webhookwith the engine's inbound dispatcher — see Multiple bots.
Set webhook
These rake tasks come from the telegram-bot gem:
# Register your app's URL with Telegram so it sends updates to your server
bin/rails telegram:bot:set_webhook RAILS_ENV=production
# Remove the webhook (e.g. before switching to polling)
bin/rails telegram:bot:delete_webhook
# Run a local poller for development (no webhook needed)
bin/rails telegram:bot:poller
The webhook URL is derived from your Rails routes (telegram_webhook route helper). Make sure your production server is accessible via HTTPS before setting the webhook.
For local development with ngrok, configure default_url_options in config/environments/development.rb:
if ENV['HOST'].present?
routes.[:host] = ENV.fetch("HOST", "localhost:3000")
routes.[:protocol] = ENV.fetch("PROTOCOL", "https")
end
Then run:
HOST=your-subdomain.ngrok-free.app bin/rails telegram:bot:set_webhook
Usage
Broadcasting
# Broadcast to all active subscribers
TelegramBotEngine.broadcast("Deployment complete!")
# With Markdown formatting
TelegramBotEngine.broadcast(
"*Deploy complete*\nVersion: `v2.3.4`",
parse_mode: "Markdown"
)
Direct messaging
TelegramBotEngine.notify(
chat_id: 123456789,
text: "Your report is ready."
)
Admin UI
When mounted, the engine provides a web interface for:
- Dashboard — bot info, subscription counts
- Subscriptions — list, activate/deactivate, delete
- Allowlist — add/remove usernames (when
config.allowed_usernames = :database) - Events — browsable log of commands, deliveries, and auth failures with filtering by type, action, and chat ID
Event log
The engine automatically logs operational events to the telegram_bot_engine_events table:
| Event type | Actions | When |
|---|---|---|
command |
start, stop, help |
User sends a bot command |
delivery |
broadcast, notify, delivered, blocked |
Messages are queued or delivered |
auth_failure |
unauthorized |
Unauthorized user attempts a command |
Events are viewable in the admin UI and can be queried directly:
# Recent command events
TelegramBotEngine::Event.by_type("command").recent.limit(20)
# Deliveries to a specific chat
TelegramBotEngine::Event.by_type("delivery").by_chat_id(123456789)
# Events in the last 24 hours
TelegramBotEngine::Event.since(24.hours.ago)
# Manual purge (automatic purge runs probabilistically)
TelegramBotEngine::Event.purge_old!
Disable event logging entirely with config.event_logging = false.
Multiple bots
The engine manages many Telegram bots from one Rails host. Everything keyed by a Telegram bot identity — subscribers, allowlist, delivery, and inbound routing — is scoped per bot. Existing single-bot setups keep working unchanged: every call without a bot: argument targets the default bot, which is seeded automatically from your existing telegram.bot credentials (or TELEGRAM_BOT_TOKEN).
The Bot model
Each bot is a persisted TelegramBotEngine::Bot record (token, slug, and per-bot webhook credentials). Manage them in the admin UI, a seed, or the console:
TelegramBotEngine::Bot.create!(
name: "Support bot",
slug: "support",
token: Rails.application.credentials.dig(:telegram, :support_bot, :token),
active: true
)
TelegramBotEngine::Bot.default # the back-compat anchor (auto-seeded on first use)
TelegramBotEngine::Bot.resolve("support") # look up by slug
bot.client # this bot's memoized Telegram::Bot::Client
webhook_id (the non-secret id that appears in the webhook URL) and webhook_secret (the bearer credential, sent only in the X-Telegram-Bot-Api-Secret-Token header) are generated automatically.
Bot-aware delivery
support = TelegramBotEngine::Bot.resolve("support")
# Broadcast / notify through a specific bot's own client
TelegramBotEngine.broadcast("Support is online", bot: support)
TelegramBotEngine.notify(chat_id: 123456789, text: "Ticket updated", bot: support)
# Omit bot: to target the default bot (unchanged single-bot behavior)
TelegramBotEngine.broadcast("Deployment complete!")
Subscribers and allowlist entries are scoped per bot: a user who blocks the support bot is deactivated only for that bot, and a per-bot allowlist entry never escalates into a global one.
Inbound updates for multiple bots
telegram-bot's telegram_webhook route serves a single bot. For multiple bots, mount the engine's inbound dispatcher once — it resolves the target bot from the URL, validates the secret-token header, then hands off to your UpdatesController:
# config/initializers/telegram_bot_engine.rb
TelegramBotEngine.configure do |config|
config.webhook_base_url = "https://app.example.com" # host public HTTPS base
config.webhook_mount_path = "/telegram/bot" # must match the mount below
config.dispatch_controller = "TelegramWebhookController" # your UpdatesController (incl. SubscriberCommands)
config.auto_register_webhooks = true # (un)register webhooks on Bot save/destroy
end
# config/routes.rb
mount TelegramBotEngine::Dispatch, at: "/telegram/bot"
With webhook_base_url set and auto_register_webhooks enabled, saving an active Bot registers its Telegram webhook (<base_url>/telegram/bot/<webhook_id>, secret in the header) and deactivating it removes the webhook — no manual set_webhook per bot. You can also (re)register explicitly:
TelegramBotEngine::WebhookRegistrar.register(TelegramBotEngine::Bot.resolve("support"))
Unlike telegram-bot 0.16, the dispatcher validates the X-Telegram-Bot-Api-Secret-Token header (constant-time) on every inbound request, so only Telegram can drive your bots.
Requirements
- Ruby >= 3.3.0
- Rails >= 7.0
telegram-bot~> 0.16
License
MIT