Steps
-
Create the bot in @BotFather
Open @BotFather in Telegram and send
/newbot. It asks for a name and a username; the username must end inbot. It replies with a token, a long string with a colon in it. The token is the bot's password: anyone who has it controls the bot.You: /newbot BotFather: Alright, a new bot. How are we going to call it? You: Coffee Helper BotFather: Choose a username. It must end in "bot". You: coffee_helper_bot BotFather: Done! Use this token to access the HTTP API: 123456789:AA…
-
Prepare the folder and install the library
A virtual environment keeps the bot's packages apart from the system's. The library needs Python 3.10 or newer; Ubuntu 24.04 ships a newer one.
$ sudo apt install -y python3-venv $ mkdir ~/bot && cd ~/bot $ python3 -m venv .venv $ .venv/bin/pip install python-telegram-bot
-
Put the token in .env
Keep the token out of the code. The file is readable by you only, and git never sees it.
$ echo 'BOT_TOKEN=123456789:AA…' > .env $ chmod 600 .env $ echo '.env' >> .gitignore
-
Write the bot
This bot answers
/startand repeats any text message. It follows the echo bot from the library's own examples. Save it asbot.py.# bot.py import os from telegram import Update from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: await update.message.reply_text("Hi! Send me a message and I will repeat it.") async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: await update.message.reply_text(update.message.text) def main() -> None: app = Application.builder().token(os.environ["BOT_TOKEN"]).build() app.add_handler(CommandHandler("start", start)) app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo)) app.run_polling() if __name__ == "__main__": main() -
Run it once by hand
Load the token into the shell, start the script, then write to your bot in Telegram. It must answer. Stop it with
Ctrl+C.$ set -a; source .env; set +a $ .venv/bin/python bot.py
-
Make it a systemd service
Create the unit file, then enable it.
Restart=alwaysbrings the bot back after a crash, andenablestarts it on every boot. Replacedevwith your user name.$ sudo nano /etc/systemd/system/telegram-bot.service [Unit] Description=My Telegram bot After=network-online.target Wants=network-online.target [Service] User=dev WorkingDirectory=/home/dev/bot EnvironmentFile=/home/dev/bot/.env ExecStart=/home/dev/bot/.venv/bin/python bot.py Restart=always RestartSec=5 [Install] WantedBy=multi-user.target $ sudo systemctl daemon-reload $ sudo systemctl enable --now telegram-bot
Long polling or webhook
The bot above uses long polling: it keeps asking Telegram "any news?" and Telegram answers when a message arrives. It needs no public address, so it works on a laptop behind a home router. A webhook turns this around: Telegram sends each message to an https address you register, and your bot answers the request.
| Long polling | Webhook | |
|---|---|---|
| Public address | not needed | https, port 443, 80, 88 and 8443, TLS 1.2 and up |
| Works on a laptop | yes | no, Telegram must reach it |
| Setup in the code | one line: run_polling() | an address, a path and a secret |
| While the bot is off | Telegram keeps your messages up to 24 hours | Telegram repeats the request, then gives up after a reasonable number of attempts |
| Best for | testing and a server that is always running | a server that sleeps, scales to zero or sits behind a proxy |
To switch, install the webhook extra with pip install "python-telegram-bot[webhooks]" and replace the last line of main(). The secret makes Telegram add the header X-Telegram-Bot-Api-Secret-Token to every request, so your bot can ignore anyone else.
# instead of app.run_polling()
app.run_webhook(
listen="0.0.0.0",
port=8080,
url_path="telegram",
webhook_url="https://bot.example.com/telegram",
secret_token=os.environ["WEBHOOK_SECRET"],
)Check that it works
Ask systemd for the status, then watch the log while you write to the bot. Reboot once to see it come back by itself.
$ sudo systemctl status telegram-bot Active: active (running) $ journalctl -u telegram-bot -f $ sudo reboot
The short way: let the agent build and run it
In Uno Work you describe the bot to the agent, paste the token from @BotFather into a safe field, and the agent builds and runs the bot on your computer. You skip steps 2 to 6. Uno Work in the cloud starts with Plus. For a bot that sleeps between messages, see where to host it.
Plus: $20 a month
Common problems
The bot does not answer
journalctl -u telegram-bot -e. A wrong token ends with an "Unauthorized" error; copy the token from @BotFather again. Also check that you started the bot's chat with /start."Conflict: terminated by other getUpdates request"
The service does not start
sudo systemctl status telegram-bot. The usual cause is a wrong path in ExecStart, WorkingDirectory or EnvironmentFile, or a different user name. Fix the unit, then run sudo systemctl daemon-reload and sudo systemctl restart telegram-bot.I pasted the token in a public place
/revoke to @BotFather and pick the bot. It issues a new token and the old one stops working. Update .env and restart the service.Can I use aiogram instead?
Run your bot on an Uno computer
A computer with root and Docker that sleeps when nothing is happening. A message sent to a webhook address wakes it.
Small
A Telegram bot, a VPN, a small backend
$5/ month
- 1 vCPU · 1 GB
- 15 GB disk
- 25 GB cloud storage
- 5 boosts a month
Plus
Agents that work with your laptop closed
$20/ month
- 4 vCPU · 4 GB
- 40 GB disk
- 100 GB cloud storage
- Uno Work in the cloud
Pro
A product in production, a second MacBook
$70/ month
- 16 vCPU · 16 GB
- 120 GB disk
- 300 GB cloud storage
- Speed of a MacBook Air M4
Max
A team and many agents at once
$200/ month
- 32 vCPU · 48 GB
- 300 GB disk
- 1,024 GB cloud storage
- 5 public IPv4
Add Uno AI to any plan: 40 h of AI work a month on Small, 100 h on Plus, unlimited on Max. Today: GLM-5.3 Flash and MiMo v2.6 Pro. Or sign in with your own Claude or ChatGPT.