telega
Types
pub opaque type TelegaBuilder(session, error, dependencies)
Values
pub fn get_api_config(
telega: Telega(session, error, dependencies),
) -> client.TelegramClient
Helper to get the config for API requests.
pub fn get_dependencies(
ctx: bot.Context(session, error, dependencies),
) -> dependencies
Get the injected dependencies (services) for the current context.
dependencies is set once at bot init via with_dependencies and is never persisted.
See the session vs dependencies distinction in with_dependencies.
pub fn get_me(
telega: Telega(session, error, dependencies),
) -> types.User
Get the bot’s information.
pub fn get_session(
ctx: bot.Context(session, error, dependencies),
) -> session
Get session for the current context.
pub fn get_supervisor_pid(
telega: Telega(session, error, dependencies),
) -> process.Pid
Get the supervisor PID for the running bot instance.
pub fn handle_update(
telega: Telega(session, error, dependencies),
raw_update: types.Update,
) -> Bool
Handle an update.
This function is useful when you want to handle updates in your own way.
pub fn init(
builder: TelegaBuilder(session, error, dependencies),
) -> Result(
Telega(session, error, dependencies),
error.TelegaError,
)
Initialize the bot for webhook mode with a supervision tree.
pub fn init_for_polling(
builder: TelegaBuilder(session, error, dependencies),
) -> Result(
Telega(session, error, dependencies),
error.TelegaError,
)
Initialize the bot for long polling with a supervision tree. Includes a supervised polling worker that auto-starts.
pub fn init_for_polling_nil_session(
builder: TelegaBuilder(Nil, error, dependencies),
) -> Result(Telega(Nil, error, dependencies), error.TelegaError)
Initialize the bot for long polling with nil session.
pub fn is_draining(
telega: Telega(session, error, dependencies),
) -> Bool
Whether the bot is currently draining and no longer accepting updates.
Webhook adapters should answer 503 when this is True so Telegram retries
the update after the deploy instead of it being dropped.
pub fn is_secret_token_valid(
telega: Telega(session, error, dependencies),
token: String,
) -> Bool
Check if a secret token is valid.
Useful if you plan to implement own adapter.
pub fn is_webhook_path(
telega: Telega(session, error, dependencies),
path: String,
) -> Bool
Check if a path is the webhook path for the bot.
Useful if you plan to implement own adapter.
pub fn log_context(
ctx: bot.Context(session, error, dependencies),
prefix: String,
fun: fn(bot.Context(session, error, dependencies)) -> Result(
bot.Context(session, error, dependencies),
error,
),
) -> Result(bot.Context(session, error, dependencies), error)
Add logging context to the current context.
pub fn log_error(
ctx: bot.Context(session, error, dependencies),
message: String,
) -> Nil
pub fn log_info(
ctx: bot.Context(session, error, dependencies),
message: String,
) -> Nil
Context helpers for logging
pub fn new(
api_client api_client: client.TelegramClient,
url server_url: String,
webhook_path webhook_path: String,
secret_token secret_token: option.Option(String),
) -> TelegaBuilder(session, error, Nil)
Create a new Telega instance with no injected dependencies (dependencies is Nil).
Requires an api_client created by an adapter package like telega_httpc or telega_hackney.
To inject services, prefer new_with_dependencies — it fixes the dependencies type up front
and avoids the field-reset footgun of with_dependencies (see with_dependencies).
pub fn new_for_polling(
api_client api_client: client.TelegramClient,
) -> TelegaBuilder(session, error, Nil)
Create a new Telega instance optimized for long polling, with no injected
dependencies (dependencies is Nil).
Requires an api_client created by an adapter package like telega_httpc or telega_hackney.
This is a convenience function for polling bots that don’t need webhook configuration.
To inject services, prefer new_for_polling_with_dependencies.
pub fn new_for_polling_with_dependencies(
api_client api_client: client.TelegramClient,
dependencies dependencies: dependencies,
) -> TelegaBuilder(session, error, dependencies)
Like new_for_polling, but injects dependencies (services) at construction.
Preferred over new_for_polling + with_dependencies: the dependencies type is fixed up
front, so the builder steps that follow are never reset (see with_dependencies).
telega.new_for_polling_with_dependencies(api_client:, dependencies: Dependencies(db:, catalog:))
|> telega.with_router(router)
|> telega.init_for_polling()
pub fn new_with_dependencies(
api_client api_client: client.TelegramClient,
url server_url: String,
webhook_path webhook_path: String,
secret_token secret_token: option.Option(String),
dependencies dependencies: dependencies,
) -> TelegaBuilder(session, error, dependencies)
Like new, but injects dependencies (services) at construction.
This is the safest way to use dependency injection: the builder’s dependencies
type is fixed from the start, so with_router/with_catch_handler/on_start
can be called in any order without being reset. See with_dependencies for the
session vs dependencies distinction.
telega.new_with_dependencies(api_client:, url:, webhook_path:, secret_token:, dependencies: Dependencies(db:, catalog:))
|> telega.with_router(router)
|> telega.init()
pub fn set_allowed_updates(
builder: TelegaBuilder(session, error, dependencies),
updates: List(String),
) -> TelegaBuilder(session, error, dependencies)
Set allowed updates for webhook.
pub fn set_api_client(
builder: TelegaBuilder(session, error, dependencies),
client: client.TelegramClient,
) -> TelegaBuilder(session, error, dependencies)
Set a custom API client.
pub fn set_certificate(
builder: TelegaBuilder(session, error, dependencies),
cert: types.File,
) -> TelegaBuilder(session, error, dependencies)
Set certificate for webhook.
pub fn set_drop_pending_updates(
builder: TelegaBuilder(session, error, dependencies),
drop: Bool,
) -> TelegaBuilder(session, error, dependencies)
Set whether to drop pending updates.
pub fn set_ip_address(
builder: TelegaBuilder(session, error, dependencies),
ip: String,
) -> TelegaBuilder(session, error, dependencies)
Set IP address for webhook.
pub fn set_max_connections(
builder: TelegaBuilder(session, error, dependencies),
max: Int,
) -> TelegaBuilder(session, error, dependencies)
Set max connections for webhook.
pub fn shutdown(
telega: Telega(session, error, dependencies),
) -> Nil
Graceful shutdown with in-flight draining.
- Emits
[telega, shutdown, start]. - Stops intake — for polling, tells the worker to stop fetching updates
(Telegram re-delivers unconfirmed updates on the next start); for webhook,
the bot starts rejecting updates and
is_drainingreportsTrueso adapters can answer503. - Waits up to
drain_timeoutfor in-flight updates to finish. - Runs the
on_shutdownhook. - Emits
[telega, shutdown, stop]with the number of drained updates. - Stops the supervisor, cascading to all children (polling → bot → chat_factory).
pub fn start_polling_default(
telega: Telega(session, error, dependencies),
) -> Result(polling.Poller, error.TelegaError)
Start polling with default configuration for a Telega instance. This is useful when you want to manually start polling outside the supervision tree.
pub fn use_pre_handler(
builder: TelegaBuilder(session, error, dependencies),
pre_handler: fn(bot.PreContext(dependencies)) -> bot.PreRouterResult,
) -> TelegaBuilder(session, error, dependencies)
Register a global pre-router middleware (bot.PreHandler).
Pre-router middleware runs once per update inside the bot actor, before
routing and before any chat instance is spawned or session loaded. Use it
for cross-cutting concerns that apply to every update: anti-spam, analytics,
and update deduplication. Returning bot.Stop drops the update before
routing; bot.Continue lets it through to the next pre-handler and the
router. Handlers run in the order they are registered, and the first Stop
short-circuits the rest. Because they all run sequentially in the single bot
actor, read-then-write logic (like dedup) is race-free across updates.
// Drop updates from a banned chat before they reach any handler.
telega.new_for_polling(api_client:)
|> telega.use_pre_handler(fn(pre) {
case pre.update.chat_id == banned_chat {
True -> bot.Stop
False -> bot.Continue
}
})
|> telega.with_router(router)
// Webhook idempotency: drop updates Telegram re-delivers on retry.
|> telega.use_pre_handler(idempotency.deduplicate(storage:, ttl_ms: 3600_000))
pub fn wait_any(
ctx ctx: bot.Context(session, error, dependencies),
or handle_else: option.Option(
bot.Handler(session, error, dependencies),
),
timeout timeout: option.Option(Int),
continue handler: fn(
bot.Context(session, error, dependencies),
update.Update,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Result(bot.Context(session, error, dependencies), error)
Pauses the current chat actor’s handler and waits for any update. Other chats and users continue to be handled concurrently.
See conversation
pub fn wait_audio(
ctx ctx: bot.Context(session, error, dependencies),
or handle_else: option.Option(
bot.Handler(session, error, dependencies),
),
timeout timeout: option.Option(Int),
continue continue: fn(
bot.Context(session, error, dependencies),
types.Audio,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Result(bot.Context(session, error, dependencies), error)
Pauses the current chat actor’s handler and waits for an audio message. Other chats and users continue to be handled concurrently.
See conversation
pub fn wait_callback_query(
ctx ctx: bot.Context(session, error, dependencies),
filter filter: option.Option(bot.CallbackQueryFilter),
or handle_else: option.Option(
bot.Handler(session, error, dependencies),
),
timeout timeout: option.Option(Int),
continue continue: fn(
bot.Context(session, error, dependencies),
String,
String,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Result(bot.Context(session, error, dependencies), error)
Pauses the current chat actor’s handler and waits for a callback query. Other chats and users continue to be handled concurrently.
See conversation
pub fn wait_choice(
ctx ctx: bot.Context(session, error, dependencies),
options options: List(#(String, a)),
or handle_else: option.Option(
bot.Handler(session, error, dependencies),
),
timeout timeout: option.Option(Int),
continue continue: fn(
bot.Context(session, error, dependencies),
a,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Result(bot.Context(session, error, dependencies), error)
Wait for user choice from inline keyboard.
This function creates an inline keyboard with provided options and waits for user to select one.
Examples
use ctx, color <- wait_choice(
ctx,
[
#("🔴 Red", Red),
#("🔵 Blue", Blue),
#("🟢 Green", Green),
],
or: None,
timeout: None,
)
See conversation
pub fn wait_command(
ctx ctx: bot.Context(session, error, dependencies),
command command: String,
or handle_else: option.Option(
bot.Handler(session, error, dependencies),
),
timeout timeout: option.Option(Int),
continue continue: fn(
bot.Context(session, error, dependencies),
update.Command,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Result(bot.Context(session, error, dependencies), error)
Pauses the current chat actor’s handler and waits for a specific command. Other chats and users continue to be handled concurrently.
See conversation
pub fn wait_commands(
ctx ctx: bot.Context(session, error, dependencies),
commands commands: List(String),
or handle_else: option.Option(
bot.Handler(session, error, dependencies),
),
timeout timeout: option.Option(Int),
continue continue: fn(
bot.Context(session, error, dependencies),
update.Command,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Result(bot.Context(session, error, dependencies), error)
Pauses the current chat actor’s handler and waits for one of the specified commands. Other chats and users continue to be handled concurrently.
See conversation
pub fn wait_email(
ctx ctx: bot.Context(session, error, dependencies),
or handle_else: option.Option(
bot.Handler(session, error, dependencies),
),
timeout timeout: option.Option(Int),
continue continue: fn(
bot.Context(session, error, dependencies),
String,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Result(bot.Context(session, error, dependencies), error)
Wait for email with validation.
This function waits for user to send text that matches email pattern.
If validation fails and or handler is provided, it will be called.
Otherwise, the function will keep waiting for valid input.
Examples
use ctx, email <- wait_email(
ctx,
or: Some(bot.HandleText(fn(ctx, invalid) {
reply.with_text(ctx, "Invalid email format. Try again.")
})),
timeout: None,
)
See conversation
pub fn wait_for(
ctx ctx: bot.Context(session, error, dependencies),
filter filter: fn(update.Update) -> Bool,
or handle_else: option.Option(
bot.Handler(session, error, dependencies),
),
timeout timeout: option.Option(Int),
continue continue: fn(
bot.Context(session, error, dependencies),
update.Update,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Result(bot.Context(session, error, dependencies), error)
Wait for update matching custom filter.
This function waits for any update that passes the provided filter function.
Examples
use ctx, photo_update <- wait_for(
ctx,
filter: fn(upd) {
case upd {
update.PhotoUpdate(..) -> True
_ -> False
}
},
or: Some(bot.HandleAll(fn(ctx, wrong_update) {
reply.with_text(ctx, "Please send a photo")
})),
timeout: Some(60_000),
)
See conversation
pub fn wait_hears(
ctx ctx: bot.Context(session, error, dependencies),
hears hears: bot.Hears,
or handle_else: option.Option(
bot.Handler(session, error, dependencies),
),
timeout timeout: option.Option(Int),
continue continue: fn(
bot.Context(session, error, dependencies),
String,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Result(bot.Context(session, error, dependencies), error)
Pauses the current chat actor’s handler and waits for a message that matches the given Hears.
Other chats and users continue to be handled concurrently.
See conversation
pub fn wait_message(
ctx ctx: bot.Context(session, error, dependencies),
or handle_else: option.Option(
bot.Handler(session, error, dependencies),
),
timeout timeout: option.Option(Int),
continue continue: fn(
bot.Context(session, error, dependencies),
types.Message,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Result(bot.Context(session, error, dependencies), error)
Pauses the current chat actor’s handler and waits for any message. Other chats and users continue to be handled concurrently.
See conversation
pub fn wait_number(
ctx ctx: bot.Context(session, error, dependencies),
min min: option.Option(Int),
max max: option.Option(Int),
or handle_else: option.Option(
bot.Handler(session, error, dependencies),
),
timeout timeout: option.Option(Int),
continue continue: fn(
bot.Context(session, error, dependencies),
Int,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Result(bot.Context(session, error, dependencies), error)
Wait for a number with validation.
This function waits for user to send text that can be parsed as an integer, with optional min/max validation.
If validation fails and or handler is provided, it will be called.
Otherwise, the function will keep waiting for valid input.
Examples
use ctx, age <- wait_number(
ctx,
min: Some(0),
max: Some(120),
or: Some(bot.HandleText(fn(ctx, invalid) {
reply.with_text(ctx, "Please enter age between 0 and 120")
})),
timeout: None,
)
See conversation
pub fn wait_photos(
ctx ctx: bot.Context(session, error, dependencies),
or handle_else: option.Option(
bot.Handler(session, error, dependencies),
),
timeout timeout: option.Option(Int),
continue continue: fn(
bot.Context(session, error, dependencies),
List(types.PhotoSize),
) -> Result(bot.Context(session, error, dependencies), error),
) -> Result(bot.Context(session, error, dependencies), error)
Pauses the current chat actor’s handler and waits for photos. Other chats and users continue to be handled concurrently.
See conversation
pub fn wait_text(
ctx ctx: bot.Context(session, error, dependencies),
or handle_else: option.Option(
bot.Handler(session, error, dependencies),
),
timeout timeout: option.Option(Int),
continue continue: fn(
bot.Context(session, error, dependencies),
String,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Result(bot.Context(session, error, dependencies), error)
Pauses the current chat actor’s handler and waits for a text message. Other chats and users continue to be handled concurrently.
See conversation
pub fn wait_video(
ctx ctx: bot.Context(session, error, dependencies),
or handle_else: option.Option(
bot.Handler(session, error, dependencies),
),
timeout timeout: option.Option(Int),
continue continue: fn(
bot.Context(session, error, dependencies),
types.Video,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Result(bot.Context(session, error, dependencies), error)
Pauses the current chat actor’s handler and waits for a video message. Other chats and users continue to be handled concurrently.
See conversation
pub fn wait_voice(
ctx ctx: bot.Context(session, error, dependencies),
or handle_else: option.Option(
bot.Handler(session, error, dependencies),
),
timeout timeout: option.Option(Int),
continue continue: fn(
bot.Context(session, error, dependencies),
types.Voice,
) -> Result(bot.Context(session, error, dependencies), error),
) -> Result(bot.Context(session, error, dependencies), error)
Pauses the current chat actor’s handler and waits for a voice message. Other chats and users continue to be handled concurrently.
See conversation
pub fn with_auto_allowed_updates(
builder: TelegaBuilder(session, error, dependencies),
) -> TelegaBuilder(session, error, dependencies)
Derive allowed_updates from the router’s registered routes.
Telegram then sends only the update types the bot actually handles, cutting
out traffic for routes you never registered. A manual set_allowed_updates
always wins (the escape hatch). If the router has a fallback, custom, or
filtered route — which can match anything — derivation can’t narrow safely
and falls back to Telegram’s default update set.
pub fn with_auto_commands(
builder: TelegaBuilder(session, error, dependencies),
) -> TelegaBuilder(session, error, dependencies)
Publish the router’s commands to Telegram on start.
Every command registered with router.on_command_with_description is sent
via setMyCommands once the bot is up, so the Telegram client shows them in
the command menu without a manual call. Commands added with plain
router.on_command (no description) are not published.
For localized descriptions use with_command_translations instead — it
turns this on as well.
telega.new_for_polling(api_client:)
|> telega.with_router(router)
|> telega.with_auto_commands()
|> telega.init_for_polling()
pub fn with_catch_handler(
builder: TelegaBuilder(session, error, dependencies),
catch_handler: fn(
bot.Context(session, error, dependencies),
error,
) -> Result(Nil, error),
) -> TelegaBuilder(session, error, dependencies)
Set catch handler for system errors (like session persistence failures) and conversation errors. This is different from router’s catch handler which handles route errors.
pub fn with_chat_config(
builder: TelegaBuilder(session, error, dependencies),
restart_tolerance_intensity intensity: Int,
restart_tolerance_period period: Int,
init_timeout timeout: Int,
) -> TelegaBuilder(session, error, dependencies)
Configure the chat instance factory supervisor.
restart_tolerance_intensity— max restarts within the period (default: 5)restart_tolerance_period— period in seconds (default: 10)init_timeout— chat instance init timeout in ms (default: 10 000)
pub fn with_command_translations(
builder: TelegaBuilder(session, error, dependencies),
locales locales: List(String),
translate translate: fn(String, String) -> option.Option(String),
) -> TelegaBuilder(session, error, dependencies)
Publish localized command descriptions on start.
Implies with_auto_commands: the default-language commands are published
first, then for every locale in locales a setMyCommands(language_code:)
call is made. translate(command, locale) supplies the per-language text;
returning None keeps the router’s default description for that command.
telega_i18n provides a convenience wrapper that builds translate from a
translation catalog, so you usually call this through it.
telega.new_for_polling(api_client:)
|> telega.with_router(router)
|> telega.with_command_translations(
locales: ["en", "ru"],
translate: fn(command, locale) { lookup_description(command, locale) },
)
|> telega.init_for_polling()
pub fn with_dependencies(
builder builder: TelegaBuilder(session, error, old_dependencies),
dependencies dependencies: dependencies,
) -> TelegaBuilder(session, error, dependencies)
Inject typed, non-persisted dependencies (services) available in every
handler via ctx.dependencies (or get_dependencies).
Use this for things that are not user state and must not be persisted —
a database pool, an http client, an i18n catalog, configuration, etc. The
rule of thumb: session is the user’s state (persisted), dependencies is the
bot’s services (set once at init, never persisted).
⚠️ Footgun — silently resets fields.
with_dependencieschanges the builder’sdependenciestype, so it cannot keep the previously-setrouter,catch_handler, oron_start(they are typed against the olddependencies). It resets them to their defaults. If you call it afterwith_router, your router is silently dropped and the bot runs with no routes — and there is no compile error, only a dead bot at runtime. Either callwith_dependenciesfirst, or — better — skip it entirely and inject at construction withnew_for_polling_with_dependencies/new_with_dependencies, which fix thedependenciestype up front and have no reset behaviour.
// Preferred — no reset, any order:
telega.new_for_polling_with_dependencies(api_client:, dependencies: Dependencies(db:, catalog:))
|> telega.with_router(router)
|> telega.init_for_polling()
// With `with_dependencies` — MUST come before with_router/with_catch_handler/on_start:
telega.new_for_polling(api_client:)
|> telega.with_dependencies(Dependencies(db:, catalog:))
|> telega.with_router(router)
|> telega.init_for_polling()
pub fn with_drain_timeout(
builder: TelegaBuilder(session, error, dependencies),
timeout timeout: Int,
) -> TelegaBuilder(session, error, dependencies)
Set the maximum time (in milliseconds) shutdown waits for in-flight
updates to finish before forcibly stopping the supervision tree.
Defaults to 5000ms.
pub fn with_nil_session(
builder: TelegaBuilder(Nil, error, dependencies),
) -> TelegaBuilder(Nil, error, dependencies)
Set nil session for the bot.
pub fn with_on_shutdown(
builder: TelegaBuilder(session, error, dependencies),
on_shutdown on_shutdown: fn() -> Nil,
) -> TelegaBuilder(session, error, dependencies)
Set a hook to run during shutdown, after in-flight updates have drained
and before the supervision tree is stopped. Use it to release resources
(close pools, flush buffers, deregister from a service discovery, …).
pub fn with_on_start(
builder: TelegaBuilder(session, error, dependencies),
on_start on_start: fn(Telega(session, error, dependencies)) -> Result(
Nil,
error.TelegaError,
),
) -> TelegaBuilder(session, error, dependencies)
Set a hook to run once the bot has fully started.
Runs after the supervision tree is up and the Telega instance is built, so
you can use it for warming caches, registering commands via the API, etc.
Returning Error aborts startup and tears the supervision tree back down.
telega.new_for_polling(api_client:)
|> telega.with_router(router)
|> telega.with_on_start(fn(bot) {
// register commands, warm caches...
Ok(Nil)
})
|> telega.init_for_polling()
pub fn with_polling_config(
builder: TelegaBuilder(session, error, dependencies),
timeout timeout: Int,
limit limit: Int,
poll_interval poll_interval: Int,
) -> TelegaBuilder(session, error, dependencies)
Set polling configuration for the supervised polling worker.
pub fn with_polling_on_stop(
builder: TelegaBuilder(session, error, dependencies),
on_stop on_stop: fn(error.TelegaError) -> Nil,
) -> TelegaBuilder(session, error, dependencies)
Set a callback for when polling stops due to errors.
pub fn with_router(
builder: TelegaBuilder(session, error, dependencies),
router: router.Router(session, error, dependencies),
) -> TelegaBuilder(session, error, dependencies)
Set the router for handling updates. This is the primary way to handle updates - use router.new() to create a router and configure it with command handlers, text handlers, middleware, etc.
pub fn with_session_settings(
builder: TelegaBuilder(session, error, dependencies),
session_settings: bot.SessionSettings(session, error),
) -> TelegaBuilder(session, error, dependencies)
Set session settings for the bot.
pub fn with_signal_handlers(
builder: TelegaBuilder(session, error, dependencies),
) -> TelegaBuilder(session, error, dependencies)
Install an OS signal handler (SIGTERM) that runs a graceful shutdown and
then halts the VM.
This makes the bot survive rolling deploys on platforms like fly.io or
Kubernetes: on SIGTERM the bot stops accepting new updates, drains in-flight
work (bounded by with_drain_timeout), runs the on_shutdown hook, and
stops cleanly. The handler replaces the runtime’s default signal behavior.
Only SIGTERM is handled — BEAM reserves SIGINT for its interactive break handler, so it cannot be intercepted this way.