Running Your Bot
There are two ways your bot can receive updates from Telegram: long polling and webhooks. This page covers both, along with environment configuration and graceful shutdown.
Environment Variables
The framework reads the bot token from your code, not from environment variables directly. However, the convention across all examples is:
export TELEGRAM_BOT_TOKEN="123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
Then in your code:
#![allow(unused)]
fn main() {
let token = std::env::var("TELEGRAM_BOT_TOKEN")
.expect("TELEGRAM_BOT_TOKEN must be set");
}
Never hard-code your token. Use environment variables, a .env file, or a secrets manager.
Long Polling
Long polling is the simplest approach and the right choice for development and most deployments.
#![allow(unused)]
fn main() {
let app = ApplicationBuilder::new().token(token).build();
// ... register handlers ...
app.run_polling().await.unwrap();
}
run_polling() handles the full lifecycle internally:
- Calls
initialize()to set up the application. - Calls
start()to begin processing. - Enters a loop calling
getUpdateson the Telegram API. - Dispatches each update to your registered handlers.
- On
Ctrl+C, callsstop()andshutdown().
How Long Polling Works
Your bot sends a request to Telegram saying “give me any new updates.” Telegram holds the connection open (up to 30 seconds by default) until either a new update arrives or the timeout expires. This is efficient – there is no busy-waiting.
Webhooks
For production deployments behind a reverse proxy, webhooks are more efficient. Telegram pushes updates to your server instead of your server pulling them.
Enable the webhooks feature:
[dependencies]
rust-tg-bot = { version = "1.0.0-rc.1", features = ["webhooks"] }
Simple Webhook
use rust_tg_bot::ext::prelude::{ApplicationBuilder, Arc, Context, HandlerResult, Update};
// ... define handlers ...
#[tokio::main]
async fn main() {
let token = std::env::var("TELEGRAM_BOT_TOKEN").unwrap();
let webhook_url = std::env::var("WEBHOOK_URL").unwrap();
let app = ApplicationBuilder::new().token(token).build();
// ... register handlers ...
app.run_webhook(&webhook_url, "0.0.0.0:8443").await.unwrap();
}
Custom Webhook with axum
For full control, use the manual lifecycle and run your own axum server alongside the bot. This lets you add health checks, custom endpoints, and other routes. See the Webhooks guide for a complete example.
The Application Lifecycle
Whether you use polling or webhooks, the application goes through these stages:
initialize --> start --> idle --> stop --> shutdown
| Stage | What Happens |
|---|---|
initialize() | Calls getMe to validate the token, loads persistence data, runs post_init hook |
start() | Begins the update processing loop |
| idle | The bot is running and handling updates |
stop() | Signals the processing loop to stop, runs post_stop hook |
shutdown() | Flushes persistence, cleans up resources, runs post_shutdown hook |
When you call run_polling() or run_webhook(), all of these stages are managed automatically. You only need to call them manually if you are building a custom server setup.
Lifecycle Hooks
You can register hooks that run at specific lifecycle stages:
#![allow(unused)]
fn main() {
use rust_tg_bot::ext::prelude::{ApplicationBuilder, Arc};
let post_init = Arc::new(|app: Arc<_>| Box::pin(async move {
println!("Bot initialized! Username: {:?}",
app.bot().bot_data().and_then(|d| d.username.clone()));
}) as std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>);
let app = ApplicationBuilder::new()
.token(token)
.post_init(post_init)
.build();
}
Available hooks:
post_init– runs afterinitialize().post_stop– runs afterstop().post_shutdown– runs aftershutdown().
Concurrent Update Processing
By default, updates are processed one at a time. For bots with high traffic, enable concurrent processing:
#![allow(unused)]
fn main() {
let app = ApplicationBuilder::new()
.token(token)
.concurrent_updates(8)
.build();
}
This allows up to 8 updates to be processed simultaneously. The value 0 is treated as 1.
Logging
Enable structured logging for debugging:
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
// ... rest of your bot ...
}
Control verbosity with the RUST_LOG environment variable:
RUST_LOG=info TELEGRAM_BOT_TOKEN="..." cargo run
RUST_LOG=rust_tg_bot=debug cargo run
RUST_LOG=trace cargo run
Next Steps
Now that your bot is running, learn about the building blocks: