rustigram
A comprehensive, async-first Rust framework for the Telegram Bot API.
[]
= "0.9.5"
= { = "1", = ["full"] }
Overview
rustigram is a typed, ergonomic Rust library for building Telegram bots. Every Bot API method is exposed as an awaitable builder — set only the parameters you need, then .await. Incoming updates are routed through a composable filter-handler pipeline running concurrently on tokio.
The framework is split into focused crates so you can depend on only what you need:
| Crate | Purpose |
|---|---|
rustigram-types |
All Bot API types, fully serde-serialisable |
rustigram-api |
HTTP client and typed method builders |
rustigram-bot |
Dispatcher, filters, handlers, FSM, update listeners |
rustigram-macros |
Procedural macros (#[handler], #[derive(DialogueState)]) |
rustigram |
Public facade re-exporting all sub-crates |
Supported Bot API version: 9.6 (April 2026)
Minimum Rust version: 1.75
Table of Contents
- rustigram
- Overview
- Table of Contents
- Quick start
- Architecture
- Receiving updates
- Sending messages
- Filters
- Handlers
- Callback queries and inline keyboards
- Conversation state (FSM)
- Shared state
- File uploads
- Payments and Stars
- Webhook setup
- Local Bot API server
- Error handling
- Examples
- Running the tests
- Contributing
- License
Quick start
use *;
async
async
async
async
Get your bot token from @BotFather, then:
BOT_TOKEN=your_token
Architecture
Telegram
│ getUpdates (long poll) or POST (webhook)
▼
Dispatcher
├── Route 1: filter::command("start") ──► handler A ─┐
├── Route 2: filter::callback_query() ──► handler B │ tokio::spawn
├── Route 3: filter::message() ──► handler C │ (concurrent)
└── Fallback ──► handler D ──┘
│
Context { update, bot }
│
ctx.bot.send_message(...).await
Each incoming update is dispatched in its own tokio::spawn task. Handlers run concurrently — a slow handler does not block others. The dispatcher evaluates routes in registration order and stops at the first match.
Receiving updates
Long polling
Long polling is the simplest mode and requires no public server.
bot.dispatcher
.on
.build
.polling
.await?;
The poller automatically advances the offset after each batch, so updates are never processed twice. Transient network errors (timeout, connection reset) are retried automatically. Rate-limit responses (HTTP 429) honour the retry_after value from Telegram.
Webhook
// Register the webhook URL with Telegram once
bot.client
.set_webhook
.secret_token
.await?;
// Start the axum-based server
bot.dispatcher
.on
.build
.webhook
.await?;
Supported Telegram webhook ports: 443, 80, 88, 8443.
The secret token is validated on every incoming request. Requests with a missing or incorrect X-Telegram-Bot-Api-Secret-Token header are rejected with HTTP 401.
Sending messages
Every API method returns a builder. Call only the setters you need, then .await.
// Plain text
ctx.bot.send_message.await?;
// With formatting and a reply
ctx.bot
.send_message
.parse_mode
.reply_to
.await?;
// Disable notification
ctx.bot
.send_message
.disable_notification
.await?;
// Photo from URL
ctx.bot
.send_photo
.caption
.await?;
// Forward a message
ctx.bot.forward_message.await?;
// Delete a message
ctx.bot.delete_message.await?;
// Edit an existing message
ctx.bot
.edit_message_text
.parse_mode
.await?;
Available send methods
| Method | Description |
|---|---|
send_message |
Text (up to 4096 characters) |
send_photo |
Photo (.jpg, .png, etc.) |
send_audio |
Audio file treated as music |
send_document |
General file |
send_video |
Video (MPEG4) |
send_animation |
GIF or silent H.264 video |
send_voice |
Voice note (OGG/OPUS) |
send_video_note |
Rounded-square video |
send_sticker |
.WEBP / .TGS / .WEBM sticker |
send_location |
Map point, optionally live |
send_contact |
Phone contact |
send_poll |
Native poll or quiz |
send_dice |
Animated dice/emoji |
send_invoice |
Payment invoice |
send_media_group |
Album of 2–10 media items |
send_chat_action |
Typing indicator etc. |
forward_message |
Forward from another chat |
copy_message |
Copy without forward header |
Filters
Filters are composable predicates evaluated against an incoming Context. The dispatcher calls the first handler whose filter returns true.
Built-in filters
use filters;
message // Any Message update
edited_message // Any EditedMessage update
callback_query // Any CallbackQuery update
inline_query // Any InlineQuery update
command // /start (case-insensitive, strips @BotName)
text // Exact text match
text_contains // Substring match
callback_data // Exact callback data
callback_data_prefix // Callback data prefix
private // Private chats only
group // Groups and supergroups
any // Always passes
Combinators
use filters;
use FilterExt;
// Both must match
message.and
// Either can match
private.or
// Invert
private.not
// Chain freely
command
.and
.and
Custom filters
use ;
let has_photo = filter_fn;
bot.dispatcher
.on
.build
.polling
.await?;
Handlers
A handler is any async function with the signature async fn(Context) -> BotResult<()>.
use *;
async
Handlers can also be closures:
bot.dispatcher
.on
.build
.polling
.await?;
Callback queries and inline keyboards
use *;
use ;
async
async
// Registration
bot.dispatcher
.on
.on
.build
.polling
.await?;
Conversation state (FSM)
DialogueStorage tracks per-user state keyed by (chat_id, user_id). State values are type-erased with Any so any type works without a shared trait.
use *;
use DialogueStorage;
use Arc;
async
async
async
Shared state
StateStorage is a thread-safe, type-keyed store for data shared across all handlers — database connection pools, configuration, counters, etc.
use StateStorage;
use Arc;
let store = new;
store.insert;
store.insert;
// Inside a handler (store captured via Arc):
let config = store..unwrap;
File uploads
InputFile accepts three variants:
use BotClient;
use InputFile;
// Re-send an existing Telegram file by file_id (most efficient)
FileId
// Download from a URL (Telegram fetches it; max 20 MB for most types)
Url
// Upload raw bytes
Bytes
All three variants share the same API:
ctx.bot
.send_document
.caption
.await?;
Downloading a file sent to your bot:
let file = ctx.bot.get_file.await?;
let bytes = ctx.bot.download_file.await?;
Payments and Stars
Telegram Stars (XTR)
use LabeledPrice;
ctx.bot
.send_invoice
.await?;
No provider_token is needed for Stars. The amount is in whole Stars.
Handling purchases
async
Star balance
let balance = ctx.bot.get_my_star_balance.await?;
println!;
Webhook setup
A complete production webhook setup:
use *;
async
To remove the webhook and switch back to polling:
bot.client.delete_webhook.drop_pending_updates.await?;
Local Bot API server
Run a local Bot API server for higher limits:
- File upload up to 2 GB (vs 50 MB)
- File download up to 2 GB (vs 20 MB)
- Up to 100,000 webhook connections
- HTTP webhooks on any port and local IP
getFilereturns absolute local path
Point rustigram at your local server:
use ClientConfig;
use Duration;
let config = new?
.api_base_url
.timeout;
let client = new?;
Error handling
BotError covers all failure modes:
use BotError;
use Error as ApiError;
match result
Polling and webhook modes handle RateLimit and transient network errors automatically. Only non-recoverable errors propagate to your application.
Useful error predicates
let err: Error = ...;
err.is_rate_limit // true for HTTP 429
err.is_blocked // true when bot was blocked by user
err.is_chat_not_found // true for "chat not found" 400s
err.retry_after // Some(secs) when rate-limited
Examples
The examples/ directory contains complete runnable bots:
| Example | Description |
|---|---|
echo_bot |
Minimal bot — echoes any message back |
inline_keyboard |
Inline buttons and callback query routing |
webhook_bot |
Production webhook server setup |
state_machine |
Multi-step conversation with FSM |
Run any example:
BOT_TOKEN=your_token
Running the tests
The test suite requires no bot token — all tests use hand-crafted in-memory data.
| Suite | Count | What is tested |
|---|---|---|
client_tests |
11 | Token validation, error types, client config |
types_tests |
23 | Serde round-trips, helper methods, Update deserialization |
dispatcher_tests |
20 | All filters, combinators, Context helpers, state storage |
Contributing
Contributions are welcome. Please open an issue before submitting a pull request for non-trivial changes.
Guidelines:
- Code style follows
rustfmtdefaults — runcargo fmtbefore committing - All public items must have doc comments
- New features should include tests
- Avoid
unwrap()in library code; propagate errors with?
Running checks locally:
License
MIT — see LICENSE.