How it works
- create a bot with BotFather
- get the bot token
- find your chat_id
Example (HTTP API)
import os
import requests
def telegram_send(text: str):
token = os.environ["TELEGRAM_BOT_TOKEN"]
chat_id = os.environ["TELEGRAM_CHAT_ID"]
url = f"https://api.telegram.org/bot{token}/sendMessage"
r = requests.post(url, data={"chat_id": chat_id, "text": text}, timeout=10)
r.raise_for_status()
# telegram_send("Build finished")Getting a bot and a chat id
Telegram bots are the easiest way to get a push notification on your phone from a script. There is no server to run, and the API is a plain HTTPS call.
- In Telegram, open a chat with @BotFather and send
/newbot. Choose a name. BotFather replies with a token like123456:ABC-DEF.... It is a password, so treat it as one. - Open a chat with your new bot and send it any message, such as
/start. A bot cannot message you until you have written to it first. - Find your chat id. Open
https://api.telegram.org/bot<TOKEN>/getUpdatesin a browser after sending the message. The JSON contains"chat": {"id": 123456789, ...}. That number is your chat id. For a group, add the bot to the group, send a message there, and read the id (group ids are negative).
Store both as environment variables: TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID.
Sending with formatting and error handling
The sendMessage method takes the chat_id and the text. The basic example works, but here is a version that formats the message, escapes text, and reports API errors clearly. Telegram returns JSON with "ok": false and a description when something is wrong.
import html
import os
import requests
API = "https://api.telegram.org/bot{token}/{method}"
def telegram_send(text: str, silent: bool = False) -> bool:
token = os.environ["TELEGRAM_BOT_TOKEN"]
payload = {
"chat_id": os.environ["TELEGRAM_CHAT_ID"],
"text": text[:4096], # Telegram's maximum message length
"parse_mode": "HTML",
"disable_notification": silent,
}
r = requests.post(API.format(token=token, method="sendMessage"), json=payload, timeout=10)
data = r.json()
if not data.get("ok"):
print("Telegram error:", data.get("description"))
return False
return True
def notify(job: str, ok: bool, detail: str) -> bool:
mark = "OK" if ok else "FAILED"
text = f"<b>{html.escape(job)}: {mark}</b>\n<code>{html.escape(detail)}</code>"
return telegram_send(text, silent=ok)
print(html.escape("disk <95%> & rising"))
# notify("Nightly backup", False, "disk <95%> & rising")With parse_mode set to HTML, characters like <, > and & in your text must be escaped, or Telegram rejects the message with a parse error. html.escape does that. disable_notification sends the message without a sound, which suits success messages, while failures still ring.
Errors you will see
- 401 Unauthorized: the token is wrong or was revoked with BotFather.
- 400 chat not found: you never messaged the bot, or the chat id is wrong.
- 400 can't parse entities: unescaped HTML characters in the text.
- 429 Too Many Requests: you are sending too fast. The response tells you how many seconds to wait in
parameters.retry_after.
Safety
Anyone who has the token can send messages as your bot and read its updates. Never commit it. Revoke it with /revoke in BotFather if it leaks. Keep in mind that a bot only sends to chats that have already started talking to it, and never use a bot to message people who did not ask for it.
Check your understanding
Your bot returns 'chat not found'. What is the most likely cause?
A bot can only message chats that have started a conversation with it. Send /start to the bot and check the id from getUpdates.
Show answer
A — You never sent the bot a message, or the chat id is wrong — A bot can only message chats that have started a conversation with it. Send /start to the bot and check the id from getUpdates.
With parse_mode HTML, why call html.escape on the message text?
In HTML mode Telegram parses tags. Escaping turns special characters into entities so they display literally.
Show answer
A — Characters like < and & would otherwise be read as markup and could make Telegram reject the message — In HTML mode Telegram parses tags. Escaping turns special characters into entities so they display literally.
Where should the bot token be stored?
The token gives full control of the bot. Keep it out of the code, and revoke it via BotFather if exposed.
Show answer
B — In an environment variable, kept out of version control — The token gives full control of the bot. Keep it out of the code, and revoke it via BotFather if exposed.