tg_error_notifier
Gem for Rails error notifications to Telegram.
What it catches
- Unhandled errors in Rack/Rails request cycle.
- Failed ActiveJob executions.
Quick usage
# Gemfile
gem "tg_error_notifier"
# config/initializers/telegram_error_notifier.rb
Rails.application.configure do
config.telegram_error_notifier.bot_token = ENV["TELEGRAM_BOT_TOKEN"]
config.telegram_error_notifier.chat_id = ENV["TELEGRAM_ERRORS_CHAT_ID"]
config.telegram_error_notifier.app_name = "my_app"
end
Environment variables
TELEGRAM_BOT_TOKENTELEGRAM_ERRORS_CHAT_IDTELEGRAM_ERRORS_APP_NAME(optional)
Telegram setup (bot, private channel/group, chat_id)
1. Create a bot and get token
- Open Telegram and start chat with
@BotFather. - Send
/newbotand follow prompts (bot name + username ending withbot). - Copy the HTTP API token (looks like
123456:ABC...) and save it asTELEGRAM_BOT_TOKEN.
2. Create a private channel or private group
Private channel
- Telegram -> New Channel -> set as Private.
- Open channel settings -> Administrators -> add your bot as admin.
- Grant at least permission to post messages.
Private group (or supergroup)
- Telegram -> New Group -> add at least one member.
- Add your bot to the group.
- Promote bot to admin if needed (recommended for reliability).
- Keep group private (no public username).
3. Get chat_id
Method A (easy): @userinfobot / @RawDataBot
- Add bot
@userinfobot(or@RawDataBot) into your target channel/group. - Send any message there.
- Open the helper bot dialog and read chat id.
- For channels/supergroups it is usually negative and starts with
-100.... - Delete this bot from your channel.
Method B (official API): getUpdates
- Send at least one message in target chat after adding your bot.
- Open in browser:
https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/getUpdates - Find
chatobject and copychat.id.
Examples:
- Private channel/supergroup:
-1001234567890 - Private group (old style):
-123456789
4. Validate before using in Rails
- Ensure bot is present in the target chat.
- Ensure bot has permission to send messages.
- Put value into
TELEGRAM_ERRORS_CHAT_IDand run a smoke test.
Error grouping
Group identical errors to avoid flooding. When the same exception repeats within a time window, only the first message is sent — subsequent occurrences are suppressed and reported as a count in the next message.
TgErrorNotifier.configure do |config|
config.grouping_enabled = true
config.grouping_window = 60 # seconds (default)
end
Errors are grouped by exception class + normalized message (IDs and UUIDs are replaced with placeholders for better deduplication).
Forum topics (threads)
Automatically create a Telegram Forum topic (thread) per unique error type. Each error gets its own topic in a supergroup with Forum Topics enabled.
TgErrorNotifier.configure do |config|
config.topics_enabled = true
config.topic_icon_color = 0xFB6F5F # red (default), optional
end
Requirements: The chat must be a supergroup with Forum Topics enabled. The bot must have can_manage_topics admin permission.
You can combine both features — errors will be grouped within their respective topics:
TgErrorNotifier.configure do |config|
config.grouping_enabled = true
config.grouping_window = 60
config.topics_enabled = true
end
Manual notification
begin
do_work
rescue => e
pp TgErrorNotifier.capture_exception(e)
# or with extra context:
TgErrorNotifier.capture_exception(
e,
source: "custom",
context: { feature: "sync", user_id: current_user&.id }
)
raise
end
capture_exception returns a diagnostic hash, e.g.:
{ sent: true, status: :sent, code: 200 }{ sent: false, status: :skipped, reason: "missing_chat_id" }{ sent: false, status: :failed, reason: "telegram_api_error", code: 400, body: "..." }
Manual message
TgErrorNotifier.(
"Background sync started",
level: :info,
source: "custom",
context: { feature: "sync", user_id: current_user&.id }
)
capture_message returns the same diagnostic hash format as capture_exception.
Named topics for messages
Send messages to a dedicated Forum topic by name. The topic is created on first use (blue icon) and reused afterwards:
TgErrorNotifier.("New account: #{account.name}", topic: "Registrations")
Since the topic → thread_id mapping is cached in memory per process, configure a persistent store so restarts and multiple processes (Puma workers, Sidekiq) don't create duplicate topics:
TgErrorNotifier.configure do |config|
config.topic_store_read = ->(name) { MyKeyValueStore.get("tg_topic:#{name}") }
config.topic_store_write = ->(name, thread_id) { MyKeyValueStore.set("tg_topic:#{name}", thread_id) }
end
Without a store everything still works, but each process creates its own topic. Named topics work independently of topics_enabled (which controls per-exception topics). Requirements are the same: forum supergroup + can_manage_topics bot permission.
Buttons under the message
Attach an inline keyboard to error notifications — e.g. a link that files the error as a task on your board. The gem does not interpret the buttons: whatever the callable returns is passed to Telegram as is, so url, callback_data and web_app buttons all work.
TgErrorNotifier.configure do |config|
config. = lambda do |kind:, source:, context:, fingerprint:, exception: nil, message: nil|
next nil unless kind == :exception
token = ErrorTaskLink.issue(exception: exception, source: source, fingerprint: fingerprint)
[[{ text: "\u2795 To the board", url: "https://example.com/e/#{token}" }]]
end
end
Arguments:
| key | value |
|---|---|
kind |
:exception for capture_exception/rescued errors, :message for capture_message |
exception |
the exception (nil for :message) |
message |
the message text (nil for :exception) |
source |
where it came from ("Sidekiq: MyWorker", "manual", ...) |
context |
the context hash passed to the notifier |
fingerprint |
grouping key of the exception — stable across repeats, use it to deduplicate whatever the button creates (nil for :message) |
Return an array of rows ([[button, button], [button]]); a flat array becomes a single row; nil or [] means no keyboard. Exceptions raised inside the callable are logged and the notification is still delivered — without the keyboard.