Scalare un assistente AI su Telegram: dal singolo utente al team
Hai costruito un assistente AI su Telegram per uso personale — un bot che fa da wrapper a Claude, GPT o un LLM simile, in esecuzione sul tuo server. Funziona bene da solo. Ora vuoi condividerlo con il tuo team: sviluppatori, consulenti o specialisti che trarrebbero vantaggio dall'accesso on-demand allo stesso assistente. Il passaggio da un singolo utente a un piccolo team ha più pezzi in movimento di quanto la documentazione di solito menzioni.
Questa guida copre ogni passaggio: raccogliere i Telegram ID dei membri del team, estendere l'allow-list in modo che sopravviva alla differenza tra restart e recreate di Docker, aprire il tipo giusto di gruppo, configurare il rilevamento dei trigger così il bot non diventa spam, e risolvere il trade-off sulla privacy che coglie di sorpresa la maggior parte dei team.
Cosa ti serve
- Un bot Telegram funzionante che già gestisce i messaggi per almeno un utente (tu)
- Accesso SSH o shell al server che esegue il container del bot
- I username Telegram dei membri del team che vuoi aggiungere
- Accesso a BotFather (lo stesso account Telegram che ha creato il bot originariamente)
- Circa venti minuti per il lavoro tecnico, più il tempo di coordinamento asincrono con i membri del team
Cosa risolve questa guida
- I membri del team non riescono ad attivare il bot — "funziona per me, non funziona per loro"
- Le modifiche all'allow-list sembrano applicate ma il bot ignora ancora i nuovi utenti dopo un riavvio
- Il bot spamma nei gruppi — risponde a ogni messaggio invece che solo a quelli rilevanti
- Comportamento dei trigger confuso — quando dovrebbe rispondere il bot? Solo con @menzione? Con una parola chiave? Con una reply?
- Preoccupazioni sulla privacy legate a una memoria condivisa tra utenti che non avevi anticipato
Passo 1: Raccogli il Telegram User ID numerico di ogni membro del team
L'allow-list del tuo assistente AI su Telegram è indicizzata per user ID numerici, non per username o nomi visualizzati. Gli username possono cambiare; l'ID numerico è stabile per tutta la vita dell'account. Hai bisogno di questo ID per ogni membro del team che vuoi aggiungere.
Il modo più semplice: chiedi a ogni membro del team di aprire una chat con @userinfobot (un bot pubblico di utilità). Il primo messaggio che rimanda contiene il loro ID numerico — qualcosa come 100000001. Digli di copiare l'ID e inviartelo in DM.
Alternative se @userinfobot è bloccato o non disponibile nella tua regione:
- Usa i log del tuo bot. Aggiungi temporaneamente una riga "registra tutti i tentativi" al middleware dell'allow-list del tuo bot, chiedi al membro del team di inviare un messaggio al tuo bot, e leggi lo user ID dai log del container. Rimuovi la riga di logging dopo aver raccolto l'ID.
- Leggi da un messaggio di gruppo. Se un utente ha già scritto in un gruppo in cui si trova il tuo bot, il campo
from.iddel messaggio contiene il suo ID numerico — leggibile tramite l'endpointgetUpdatesdel bot.
Conserva gli ID raccolti in un posto sicuro — un appunto, un password manager, o direttamente nel tuo file .env. Trattali come indirizzi email: identificano una persona specifica e possono essere incrociati con i profili pubblici di Telegram.
Passo 2: Estendi l'allow-list del tuo bot
La maggior parte dei framework per bot Telegram — aiogram, python-telegram-bot, Telegraf, grammY — implementa i controlli dell'allow-list come middleware. Ogni aggiornamento in ingresso viene filtrato per sender ID prima che qualsiasi handler venga eseguito. I mittenti non autorizzati vengono scartati silenziosamente: il tuo bot riceverà i loro messaggi internamente ma non risponderà mai.
L'allow-list di solito vive in una variabile d'ambiente, caricata nel container tramite env_file: nel tuo docker-compose.yml:
BOT_ALLOWED_USERS=100000001,100000002,100000003 BOT_OPERATOR_ID=100000001
Si usano comunemente due formati:
- Lista CSV:
BOT_ALLOWED_USERS=111,222,333. Analizzata dal codice di configurazione del bot comelist[int]. - Array JSON:
BOT_ALLOWED_USERS=[111,222,333]. Usata quando il parser dei settings si aspetta JSON per i tipi complessi.
Se il tuo bot usa Python e pydantic-settings per la configurazione, la forma con array JSON è la scelta più sicura — anche con un singolo utente, preferisci BOT_ALLOWED_USERS=[100000001] rispetto a BOT_ALLOWED_USERS=100000001. Il motivo è spiegato in dettaglio nella sezione pydantic più avanti, ma in breve: la forma JSON evita un'ambiguità del parser che manda in crash il container all'avvio.
Passo 3: Riavvia il container nel modo giusto
È qui che la maggior parte degli aggiornamenti all'allow-list va storta in silenzio. Modifichi .env, esegui docker compose restart your-bot, guardi il container tornare su — e i nuovi utenti non riescono ancora ad attivare il bot. La modifica "non ha effetto".
Il motivo: docker compose restart si limita a fermare e avviare il container esistente. Non lo ricrea. Le variabili d'ambiente — inclutto tutto ciò che viene da env_file: — vengono iniettate nel container al momento della creazione, non all'avvio. Un restart preserva lo snapshot originale delle env var. Il tuo file .env modificato è irrilevante per un container riavviato.
Il comando corretto:
docker compose up -d --force-recreate --no-deps your-bot-service-name
Cosa fa ciascun flag:
--force-recreateferma il vecchio container, lo rimuove e ne crea uno nuovo con le specifiche Compose aggiornate — incluso il contenuto appena modificato dienv_file:.--no-depsevita che Compose ricrei anche i servizi da cui dipende il tuo bot (database, code di messaggi). Se il bot non hadepends_on, questo flag è un no-op ma non fa danni.-davvia il container ricreato in modalità detached, così il terminale torna subito libero.
Verifica che il recreate sia avvenuto controllando l'uptime del container:
docker ps --filter name=your-bot --format "{{.Status}}"
# Atteso: "Up 10 seconds" (non "Up 4 hours")
Se lo status mostra lo stesso uptime lungo di prima, il recreate non è avvenuto — controlla eventuali errori di battitura nel nome del servizio o se hai eseguito il comando dalla directory corretta.
Questo pattern si applica a qualsiasi servizio Docker Compose la cui configurazione vive in un env-file: API gateway, worker, scraper, agenti di monitoring. La stessa trappola ti coglie ogni volta. Abbiamo documentato pattern più ampi per gestire molti container di questo tipo nella nostra guida ai controlli di salute periodici per infrastrutture Dockerizzate.
Pydantic-Settings: una trappola del parser da conoscere
Se il layer di configurazione Python del tuo bot usa pydantic-settings — la libreria standard per i settings con Pydantic v2 — e dichiari l'allow-list come list[int], incapperai in un problema del parser che vale la pena capire prima che ti morda.
Pydantic-settings tratta i tipi complessi (list, dict, tuple) come JSON-encoded per default. Quando legge BOT_ALLOWED_USERS=111,222 dal tuo env-file, tenta prima json.loads("111,222"). Questo fallisce con JSONDecodeError: Extra data perché una CSV semplice non è JSON valido. Il container va in crash all'avvio con un SettingsError: error parsing value for field.
Se hai un BeforeValidator personalizzato associato al campo, che sa come analizzare il CSV, potresti assumere che venga eseguito prima e intercetti la stringa grezza prima del tentativo di JSON-decode. Non è così. Pydantic-settings applica il passaggio di JSON-decode prima di qualsiasi validatore a livello di campo per i tipi complessi.
Hai due workaround:
Soluzione rapida — sintassi JSON-array nell'env-file:
BOT_ALLOWED_USERS=[100000001,100000002,100000003]
È JSON valido. Pydantic-settings lo decodifica direttamente in una lista di interi. Nessun validatore necessario. Il trade-off è puramente estetico: parentesi quadre attorno a una lista.
Soluzione definitiva — annota il campo con NoDecode:
from pydantic_settings import NoDecode
from pydantic import BeforeValidator
from typing import Annotated
def parse_csv(v):
if isinstance(v, str):
return [int(x.strip()) for x in v.split(",")]
return v
class Settings(BaseSettings):
bot_allowed_users: Annotated[list[int], NoDecode, BeforeValidator(parse_csv)]
NoDecode sopprime completamente il passaggio di JSON-decode. Il tuo BeforeValidator riceve la stringa grezza e la analizza come CSV. Questa è la soluzione più pulita se hai il controllo del codice dei settings.
Il problema di fondo è tracciato in pydantic-settings issue #157, con discussioni correlate nei ticket #184 e #570. Il comportamento è coerente in tutte le versioni attualmente rilasciate di pydantic-settings (v2.x). Se non controlli il codice dei settings — usando un framework di terze parti per il bot — usa il workaround con la sintassi JSON-array.
Passo 4: Crea un gruppo Telegram con il bot e il team
Telegram offre due tipi di gruppi per questo caso d'uso:
- Gruppo normale: fino a 200 membri, modello admin semplice, nessuna funzionalità avanzata. Adatto per team piccoli.
- Supergruppo: fino a 200.000 membri, permessi admin granulari, discussioni con thread, cronologia messaggi persistente. Puoi convertire un gruppo normale in supergruppo in seguito, se cresci.
Per workflow di team fino a una dozzina di membri, un gruppo normale è sufficiente. Passi:
- Nell'app Telegram, tocca "Nuovo Gruppo" e seleziona i membri del team dai tuoi contatti
- Dai al gruppo un nome descrittivo — "Progetto X — AI Assistant", "Engineering Bot Workspace", ecc.
- Una volta creato, apri le impostazioni del gruppo, tocca "Aggiungi Membro", cerca il username del tuo bot (
@your_bot_name) e aggiungilo - Promuovi il bot ad admin solo se ha bisogno di azioni da admin (eliminare messaggi, appuntare messaggi). Per un uso puramente question-and-answer, lo stato di membro normale è sufficiente
Se gestisci più bot specifici per progetto su più team (noi ne manteniamo una manciata su infrastruttura condivisa), i pattern multi-tenant che utilizziamo sono documentati nella nostra guida al development stack Docker multi-tenant.
Passo 5: Configura il rilevamento dei trigger
Per default, un bot Telegram in un gruppo riceve solo i messaggi che lo menzionano esplicitamente (@your_bot_name), rispondono ai suoi messaggi, o usano un comando slash. Telegram chiama questa modalità "Privacy Mode", ed è attiva per default — un default sensato che previene lo spam accidentale del bot.
Ma per un assistente AI su Telegram che dovrebbe rispondere a domande naturali ("Ehi bot, qual è lo stato del deploy?" senza una menzione esplicita), la Privacy Mode è troppo restrittiva. Hai due strade:
Strada A: tieni la Privacy Mode attiva e addestra il team a usare @menzione. Semplice, nessuna modifica alla configurazione necessaria. Il bot vede solo ciò a cui dovrebbe rispondere. Svantaggio: attrito. I membri del team dimenticano la @ e il bot rimane in silenzio.
Strada B: disabilita la Privacy Mode e implementa la tua logica di trigger. Apri BotFather, invia /setprivacy, scegli il tuo bot, imposta su Disable. Il bot riceve ora ogni messaggio del gruppo. Implementi tu stesso il controllo "devo rispondere?".
Un set di trigger pratici che usiamo in produzione:
- Messaggio diretto: rispondi sempre — stai parlando con il bot in privato
- @menzione nel gruppo: rispondi sempre — invocazione esplicita
- Reply a uno dei messaggi del bot: rispondi sempre — continuazione di un thread avviato dal bot
- Messaggio del gruppo contenente la parola trigger del bot: rispondi. La parola trigger è tipicamente il nickname del bot o il nome del progetto, rilevata con una regex a word-boundary così "advisor" non attiva accidentalmente "advisory"
- Tutto il resto: registra silenziosamente in un file di conversazione, nessuna risposta
La parte del log silenzioso conta. Anche quando il bot non risponde, vede comunque il flusso della conversazione del gruppo. Registrare ogni messaggio in un file per-chat dà al bot un contesto futuro — quando qualcuno lo @menziona finalmente con una domanda come "cosa abbiamo deciso?", il bot ha la conversazione recente disponibile come contesto su cui ragionare.
L'implementazione dipende dal tuo framework. In aiogram, un singolo message handler esegue tutti e cinque i controlli prima di decidere se chiamare il tuo LLM e rispondere. In Telegraf o grammY, il pattern è identico — un handler bot.on('message') che filtra esplicitamente prima di reagire.
Passo 6: Risolvi il trade-off sulla privacy
Ecco la domanda a cui la maggior parte dei team non pensa finché non diventa un problema: il bot mantiene memoria separata per utente, per gruppo, o globalmente su tutte le conversazioni?
Tre pattern sono comuni:
- Memoria per-chat: il bot inizia una sessione fresca per ogni chat. Il DM con l'utente A è indipendente dal DM con l'utente B, ed entrambi sono indipendenti dal gruppo X. Massima privacy. Svantaggio: il bot non ricorda il contesto tra le sessioni, il che limita la sua utilità come "assistente che conosce il nostro progetto"
- Memoria per-utente: il bot mantiene thread di memoria separati per utente, ma li condivide tra DM e menzioni in gruppo dello stesso utente. Un ragionevole compromesso
- Memoria globale: il bot ha un'unica sessione a cui contribuiscono tutte le conversazioni. Massima condivisione del contesto — DM e conversazioni di gruppo costruiscono tutti la stessa memoria a lungo termine. Svantaggio: leak di privacy. Una cosa confidenziale che un membro del team dice al bot in DM può emergere in una risposta di gruppo a una domanda di un altro membro
Ogni pattern è difendibile. Ognuno ha trade-off su cui il tuo team deve essere d'accordo prima di passare al multi-utente.
Se scegli la memoria globale — noi lo facciamo, per team affiatati dove la condivisione del contesto è parte del valore offerto — sii esplicito con il team prima che inizi a usare il bot: "Qualsiasi cosa dici a questo bot potrebbe emergere in risposte visibili all'intero gruppo. Trattalo come uno spazio di lavoro condiviso, non come un confidente privato."
Se scegli la memoria per-chat, rinunci al ragionamento cross-context ("cosa abbiamo deciso la settimana scorsa su X?") ma eviti completamente il rischio di leak.
Questa è una scelta di design con conseguenze sociali reali, non una leva tecnica che puoi cambiare in seguito senza un allineamento dell'intero team. Discutiamo trade-off simili nel nostro approfondimento più ampio su AI agent skills per workflow specifici di dominio, dove le configurazioni a contesto condiviso emergono in ogni ingaggio con i clienti.
Scalare l'assistente AI su Telegram oltre il piccolo team
Il pattern dell'allow-list via env-file funziona bene per team fino a circa venti o trenta utenti. Oltre quella soglia, le voci hardcoded diventano scomode — ogni onboarding richiede un commit git (se il tuo env è versionato tramite SOPS o un layer simile di secrets management), un deploy e un container recreate.
Pattern che scalano ulteriormente:
- Allow-list basata su database: gli utenti vivono in una tabella SQL, il bot legge e mette in cache la lista all'avvio, poi la aggiorna periodicamente (o tramite webhook sulle modifiche agli utenti). Fare l'onboarding di un utente diventa un'istruzione
INSERT— nessun deploy necessario - Accesso basato su membership al gruppo: invece di permettere singoli utenti, permetti qualsiasi utente che sia membro di un gruppo Telegram specifico (o un piccolo insieme di gruppi). La membership al gruppo diventa il confine di accesso. L'API
getChatMemberdi Telegram conferma la membership prima di ogni invocazione - Basato su canale: per assistenti in sola lettura (riepiloghi giornalieri, alert, digest di monitoring), usa un canale Telegram invece di un gruppo. I canali hanno un modello di permessi diverso — solo gli admin pubblicano, gli altri leggono. Utile quando c'è un fan-out uno-a-molti piuttosto che una conversazione bidirezionale
Per workflow di piccoli team — sviluppatori, consulenti, specialisti occasionali — il pattern dell'allow-list via env-file è sufficiente. Lo usiamo su gran parte della nostra infrastruttura interna e trattiamo la variante basata su database come un refactor che facciamo una volta che un progetto supera dimostrabilmente la forma più semplice.
Checklist finale
Prima che il team inizi a usare il bot, verifica:
- Gli ID Telegram numerici di tutti i membri del team raccolti e aggiunti all'allow-list
- Il formato dell'allow-list corrisponde a quello che si aspetta il tuo parser dei settings — JSON-array se pydantic-settings è nello stack
- Il container ricreato (non solo riavviato) così le nuove env var sono caricate in un container fresco
- Il gruppo Telegram creato con il bot aggiunto come membro (e promosso ad admin se ha bisogno di azioni da admin)
- La Privacy Mode di BotFather configurata in base alla tua strategia di trigger — disabilitata solo se hai implementato la tua logica di filtro
- La logica dei trigger nel codice del bot allineata con la Privacy Mode (non disabilitare la privacy senza filtrare, altrimenti il bot spammerà ogni messaggio del gruppo)
- Il modello di memoria (per-chat / per-utente / globale) scelto e comunicato al team
- Le aspettative sulla privacy impostate esplicitamente con i membri del team prima che inizino a usare il bot
Se stai costruendo un assistente specifico per progetto da zero e vuoi capire come si incastra l'infrastruttura del bot sottostante, il nostro articolo complementare su costruire un assistente AI specifico per progetto via Telegram copre le fondamenta. Per l'infrastruttura email che spesso accompagna questi setup — notifiche, percorsi di escalation, audit trail — vedi il nostro articolo su setup di casella email specifica per progetto con DKIM e DMARC.
Se gestisci questo tipo di infrastruttura di assistenti AI su Telegram multi-utente per clienti o per il tuo team e vuoi supporto con il rollout, contattaci. Costruiamo e gestiamo questo tipo di setup come parte del nostro lavoro sui progetti.
Approfondimenti correlati
- Costruire un assistente AI locale con ricerca web: setup MCP + Ollama — pattern di deployment alternativi per assistenti AI self-hosted
- Claude Skills: la risposta di Anthropic al problema della personalizzazione AI — contesto più ampio sulla personalizzazione degli assistenti AI per workflow specifici
- Guida completa al troubleshooting di n8n self-hosted: risolvere problemi di dimensione dei dati di esecuzione e webhook — precedenti di troubleshooting Docker Compose per servizi self-hosted