Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GestisciMail

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)

Indice


Come funziona

I due assi

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.

Le due fasi

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.


Requisiti

  • 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

Installazione

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.yaml

Su 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:4b

Configurazione OAuth (una volta sola)

Serve 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.

  1. Vai su console.cloud.google.com e crea un progetto.
  2. API e servizi → Libreria → cerca Gmail APIAbilita.
  3. 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
  4. Credenziali → Crea credenziali → ID client OAuth → tipo App desktopScarica JSON.
  5. Rinomina il file in credentials.json e mettilo nella cartella data/.

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

Permessi richiesti

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

Avvio rapido

.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 --conferma

I 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 --conferma

Tutti i comandi

Tutti i comandi che modificano qualcosa richiedono --conferma: senza, sono esecuzioni di prova che mostrano soltanto cosa farebbero.

auth — autorizza l'accesso a Gmail

.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).

verifica — controlla che tutto sia a posto

.venv\Scripts\python -m gestiscimail verifica

Controlla che Ollama risponda, che il modello sia scaricato, e mostra le categorie configurate con le rispettive etichette Gmail.

classifica — scarica e classifica mail

.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

addestra — ripassa l'archivio

.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.

riallinea — ricalcola i mittenti, senza modello

.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.

anteprima — rivedi le decisioni

.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.

applica — scrive le etichette in Gmail

.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.

regole — cosa si può automatizzare

.venv\Scripts\python -m gestiscimail regole

Mostra 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.

sincronizza-filtri — aggiorna i filtri in Gmail

.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.

esporta-filtri — in alternativa, genera l'XML

.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.

stato — riepilogo

.venv\Scripts\python -m gestiscimail stato

Quante mail, quante decisioni per asse, quante etichette già in Gmail, quante in attesa, e la copertura delle regole.

dimentica — azzera l'archivio locale

.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.


Configurazione

config/categories.yaml — le etichette

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.

Indirizzi completi

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 clienti

Prendersi tutto societa-sondaggi.example etichetterebbe come università anche i questionari di chiunque altro.

config/settings.yaml — il resto

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, ...]

Il ciclo di taratura

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 --conferma

Il 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 corriere2

In ogni caso poi anteprima, e quando convince applica --conferma.

Le leve, in ordine di efficacia

  1. 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ì.
  2. Le esclusioni nella descrizione — per le categorie di contenuto.
  3. Gli esempi — il modo più rapido di correggere un errore ricorrente.

Prestazioni misurate

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

Tre risultati controintuitivi

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.

Su 8 GB di VRAM

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.


Struttura del progetto

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

Privacy e sicurezza

  • 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.modify non lo permette.
  • Ogni operazione distruttiva richiede --conferma e 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'è solo categories.example.yaml, con nomi segnaposto. Se fai un fork, controlla di non reintrodurlo con git add -f.

Limiti

  • 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 regole aiuta 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 regole mostra 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.

Licenza

Non ancora scelta. Se pubblichi il repository, aggiungi un file LICENSE: MIT è la scelta usuale per un progetto come questo.

About

Etichettatura automatica di Gmail con un modello AI locale (Ollama): nessuna mail lascia il PC, e i mittenti ricorrenti diventano filtri Gmail gratuiti.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages