Etichettatura automatica della posta Gmail con un modello AI che gira sul tuo PC.
Il contenuto delle mail non esce dal computer: l'unica chiamata di rete del
classificatore è verso localhost:11434 (Ollama). Verso Google passano solo le
richieste di lettura dei messaggi e di applicazione delle etichette.
Una mail può ricevere più etichette: un pagamento della tua banca prende sia
Banca/NomeBanca sia Banca/Conferma pagamenti.
Da: noreply@labanca.it
Oggetto: Pagamento eseguito di 45,00 EUR
→ Banca/NomeBanca (chi ha scritto, dal dominio)
→ Banca/Conferma pagamenti (di cosa si tratta, dal modello)
- Come funziona
- Requisiti
- Installazione
- Configurazione OAuth
- Avvio rapido
- Tutti i comandi
- Configurazione
- Il ciclo di taratura
- Prestazioni misurate
- Struttura del progetto
- Privacy e sicurezza
- Limiti
Le etichette rispondono a due domande diverse, e il sistema le tiene separate. Ogni mail riceve al massimo un'etichetta per asse, ma può averne una per ciascuno.
| asse | domanda | come si decide | costo |
|---|---|---|---|
| mittente | chi ha scritto? | confronto del dominio | istantaneo, esatto |
| contenuto | di cosa si tratta? | il modello legge la mail | ~1 s per mail |
Questa separazione non è cosmetica. Chiedere a un modello da 4-7 miliardi di
parametri di scegliere fra quindici categorie con precedenze incrociate produce
errori grossolani: nei test una mail da ilpost.it finiva nella categoria
poste. Riconoscere il mittente è un confronto esatto, non un giudizio, e
toglierlo dal piatto del modello ha portato l'accuratezza da 16/20 a 20/20.
Il modello resta a decidere fra quattro o cinque categorie omogenee, che è un compito alla sua portata.
Fase 1 — sul tuo PC, quando lo lanci tu. Il sistema legge le mail, decide, e scrive solo in un database SQLite locale. Gmail non viene toccata finché non lo chiedi esplicitamente.
Fase 2 — su Google, gratis e per sempre. Le regole ricorrenti diventano filtri Gmail, che etichettano da soli la posta futura anche a PC spento. Vengono da due fonti:
- i domini dichiarati nella configurazione: certi per costruzione, validi da subito senza aver classificato niente;
- i mittenti scoperti dal modello: promossi a regola solo se frequenti (almeno N mail) e coerenti (almeno X% nella stessa categoria).
Il modello resta utile per ciò che le regole non coprono, e più lo usi più regole scopre.
- Python 3.11+ (sviluppato e testato su 3.14)
- Ollama in esecuzione, con un modello scaricato
- una casella Gmail
- una GPU aiuta molto ma non è obbligatoria: su CPU il modello va, solo più lentamente
git clone <url-del-repo>
cd GestisciMail
python -m venv .venv
.venv\Scripts\pip install -r requirements.txt
copy config\categories.example.yaml config\categories.yamlSu Linux o macOS sostituisci .venv\Scripts\ con .venv/bin/ e copy con cp.
L'ultimo passo crea la tua configurazione a partire dall'esempio. Il file
config/categories.yaml è escluso da git apposta: descrive quali servizi usi,
quindi non va versionato in un repository pubblico. Le categorie di esempio sono
segnaposto (labanca.it, latuauniversita.it): sostituiscile con i domini
reali dei servizi che segui.
Scarica il modello:
ollama pull qwen3:4bServe un file di credenziali che autorizzi questo programma ad accedere alla tua casella. Va creato dal tuo account Google: nessuno può farlo al posto tuo.
- Vai su console.cloud.google.com e crea un progetto.
- API e servizi → Libreria → cerca Gmail API → Abilita.
- API e servizi → Schermata consenso OAuth (nelle console recenti:
Google Auth Platform → Branding):
- tipo di utente Esterno
- compila nome app, email di assistenza e contatto sviluppatore
- nella sezione Destinatari / Utenti di test aggiungi il tuo indirizzo Gmail
- Credenziali → Crea credenziali → ID client OAuth → tipo App desktop → Scarica JSON.
- Rinomina il file in
credentials.jsone mettilo nella cartelladata/.
Consiglio: imposta lo stato di pubblicazione su In produzione. Se il progetto resta in Test, Google invalida l'autorizzazione ogni 7 giorni e dovrai rifare
auth. Vedrai un avviso "app non verificata" al primo accesso: è normale per un'app personale, si passa da Avanzate → Vai a ....
Poi autorizza:
.venv\Scripts\python -m gestiscimail auth| scope | a cosa serve |
|---|---|
gmail.modify |
leggere le mail e applicare etichette. Non può cancellare nulla in modo definitivo |
gmail.settings.basic |
leggere e gestire i filtri, per sincronizza-filtri |
.venv\Scripts\python -m gestiscimail verifica
.venv\Scripts\python -m gestiscimail classifica --max 50
.venv\Scripts\python -m gestiscimail anteprima
.venv\Scripts\python -m gestiscimail applica --confermaI primi tre comandi non modificano niente. Solo applica --conferma scrive
in Gmail, ed è reversibile: cerca le tue etichette in Gmail e toglile in blocco.
Quando l'anteprima ti convince, allarga:
.venv\Scripts\python -m gestiscimail classifica --query "in:inbox" --max 5000
.venv\Scripts\python -m gestiscimail applica --conferma
.venv\Scripts\python -m gestiscimail sincronizza-filtri --confermaTutti i comandi che modificano qualcosa richiedono --conferma: senza, sono
esecuzioni di prova che mostrano soltanto cosa farebbero.
.venv\Scripts\python -m gestiscimail auth [--rinnova]Apre il browser e salva il token in data/token.json. --rinnova butta
l'autorizzazione salvata e la rifà da capo: serve se cambi account o se i
permessi richiesti sono aumentati (il programma se ne accorge da solo e te lo
dice).
.venv\Scripts\python -m gestiscimail verificaControlla che Ollama risponda, che il modello sia scaricato, e mostra le categorie configurate con le rispettive etichette Gmail.
.venv\Scripts\python -m gestiscimail classifica [opzioni]| opzione | effetto |
|---|---|
--query QUERY |
query Gmail, sovrascrive settings.yaml. Es. "in:inbox newer_than:30d" |
--max N |
numero massimo di mail |
--riclassifica |
rianalizza anche le mail già in archivio |
--solo-categoria CAT... |
solo le mail finite in quelle categorie |
--solo-dominio FRAMMENTO... |
solo le mail di quei mittenti, per frammento di indirizzo |
Non tocca Gmail. Di default salta le mail già classificate.
--solo-dominio è il modo rapido di correggere un gruppo di mittenti senza
rifare tutto l'archivio:
.venv\Scripts\python -m gestiscimail classifica --solo-dominio flixbus ryanair.venv\Scripts\python -m gestiscimail addestra [--tutto] [--forza] [--max N]Completa le decisioni mancanti su tutte le mail già in archivio.
| modalità | cosa rielabora |
|---|---|
| (nessuna) | solo le mail senza decisione sull'asse contenuto |
--tutto |
anche quelle decise prima dell'ultima modifica alla configurazione |
--forza |
proprio tutte, anche quelle già aggiornate |
È riprendibile. Ogni mail viene salvata appena decisa, e --tutto confronta
la data di ogni decisione con quella dei file di configurazione: ciò che è già
aggiornato viene saltato. Se il PC si spegne a metà, rilanciando riprende da
dove era.
Salva anche il testo delle mail in locale, quindi le passate successive non riscaricano niente da Gmail.
.venv\Scripts\python -m gestiscimail riallinea [--conferma]Riapplica i domini della configurazione a tutto l'archivio. Non usa il modello né la rete: è istantaneo anche su migliaia di mail. È il modo rapido di propagare l'aggiunta di un dominio nuovo.
Aggiunge e corregge, non toglie: se hai rimosso un dominio, quell'etichetta va tolta a mano da Gmail.
.venv\Scripts\python -m gestiscimail anteprima [--limite N] [--categoria CAT] [--solo-pendenti]Mostra ogni mail con tutte le sue etichette e la motivazione del modello per ciascun asse. È il comando su cui si decide se fidarsi.
.venv\Scripts\python -m gestiscimail applica [--conferma]Crea le etichette mancanti e le applica. Le etichette vengono corrette, non accumulate: se una mail cambia categoria, la vecchia viene tolta e messa la nuova; se passa alla categoria di scarto, viene tolta e basta. Gli assi sono indipendenti, quindi correggere il contenuto non tocca l'etichetta del mittente.
Il programma rimuove solo etichette che ha messo lui.
.venv\Scripts\python -m gestiscimail regoleMostra le regole certe (dai domini in configurazione) e quelle scoperte dal modello, con quante mail le sostengono, e stima quale quota di posta futura verrebbe gestita da Gmail da sola.
.venv\Scripts\python -m gestiscimail sincronizza-filtri [--conferma] [--rimuovi-obsoleti]Confronta i filtri presenti in Gmail con le regole attuali e tocca solo la
differenza, senza creare doppioni. Con --rimuovi-obsoleti elimina anche i
filtri che non corrispondono più a nessuna regola.
Un filtro è considerato gestito dal programma solo se seleziona per solo mittente e aggiunge una delle etichette configurate. I filtri scritti a mano — quelli con criteri aggiuntivi, quelli verso altre etichette, quelli che archiviano — non vengono mai toccati.
.venv\Scripts\python -m gestiscimail esporta-filtri [--output FILE]Scrive data/filtri_gmail.xml, importabile da Impostazioni → Filtri e
indirizzi bloccati → Importa filtri. Utile se preferisci non concedere il
permesso sui filtri, o se vuoi rivedere l'elenco prima.
In fase di importazione puoi spuntare "Applica i nuovi filtri alle conversazioni esistenti" per etichettare anche lo storico.
Reimportando lo stesso file si creano doppioni: cancella prima i filtri esistenti, oppure usa
sincronizza-filtri, che gestisce la differenza.
.venv\Scripts\python -m gestiscimail statoQuante mail, quante decisioni per asse, quante etichette già in Gmail, quante in attesa, e la copertura delle regole.
.venv\Scripts\python -m gestiscimail dimentica [--conferma]Cancella il database locale. Non tocca Gmail: le etichette già applicate restano, e da quel momento il programma non sa più di averle messe, quindi non potrà toglierle da solo. Per ripulire davvero, rimuovi prima le etichette da Gmail.
prefisso: ""
categorie:
# Categoria di MITTENTE: decisa dal dominio, senza interpellare il modello
- nome: labanca
etichetta: "Banca/LaBanca"
descrizione: "Qualsiasi mail dalla banca."
deciso_dal_dominio: true
domini: [labanca.it, labanca.com]
# Categoria di CONTENUTO: decisa dal modello
- nome: conferma pagamenti
etichetta: "Banca/Conferma pagamenti"
descrizione: >-
La mail CONFERMA che un pagamento è GIÀ AVVENUTO: ricevute, addebiti,
rimborsi accreditati.
ESCLUDI ciò che non è ancora stato pagato: bollette in scadenza,
solleciti, avvisi di rinnovo futuro. Quelli vanno in "Altro".
esempi:
- "Ricevuta del tuo pagamento di 24,90 EUR"
- nome: Altro
descrizione: >-
Non rientra in nessuna delle precedenti. È la risposta corretta per la
maggior parte della posta.
categorie_senza_etichetta:
- Altro
fallback: Incerto| campo | significato |
|---|---|
nome |
identificatore interno: database, prompt, --solo-categoria. Piatto e unico |
etichetta |
nome esatto in Gmail. Può contenere / per annidare: "Banca/LaBanca" |
descrizione |
finisce nel prompt del modello |
esempi |
oggetti di esempio, molto utili sui casi ambigui |
domini |
domini del mittente, o indirizzi completi se contengono @ |
deciso_dal_dominio |
se true, il dominio basta e il modello non viene interpellato |
asse |
dedotto automaticamente, forzabile a mano |
Tenere nome ed etichetta separati permette di riorganizzare le etichette
in Gmail — rinominarle, annidarle, cambiare maiuscole — senza invalidare
l'archivio: basta aggiornare etichetta.
categorie_senza_etichetta elenca le categorie che il modello può scegliere ma
che non producono nessuna etichetta. Servono a dargli una via d'uscita
esplicita: senza, incasella a forza ogni mail dentro qualcosa.
Una voce di domini che contiene @ è un indirizzo esatto. Serve quando il
dominio non è attribuibile a un solo mittente:
domini:
- latuauniversita.it
- ateneo@societa-sondaggi.example # scrive anche per conto di altri clientiPrendersi tutto societa-sondaggi.example etichetterebbe come università anche
i questionari di chiunque altro.
ollama:
modello: "qwen3:4b"
host: "http://localhost:11434"
temperatura: 0.0
timeout_secondi: 120
tieni_in_memoria: "10m"
gmail:
query: "in:inbox newer_than:90d"
max_messaggi: 200
caratteri_corpo: 1500
classificazione:
soglia_confidenza: 0.60
salta_gia_classificate: true
regole:
supporto_minimo: 3 # quante mail servono per creare una regola
purezza_minima: 0.85 # che percentuale deve concordare
regole_per_indirizzo: true
domini_generici: [gmail.com, outlook.com, libero.it, ...]L'anteprima non convince quasi mai al primo giro. Il tipo di errore ti dice quale leva usare.
Una mail è finita nella categoria di scarto ma doveva andare in una di
mittente → quel servizio usa un dominio non previsto. Aggiungilo a domini,
poi:
.venv\Scripts\python -m gestiscimail riallinea --confermaIl modello ha sbagliato fra due categorie di contenuto → il problema è nella
descrizione. La regola pratica emersa dai test: scrivere cosa escludere rende
più della descrizione positiva. Aggiungere il caso sbagliato come esclusione
esplicita è il rimedio più efficace. Poi:
.venv\Scripts\python -m gestiscimail addestra --tutto--tutto rifà solo ciò che è più vecchio della configurazione, quindi non
ripaga mai due volte lo stesso lavoro.
Per correggere un gruppo ristretto di mittenti senza rifare l'archivio:
.venv\Scripts\python -m gestiscimail classifica --solo-dominio corriere1 corriere2In ogni caso poi anteprima, e quando convince applica --conferma.
domini— la più potente. Sposta una categoria dal giudizio del modello a un confronto esatto: zero errori, zero tempo di calcolo, e genera un filtro Gmail. Se una categoria è "tutto ciò che arriva da X", va sempre lì.- Le esclusioni nella
descrizione— per le categorie di contenuto. - Gli
esempi— il modo più rapido di correggere un errore ricorrente.
Su RTX 3070 (8 GB), banco di prova costruito sui casi ambigui:
| banco | risultato | tempo |
|---|---|---|
| categorie di contenuto | 22/22 | 0,62 s per mail |
| viaggi, scuola e confini vicini | 26/26 | 0,84 s per mail |
| etichette multiple (7 casi a due etichette) | 15/15 | 0,98 s per mail |
Le decisioni che hanno prodotto quei numeri, ognuna misurata:
| decisione | prima | dopo |
|---|---|---|
| mittente deciso dal dominio anziché dal modello | 16/20 | 20/20 |
qwen3:4b invece di mistral:7b sulle categorie di contenuto |
7/12 | 12/12 |
| categoria di scarto in cima all'elenco invece che in fondo | 10/12 | 12/12 |
think: false sui modelli ibridi |
121 s/mail | 1,7 s/mail |
Il modello più grande è il peggiore per questo compito. mistral:7b tende a
incasellare a forza invece di rispondere "nessuna di queste": su 12 mail da
mittenti sconosciuti ne etichettava 5 a sproposito. qwen3:4b zero.
La posizione nell'elenco conta più della descrizione. Il modello preferisce le prime voci. Per questo le categorie senza etichetta vengono presentate per prime — ed è anche la direzione giusta in cui sbagliare: nel dubbio la mail resta com'è.
I modelli "pensanti" vanno disattivati. Senza think: false, qwen3 ragiona
per oltre un minuto prima di scegliere fra cinque etichette. Con, impiega 1,7
secondi.
Chiudi gli altri programmi che usano la GPU (LM Studio, giochi, browser pesanti) prima di classificare: con la VRAM satura la velocità è crollata da ~75 a ~3 token/s, trenta volte più lento.
GestisciMail/
├── config/
│ ├── categories.yaml le etichette e come vengono decise
│ └── settings.yaml modello, query, soglie
├── gestiscimail/
│ ├── cli.py i comandi
│ ├── config.py caricamento e validazione
│ ├── classifier.py il modello su Ollama, i due assi
│ ├── gmail.py OAuth, lettura, etichette, filtri
│ ├── store.py database locale (SQLite)
│ ├── rules.py estrazione delle regole
│ └── filters_export.py generazione dell'XML per Gmail
├── data/ NON versionata: credenziali, token, database
└── requirements.txt
- Il contenuto delle mail non lascia il PC. Il classificatore parla solo con
localhost:11434. data/è esclusa da git e contiene credenziali, token, e il testo delle mail salvato per rendere ripetibile il training. Non condividerla.- Il programma non può cancellare mail. Lo scope
gmail.modifynon lo permette. - Ogni operazione distruttiva richiede
--confermae mostra prima cosa farebbe. config/categories.yamlè escluso da git. Descrive quali servizi usi — banca, assicurazione, università, scuola — e in un repository pubblico equivale a un profilo di te. Versionato c'è solocategories.example.yaml, con nomi segnaposto. Se fai un fork, controlla di non reintrodurlo congit add -f.
- I domini vanno verificati sulla tua posta. Un servizio può usare domini di
invio diversi da quelli previsti, e quelle mail finiranno nella categoria di
scarto. Il comando
regoleaiuta a scoprirli. - I filtri Gmail non capiscono il contenuto: confrontano solo mittente e testo. Automatizzano i mittenti ricorrenti, non i casi nuovi.
- Le regole scoperte ereditano gli errori del modello. Se sbaglia
sistematicamente su un mittente, la regola consolida l'errore: per questo
regolemostra sempre quante mail sostengono ciascuna, e conviene leggere l'elenco prima di sincronizzare. Le regole da configurazione non hanno questo problema. - Ogni mail è classificata da sola, senza il contesto della conversazione.
- I numeri dei banchi di prova vengono da insiemi piccoli e scritti a tavolino, con casi scelti apposta per essere difficili: indicano la direzione, non garantiscono il risultato sulla tua casella.
- Non c'è addestramento del modello. "Training" qui significa far girare il modello su tutto l'archivio per costruire il corpus da cui si estraggono le regole; i pesi del modello non vengono modificati.
Non ancora scelta. Se pubblichi il repository, aggiungi un file LICENSE:
MIT è la scelta usuale per un
progetto come questo.