Nadia is an Elixir client for the Telegram Bot API and Telegraph API. It combines complete Bot API method coverage with small, explicit helpers for building bots in an OTP application.
- Call every Telegram Bot API method through
Nadia. - Receive updates with supervised polling or framework-neutral webhooks.
- Route commands, text, and callback queries without a macro DSL.
- Use explicit clients for multi-bot applications and fake HTTP adapters for offline tests.
- Upload paths, bounded iodata, and known-size streams with explicit
Nadia.InputFilevalues, including nested media attachments. - Build current outgoing media, poll options, rich messages, and story areas with fixed-shape typed helpers while retaining raw compatibility inputs.
- Stream Telegram downloads to files under a mandatory byte limit, without returning token-bearing URLs.
- Add optional conversation state without requiring a persistence dependency.
Nadia requires Elixir 1.20 or later and Erlang/OTP 27 or later.
Add :nadia to mix.exs:
def deps do
[
{:nadia, "~> 1.6"}
]
endThen run:
mix deps.getCreate a bot with @BotFather. Read its token at runtime instead of committing it to application configuration:
# config/runtime.exs
import Config
config :nadia,
token: {:system, "TELEGRAM_BOT_TOKEN"}Direct Bot API calls return {:ok, result} or
{:error, %Nadia.Model.Error{}}:
case Nadia.get_me() do
{:ok, bot} -> IO.puts("Connected as @#{bot.username}")
{:error, error} -> IO.warn("Telegram error: #{inspect(error.reason)}")
end
Nadia.send_message(chat_id, "Hello from Nadia")For an OTP bot, generate a handler and offline test:
mix nadia.gen.bot MyApp.Bot --pollingThen supervise long polling:
children = [
{Nadia.Polling,
handler: MyApp.Bot,
allowed_updates: ["message"],
timeout: 30}
]Run the app with the token in its environment:
TELEGRAM_BOT_TOKEN=123:token mix run --no-haltThe Build Your First Bot guide walks through the complete setup.
The Examples And Learning Paths page connects Nadia's API reference to complete bot-building tasks.
| Build | Guide |
|---|---|
| A generated polling bot | Build Your First Bot |
| Commands and inline buttons | Commands And Inline Keyboards |
| A multi-step conversation | Conversation State |
| Durable conversation state | Persistent Session Backends |
| Media uploads and downloads | Media And Files |
| Rich messages and clickable story areas | Rich Messages And Stories |
| Bounded Telegram retries | Errors And Rate Limits |
| An HTTP endpoint | Receive Webhook Updates |
| Several bot identities | Run Multiple Bots |
| Credential-free tests | Test Bot Handlers |
| A production deployment | Production Checklist |
| Telegraph pages | Use The Telegraph API |
Tested, copyable handler modules live in the
examples directory.
The top-level token config is the default client used by calls such as
Nadia.get_me/0 and Nadia.send_message/3. Applications with more than one bot
can configure named clients:
config :nadia,
bots: [
support: [
token: {:system, "SUPPORT_BOT_TOKEN"},
recv_timeout: 10
],
alerts: [
token: {:system, "ALERTS_BOT_TOKEN"}
]
]support = Nadia.Client.from_config(:support)
Nadia.send_message(support, support_chat_id, "How can we help?")See Run Multiple Bots before supervising several pollers; each worker needs a distinct child ID.
Nadia uses Req as its HTTP transport. Optional HTTP or HTTPS proxy settings are passed to Req/Mint:
config :nadia,
proxy: "http://proxy.example.com:8080",
proxy_auth: {"user", "password"},
recv_timeout: 10Custom Bot API, file, or Telegraph endpoints can be configured with
:base_url, :file_base_url, and :graph_base_url. Most applications should
use the defaults.
A trusted local Bot API server started in local mode can return absolute file paths instead of remote download paths. Opt into local filesystem copying explicitly:
config :nadia,
base_url: "http://bot-api.internal/bot",
file_mode: :localThe path must be accessible in Nadia's filesystem namespace. Leave
file_mode: :remote at its default for Telegram's hosted API and for local
servers that still use HTTP file downloads.
Nadia's normal test suite is offline and credential-free:
mix testThe Test Bot Handlers guide shows how application
tests can inject a fake Nadia.HTTPClient and assert outgoing requests.
Optional maintainer smoke tests against Telegram are tagged
:telegram_live. They require the two-bot environment documented in
.env.live.local.example and are run with:
mix test --only telegram_liveSee the contributing guide for local checks, changelog expectations, Bot API maintenance notes, and the GitHub Actions release process.
Since Nadia 1.0.0, the wrapper covers all 180 official methods in Telegram Bot API 10.1, published on June 11, 2026. Current releases preserve that complete method coverage. Modeled response fields are parsed into Nadia structs; unknown future fields are ignored until Nadia explicitly models them.
Use the Nadia reference for Elixir
signatures and the official Telegram Bot API
documentation for Telegram's field
semantics.
Typed outgoing-content helpers include Nadia.InputMedia,
Nadia.InputPaidMedia, Nadia.InputPollMedia, Nadia.InputPollOption,
Nadia.InputProfilePhoto, Nadia.InputRichMessage,
Nadia.InputRichMessageContent, Nadia.InputTextMessageContent,
Nadia.InputInvoiceMessageContent, Nadia.InputLocationMessageContent,
Nadia.InputVenueMessageContent, Nadia.InputContactMessageContent,
Nadia.InputStoryContent, Nadia.InputSticker, Nadia.LabeledPrice,
Nadia.ReactionType, and Nadia.StoryArea. Their constructors reject locally
detectable mistakes; raw maps, keyword lists, structs, mixed lists, and
pre-encoded JSON remain available as compatibility escape hatches.
Copyright (c) 2015 Yu Zhang
Nadia is released under the MIT License.