Você quer que um script avise quando algo termina, dispare uma ação ou responda a um comando — e quer isso dentro do aplicativo que você já abre trinta vezes por dia, sem instalar mais nada no celular. O Telegram já é essa interface. Falta só ligar um programa nela.

A saída mais rápida é um construtor de bot sem código: você arrasta blocos, escolhe um gatilho e publica em minutos. Funciona até o bot precisar decidir algo fora do que o template previu — aí o editor visual não tem onde você editar a lógica por baixo, e o próximo passo costuma ser migrar tudo de novo, agora escrevendo código de verdade.

Este tutorial toma o caminho mais longo de propósito: escrever o próprio bot em Python, do zero até rodando em Docker, com python-telegram-bot. Dá mais trabalho no primeiro dia. Em troca, você decide exatamente o que o bot responde, para quem responde, e o que acontece quando o servidor reinicia sozinho de madrugada. O meu bot pessoal — um projeto interno que chamei de Simon — roda nessa mesma base há tempo: python-telegram-bot==22.6, empacotado em Docker, com restart: unless-stopped garantindo que ele volte sozinho depois de qualquer reinício do host.

O que você precisa

  • Conta no Telegram
  • Python 3.10 ou mais recente instalado
  • Docker e Docker Compose instalados (só entram no passo de deploy)
  • Um editor de texto ou IDE
  • Custo: a biblioteca é gratuita; hospedar o container num servidor próprio ou numa VPS tem custo de infraestrutura, não da biblioteca em si

Passo 1: Criar o bot no BotFather e guardar o token

Abra uma conversa com @BotFather dentro do próprio Telegram e mande /newbot. Ele pergunta um nome de exibição — o que aparece pro usuário — e depois um username, que precisa terminar em "bot". Ao final ele devolve um token: uma sequência longa que funciona como senha do bot. Trate como senha mesmo — nunca cole no código-fonte, nunca comite no Git.

Anote também o seu próprio chat ID agora, porque o Passo 3 vai precisar dele. Mande qualquer mensagem para o bot recém-criado e consulte o endpoint getUpdates, ou peça o número direto a um bot utilitário como @userinfobot. Esse número vira a variável TELEGRAM_CHAT_ID. Ao final do passo você tem duas strings guardadas: o token e o chat ID.

Passo 2: Estruturar o projeto e instalar a biblioteca

Crie uma pasta para o projeto e instale as duas dependências que este tutorial usa:

pip install python-telegram-bot==22.6 python-dotenv

Essa é a mesma versão que roda em produção no bot Simon. Uma decisão de arquitetura vale a pena adotar desde o primeiro commit, mesmo num bot pequeno: lógica de negócio em módulos próprios — um commands.py, por exemplo — e o wiring do framework (handlers, Application) isolado num bot.py. Assim dá para testar a lógica sem simular um objeto do Telegram inteiro. Este tutorial mantém tudo num único bot.py por ser um exemplo mínimo; separar os dois é o primeiro item dos próximos passos.

Crie um arquivo .env na raiz do projeto com TELEGRAM_BOT_TOKEN e TELEGRAM_CHAT_ID, e adicione .env ao .gitignore antes do primeiro commit — nessa ordem, não depois.

Passo 3: Escrever o guard de autorização e o primeiro comando

Todo handler deste bot passa pela mesma pergunta antes de fazer qualquer coisa: o chat que mandou a mensagem é o seu? É o guard que o bot Simon usa em produção, copiado aqui como está:

def _is_authorized(update: Update) -> bool:
    return str(update.effective_chat.id) == TELEGRAM_CHAT_ID

Um handler sem essa checagem responde para qualquer pessoa que descobrir o username do bot — e username de bot não é segredo, aparece em qualquer print de conversa. Por isso o guard entra na primeira linha de cada handler, retornando em silêncio quando falha:

async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    if not _is_authorized(update):
        return
    await update.message.reply_text("Bot online.")

O mesmo padrão apareceu nos três bots que embasam este tutorial — Simon, HealthJournal e Equity_Tracker. Nenhum dos três confia num handler sem essa linha.

Passo 4: Rodar localmente com long polling

O padrão de inicialização se repete nos três projetos: um builder recebe o token, registra os handlers, e chama run_polling(). Junte as peças dos dois passos anteriores num arquivo bot.py:

