How to Log Application Errors Using a Telegram Bot

A lightweight, free alternative to paid error tracking tools. Learn how to send Rails application errors directly to your Telegram chat using a bot and a few lines of code.

Kyrylo Silin
Paul Kim
Guest author

Error logging is one of those things every application needs, yet it’s often a pain to set up, especially for side projects or MVPs. Paid tools like Sentry or Appsignal are great, but they can feel like overkill when you just want something lightweight and free.

That’s where Telegram comes in. With just a few lines of code, you can set up a Telegram bot to send error logs directly to your chat. It’s fast, simple, and surprisingly handy for catching issues in real time.

Step 1: Set up your Telegram bot

If you don’t already have one, sign up for a Telegram account and create a bot.

  1. Open Telegram and start a chat with @BotFather (this is the official Telegram bot for creating and managing bots).
  2. Run the command /newbot and follow the prompts.
  3. Once your bot is created, BotFather will give you an API token. Save it to use later in your application.
  4. You’ll also need the chat ID of the conversation where your bot will post messages (this could be a private chat or a group). To find it:
    • Add your bot to a chat or group.
    • Send any message in that chat.
    • Visit the URL below (replace {API_TOKEN} with your bot’s token) and look for the "chat":{"id": ... field — this is your chat ID.
      https://api.telegram.org/bot{API_TOKEN}/getUpdates

Step 2: Add your environment variables

In your Rails app, add two environment variables to your .env file (or your deployment secrets) with the values from step 1.

TELEGRAM_BOT_TOKEN=your_bot_token_here
TELEGRAM_CHAT_ID=your_chat_id_here

Step 3: Add the gem

Add the telegram-bot-ruby gem to your Gemfile and install it.

bundle add telegram-bot-ruby

Step 4: Create a notification service

Now we’ll create a service class that handles sending messages to Telegram. This class will take care of formatting the message and posting to the chat.

# lib/telegram/notification.rb

require "telegram/bot"

class Telegram::Notification
  TELEGRAM_MESSAGE_LIMIT = 4096 # Ensures we don’t exceed Telegram’s message length restriction.
  TELEGRAM_BOT_TOKEN = ENV["TELEGRAM_BOT_TOKEN"]
  TELEGRAM_CHAT_ID = ENV["TELEGRAM_CHAT_ID"]

  def send_backend_error(user_email:, exception_class_name:, error_message:, class_name:)
    message = build_error_log(
      type: "Backend Error",
      user_email: user_email,
      class_name: class_name,
      messages: [exception_class_name, error_message]
    )
    deliver(message)
  end

  def send_frontend_error(user_email:, error:, message:)
    message = build_error_log(
      type: "Frontend Error",
      user_email: user_email,
      messages: [message, error]
    )
    deliver(message)
  end

  private

  def build_error_log(type:, user_email:, class_name: nil, messages: [])
    message_parts = []
    message_parts << "[#{type}]"
    message_parts << "- [User: #{user_email || "Unknown"}]"
    message_parts << "[#{class_name}]" if class_name
    message_parts << messages.compact.join(": ")

    message_parts.join(" ").slice(0, TELEGRAM_MESSAGE_LIMIT)
  end

  def deliver(message)
    if Rails.env.production?
      if TELEGRAM_BOT_TOKEN.blank? || TELEGRAM_CHAT_ID.blank?
        Rails.logger.error("Telegram Bot token or chat ID not configured")
        return
      end

      begin
        Telegram::Bot::Client.run(TELEGRAM_BOT_TOKEN) do |bot|
          bot.api.send_message(chat_id: TELEGRAM_CHAT_ID, text: message)
        end
      rescue => e
        Rails.logger.error("Failed to send Telegram message: #{e.message}")
      end
    else
      Rails.logger.info(message)
    end
  end
end

Step 5: Set up background jobs

Ideally, we want to post errors to Telegram asynchronously. So let’s add two background jobs — one for frontend errors and one for backend errors — to call the service.

# app/jobs/telegram/frontend/notification_job.rb

class Telegram::Frontend::NotificationJob < ApplicationJob
  queue_as :default

  def perform(user_email:, details:, title:)
    Telegram::Notification.new.send_frontend_error(
      user_email: user_email,
      error: details,
      message: title
    )
  end
end

# app/jobs/telegram/backend/notification_job.rb

class Telegram::Backend::NotificationJob < ApplicationJob
  queue_as :default

  def perform(user_email:, error_message:, exception_class_name:, class_name:)
    Telegram::Notification.new.send_backend_error(
      user_email: user_email,
      exception_class_name: exception_class_name,
      error_message: error_message,
      class_name: class_name
    )
  end
end

Step 6: Capture frontend errors

For frontend issues, we’ll create a simple controller to receive error reports from the browser.

# app/controllers/errors_controller.rb

class ErrorsController < ApplicationController
  def create
    params = JSON.parse(request.body.read)

    Telegram::Frontend::NotificationJob.perform_later(
      user_email: current_user&.email,
      title: params["title"],
      details: params["details"]
    )

    render json: { status: true }
  end
end

# config/routes.rb
resources :errors, only: [:create]

Then, in your JavaScript, wire up an error handler to post errors to that controller.

# app/javascript/application.js

import "@hotwired/turbo-rails"
import "controllers"

if (window.Stimulus) {
  Stimulus.handleError = (error, message, detail) => {
    // Log error to console
    console.error(`${message}\n\n`, error, detail);

    // Post error to server
    fetch("/errors", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').getAttribute('content')
      },
      body: JSON.stringify({
        title: message,
        details: `${detail.identifier} - ${error.message}`
      })
    });
  };
}

Whenever a frontend error occurs, this will send a notification to your Telegram chat.

Step 7: Capture backend errors

For backend exceptions, we can use a concern that catches errors globally and queues the notification job.

# app/controllers/concerns/error_handling.rb
module ErrorHandling
  extend ActiveSupport::Concern

  included do
    rescue_from Exception do |exception|
      Telegram::Backend::NotificationJob.perform_later(
        user_email: current_user&.email,
        exception_class_name: exception.class.name,
        error_message: exception.message,
        class_name: self.class.name
      )

      raise exception
    end
  end
end
class ApplicationController < ActionController::Base
  include ErrorHandling
end

This ensures that even unexpected exceptions are logged to Telegram, while still being re-raised so your app behaves normally.

Wrapping up

And that’s it! Your Rails application can now send error logs straight to Telegram. It’s quick to set up, free to run, and perfect for side projects or MVPs. Now you'll get a message whenever one of your users runs into an error.

Here's a sample pull request for the full code snippet.