Make cool Telegram bots with Rust easily.

Modern, fast, and easy to use

It's easy to start making bots with tbot even for comers from other languages, such as Python or JavaScript, thanks to its high-level design. Code written with tbot is easy to read, understand and reason about.

tbot supports async/.await, all built upon tokio. This lets you write clean and fast asynchronous code with a rich ecosystem built around tokio.

tbot lets you easily manage your bot's state, be it in memory, in a file or in a database, yet it doesn't constrain you in any way. Also, you can easily work with markup in tbot, without fearing that some user input will break it.

use tbot::prelude::*;
use tokio::sync::Mutex;

async fn main() {
  let mut bot = tbot::from_env!("BOT_TOKEN")

  bot.command("counter", |context, counter| {
    async move {
      let message = format!(
        "The counter is {} now",



Idiomatic and type-safe API

Every type in tbot is though out. You won’t have to .unwrap() several fields after checking a type’s kind, and get a panic if some assumption turned out to be false. In tbot, you can only access such information via pattern matching.

tbot also has a type-safe API. You’ll never forget to specify a required parameter, or specify conflicting parameters. You won’t get a runtime error because of a typo that could be prevented at compile time.

use tbot::{errors::MethodCall, types::chat};

async fn main() -> Result<(), MethodCall> {
  let bot = tbot::from_env!("BOT_TOKEN");
  let chat = chat::Id(..);
  let chat = bot.get_chat(chat).call().await?;

  match chat.kind {
    chat::Kind::Group { title, .. } =>
      println!("I’m in {}", title),
    _ => println!("Oh, it wasn’t a group"),


Complete Bot API support

tbot, among other features, lets you upload and download media, and use webhooks for your bot. tbot already implements Bot API 4.8, which means that you can already work with latest features. That includes MarkdownV2, animated stickers, Telegram Passport, polls v2.0 and other. tbot’s modular design guarantees that new Bot API updates can be quickly implemented in tbot once they’re out.

use tbot::types::{chat, input_file::Audio};
use tokio::fs::read;

type Error = Box<dyn std::error::Error>;

async fn main() -> Result<(), Error> {
  let bot = tbot::from_env!("BOT_TOKEN");
  let chat = chat::Id(..);
  let bytes = read("Rusty James.mp3").await?;
  let audio = Audio::bytes(&bytes);
  bot.send_audio(chat, audio).call().await?;


Scalable and configurable

Code written with tbot scales well as complexity of your bot increases. Need to shut down gracefully on SIGINT? Just select the event loop and the signal handler. Want to replace polling with webhooks? Just call bot.webhook instead of bot.polling. Want to have a handler generic over several contexts? No problem. You can always go from a simple way to accomplish one thing to a more powerful one without rewriting your bot’s logic.

use tbot::prelude::*;
use tokio::signal::ctrl_c;
use futures::future::select;

async fn main() {
  let mut bot = tbot::from_env!("BOT_TOKEN")

  bot.text(|context| async move { .. });

  let polling = bot.polling().timeout(60);

  // shut down gracefully...

Ready to write your first bot with tbot? Start with this tutorial. You can also check out our examples and try some real-world bots built with tbot.