import os
from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes

TELEGRAM_BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
TELEGRAM_CHAT_ID = os.environ["TELEGRAM_CHAT_ID"]

def _is_authorized(update: Update) -> bool:
    return str(update.effective_chat.id) == TELEGRAM_CHAT_ID

async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    if not _is_authorized(update):
        return
    await update.message.reply_text("Bot online.")

def main() -> None:
    app = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
    app.add_handler(CommandHandler("start", cmd_start))
    app.run_polling()

if __name__ == "__main__":
    main()

run_polling() implementa o modelo de long polling: o bot pergunta ao Telegram por novidades, em vez de esperar o Telegram bater na porta dele. Não exige endereço HTTPS público nem certificado — e é por isso que os três projetos que embasam este tutorial usam esse modelo. Todos de baixo volume, um usuário só, sem motivo para expor um servidor web.

Rode python bot.py e, no Telegram, mande /start para o bot. A resposta "Bot online." confirma que o guard e o handler estão de pé. Encerre com Ctrl+C — o próximo passo troca esse terminal aberto por um container que fica rodando sozinho.

Passo 5: Empacotar em Docker com reinício automático

Um script que só roda enquanto o terminal está aberto some na primeira queda de energia ou reinício do servidor. O Dockerfile empacota o bot:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "bot.py"]

E o docker-compose.yml decide o que acontece quando o container cai:

services:
  bot:
    build: .
    restart: unless-stopped
    env_file: .env

A política restart: unless-stopped é a mesma que roda em produção no bot Simon — o container volta sozinho depois de uma queda ou de um reinício do host, a menos que alguém pare ele na mão. Suba com:

docker compose up -d --build
LEIA TAMBÉM · ExplicaçãoO que separa um protótipo de IA de um sistema em produção são três peças baratasMostrar IA funcionando uma vez é fácil. O difícil é deixar rodando todo dia sem ninguém olhando. Três peças simples — Docker para empacotar, healthcheck para vigiar, agendador para disparar — fazem quase todo o trabalho.

E aqui mora a armadilha mais comum, registrada como lição no próprio projeto Simon: o Docker lê o .env na inicialização do container, não durante o build. Trocar um valor no arquivo e torcer para o container perceber sozinho não funciona — é preciso docker compose down seguido de docker compose up, não apenas um restart.

O que pode dar errado

  • Token comitado no Git por engano: revogue no BotFather com /revoke e gere outro na hora — não dá para "desfazer" um token que já vazou.
  • Handler sem o guard de autorização: qualquer pessoa que descobrir o username do bot consegue usá-lo. O guard do Passo 3 é obrigatório em todo handler, não só no /start.
  • Trocar o .env e o container não refletir a mudança: o Docker lê variável de ambiente no início do container, não no build. Rode down e up; um restart sozinho não recarrega nada.
  • Container sem restart: unless-stopped ou equivalente: o bot cai numa reinicialização do host e não volta sozinho — alguém precisa lembrar de subir na mão.

Próximos passos

Separar a lógica em módulos próprios — um commands.py isolado do bot.py — vale a pena assim que o bot ganhar mais de dois ou três comandos; o exemplo deste tutorial já nasce enxuto de propósito, mas cresce rápido.

LEIA TAMBÉM · Caso realTrês falhas silenciosas depois, meu bot de Telegram reinicia sozinho a cada 12 horasUm bot pode continuar aberto e respondendo enquanto uma parte dele já parou de funcionar. Um supervisor é um trecho de código que reinicia o bot de tempos em tempos, sozinho, para que essa falha silenciosa não dure.

Se o volume de mensagens crescer e interpretar linguagem natural passar a valer mais do que decidir por comando fixo, vale medir o custo daquilo antes de trocar de arquitetura. E se o bot só existir para mandar avisos — nunca para receber comando — a biblioteca inteira é peso demais: um requests.post puro contra a API HTTP do Telegram resolve com uma dependência a menos.

Um bot de Telegram é, no fundo, um script comum com um canal de entrada e saída diferente do de sempre. O que muda entre rodar no seu terminal e rodar em produção não é a lógica — é quem garante o reinício automático e onde o token mora.