- Gmail to SQLite ist eine Python-Anwendung, die Gmail-Nachrichten mit einer lokalen SQLite-Datenbank synchronisiert, um sie für Analysen und Archivierung zu nutzen
- Das Standardverhalten ist eine inkrementelle Synchronisierung, die nur neue Nachrichten herunterlädt; mit der Vollsynchronisierungsoption können alle Nachrichten heruntergeladen und auch Löschungen erkannt werden
- Der Nachrichtenabruf verwendet multithreaded Parallelverarbeitung und umfasst Fehler- und Beendigungsbehandlung wie automatische Wiederholungsversuche mit exponentiellem Backoff und Verarbeitung von CTRL+C
- Für die Ausführung werden Python 3.8 oder höher, ein Google Cloud Project mit aktivierter Gmail API und die OAuth-2.0-Datei
credentials.json benötigt
- Die gespeicherten Daten enthalten Absender, Empfänger, Labels, Textinhalt, Größe, Gelesen-Status, Gesendet-Status, Löschstatus usw., sodass sich Gmail-Nutzungsmuster direkt per SQL analysieren lassen
Tool zur lokalen Synchronisierung von Gmail-Nachrichten
- Gmail to SQLite ist eine Python-Anwendung, die Gmail-Nachrichten in einer lokalen SQLite-Datenbank speichert
- Ziel ist es, Gmail-Daten zu analysieren und zu archivieren
- Im gesamten Codebestand werden Type Hints verwendet, um Typsicherheit zu gewährleisten
Synchronisationsweise und Stabilität
- Die Standardsynchronisierung arbeitet als inkrementelle Synchronisierung und lädt nur neue Nachrichten herunter
- Mit der Option
--full-sync werden alle Nachrichten synchronisiert und in Gmail gelöschte Nachrichten erkannt
- Der Nachrichtenabruf erfolgt per multithreaded Parallelverarbeitung, um die Leistung zu erhöhen
- Die Fehlerbehandlung umfasst automatische Wiederholungsversuche und exponentiellen Backoff
- Beim Drücken von CTRL+C wird ein geordneter Beendigungsablauf ausgeführt
- Das Annehmen neuer Aufgaben wird gestoppt
- Es wird gewartet, bis laufende Aufgaben abgeschlossen sind
- Der Fortschritt abgeschlossener Aufgaben wird gespeichert
- Das Programm wird regulär beendet
- Wenn CTRL+C ein weiteres Mal gedrückt wird, wird sofort beendet
Installation und Voraussetzungen
- Als Laufzeitumgebung wird Python 3.8 oder höher benötigt
- Erforderlich ist ein Google Cloud Project mit aktivierter Gmail API
- Die OAuth-2.0-Datei
credentials.json muss sich im Projektstamm befinden
- Der Installationsablauf besteht darin, das Repository zu klonen und anschließend die Abhängigkeiten mit
uv sync zu installieren
- Für die Einrichtung der Gmail-API-Authentifizierung wird in der Google Cloud Console ein Projekt erstellt oder ausgewählt, die Gmail API aktiviert und anschließend OAuth-2.0-Anmeldedaten für eine Desktop application erzeugt und als
credentials.json gespeichert
Verwendung der Befehle
- Die standardmäßige inkrementelle Synchronisierung wird wie folgt ausgeführt
python main.py sync --data-dir ./data
# or: uv run main.py sync --data-dir ./data
- Für Vollsynchronisierung und Löschungserkennung wird
--full-sync verwendet
python main.py sync --data-dir ./data --full-sync
- Um nur eine bestimmte Nachricht zu synchronisieren, werden
sync-message und --message-id verwendet
python main.py sync-message --data-dir ./data --message-id MESSAGE_ID
- Um nur gelöschte Nachrichten zu erkennen und zu markieren, wird
sync-deleted-messages verwendet
python main.py sync-deleted-messages --data-dir ./data
- Die Anzahl der Worker-Threads kann mit
--workers festgelegt werden; der Standardwert ist die Anzahl der CPU-Kerne
python main.py sync --data-dir ./data --workers 8
- Die Kommandozeilenargumente sind wie folgt
command: erforderlich; eines von sync, sync-message oder sync-deleted-messages
--data-dir: erforderlich; Verzeichnis, in dem die SQLite-Datenbank gespeichert wird
--full-sync: optional; erzwingt eine Vollsynchronisierung
--message-id: bei sync-message erforderlich; die konkrete Nachrichten-ID, die synchronisiert werden soll
--workers: optional; Anzahl der Worker-Threads
--help: zeigt Hilfe zu Befehlen und Optionen an
SQLite-Schema und Analysebeispiele
- Die
messages-Tabelle der erzeugten SQLite-Datenbank enthält die für die Analyse von Gmail-Nachrichten nötigen Felder
message_id: eindeutige Gmail-Nachrichten-ID
thread_id: Gmail-Thread-ID
sender: JSON-Absenderinformationen mit Name und E-Mail
recipients: Empfänger-JSON nach den Typen to, cc, bcc
labels: Array von Gmail-Labels
subject: Betreff der Nachricht
body: Nur-Text-Nachrichteninhalt
size: Nachrichtengröße in Byte
timestamp: Zeitpunkt der Nachricht
is_read: Gelesen-Status
is_outgoing: ob die Nachricht vom Nutzer gesendet wurde
is_deleted: ob die Nachricht in Gmail gelöscht wurde
last_indexed: Zeitpunkt der letzten Synchronisierung
- Die Anzahl der E-Mails pro Absender lässt sich aggregieren
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
GROUP BY sender->>'$.email'
ORDER BY count DESC
- Ungelesene E-Mails lassen sich pro Absender aggregieren, um Absender zu identifizieren, die viele uninteressante E-Mails senden
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
WHERE is_read = 0
GROUP BY sender->>'$.email'
ORDER BY count DESC
- Mit
strftime lässt sich die Anzahl der E-Mails nach Jahr, Monat, Tag, Wochentag oder Stunde aggregieren
SELECT strftime('%Y', timestamp) AS period, COUNT(*) AS count
FROM messages
GROUP BY period
ORDER BY count DESC
- E-Mails, deren Inhalt
newsletter oder unsubscribe enthält, können gefunden und Newsletter nach Absender gruppiert werden
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
WHERE body LIKE '%newsletter%' OR body LIKE '%unsubscribe%'
GROUP BY sender->>'$.email'
ORDER BY count DESC
- Die gesamte E-Mail-Größe pro Absender und große E-Mail-Absender lassen sich in MB anzeigen
SELECT sender->>'$.email', sum(size)/1024/1024 AS size
FROM messages
GROUP BY sender->>'$.email'
ORDER BY size DESC
- Die Anzahl der an sich selbst gesendeten E-Mails kann über das
recipients-JSON und die sender-E-Mail-Bedingung berechnet werden
SELECT count(*)
FROM messages
WHERE EXISTS (
SELECT 1
FROM json_each(messages.recipients->'$.to')
WHERE json_extract(value, '$.email') = 'foo@example.com'
)
AND sender->>'$.email' = 'foo@example.com'
- Bei empfangenen E-Mails lässt sich die Gesamtkapazität pro Absender in absteigender Reihenfolge prüfen
SELECT sender->>'$.email', sum(size)/1024/1024 as total_size
FROM messages
WHERE is_outgoing=false
GROUP BY sender->>'$.email'
ORDER BY total_size DESC
- Gelöschte Nachrichten werden mit der Bedingung
is_deleted=1 abgefragt
SELECT message_id, subject, timestamp
FROM messages
WHERE is_deleted=1
ORDER BY timestamp DESC
1 Kommentare
Meinungen auf Hacker News
Ich frage mich, warum bestimmte Header im Schema separat herausgezogen wurden. Man könnte
recipients,subjectundsenderals JSON-Felder halten, aber auch alles in einem einzigenheaders-Feld ablegen und sogar die übrigen Header der Nachricht darin speichern.Wenn es um Performance geht, kann man
headersals einzelnen JSON-Blob belassen und die benötigten Felder als generierte Spalten anlegen.subjectließe sich zum Beispiel mitjson_extract("headers", '$.Subject')erzeugen und indizieren.Dieses Modell war mächtig, weil Nutzer per
ALTER TABLEindizierte generierte Spalten hinzufügen konnten, die sie für ihre eigenen Abfragen brauchen. Auch der DKIM-Status lässt sich ermitteln, indem man"Dkim-Signature"extrahiert, daraus eine Spalte samt Index macht und dannGROUP BYverwendet.CREATE INDEX subjectidx ON messages(json_extract(headers, '$.Subject'))anlegen; dann wird der Index an Stellen genutzt, die auf diesen Ausdruck verweisen.Nachdem man einen solchen Index erstellt hat, war es hilfreicher, eine
VIEWzu erstellen, die diesen Ausdruck verwendet, statt die Haupttabelle perALTERum eine generierte Spalte zu erweitern.Normalerweise bevorzuge ich es, Spalten, die dauerhaft genutzt werden, separat herauszuziehen. Bei etwas so stabilem wie E-Mail-Headern gilt das umso mehr; eine
headers-Spalte kann Schemaänderungen zwar etwas vereinfachen, verlagert aber den Schmerz vom Schreibzeitpunkt auf den Lesezeitpunkt und lässt Raum für stillschweigende Fehler.Nach etwa zwei Monaten, wenn man sieht, welche Felder tatsächlich benötigt werden, füllt man sie aus dem JSON heraus, sorgt dafür, dass die API sie aktuell hält, oder erstellt Views. Das hat ziemlich geholfen, die Wachstumsschmerzen von „packen wir einfach alles in MongoDB“ oder „legen wir es einfach im Dateisystem ab“ zu vermeiden, und die Kosten waren nicht hoch.
dkimist alsNOT NULLdefiniert; ich frage mich, was passiert, wenn eine E-Mail-Nachricht keinenDkim-Signature-Header hat.Vor ein paar Jahren habe ich ein Tool zur Visualisierung großer E-Mail-Mengen wie in Gmail gebaut: https://github.com/terhechte/postsack
Ich frage mich, ob es auch eine Größenoption gibt. Ich würde gern sehen, welche Absender den meisten Speicherplatz belegen. Außerdem ist das SSL-Zertifikat der Website abgelaufen.
Dagegen sind qdirstat-Cache-Dateien leicht zu erstellen und lassen sich nutzen, um verschiedene dateiähnliche Objekte zu visualisieren.
Wirklich schade ist, dass man sich inzwischen nicht einmal mehr mit app-spezifischen Passwörtern anmelden kann, sondern einen OAuth-Client erstellen und den OAuth-Flow durchlaufen muss. Es sind meine E-Mails, und trotzdem hat Google mir einen offenen Standard für den Zugriff darauf weggenommen.
Besonders erhalte ich immer häufiger Hinweise, dass meine Freelancer-E-Mails bei den Empfängersystemen im Spam landen. Allerdings weiß ich nicht, wie ich die Gewohnheiten loswerden soll, die sich durch das Google-Ökosystem eingeschliffen haben.
Kürzlich habe ich versucht, Gmail in meine App https://github.com/rumca-js/Django-link-archive zu integrieren, habe viel zu viel Zeit darauf verwendet und bin zu dem Schluss gekommen, dass Gmail-Support den Aufwand nicht wert ist.
Gmail to SQLite beschreibt die Einrichtung der Zugangsdaten in sechs Schritten, aber bei mir war es nicht so. Nach den sechs Schritten sagte Google immer noch, die App sei nicht veröffentlicht und müsse veröffentlicht werden; außerdem könne ich sie nicht als interne App belassen, weil ich kein Workspace-Nutzer sei, und nach der Umstellung auf eine externe App hieß es, sie sei vor der Verifizierung nicht nutzbar.
Im Verifizierungsprozess wurden eine Domain, eine Adresse, weitere Details, eine Begründung für die Scopes und sogar ein Video verlangt, das die Nutzung der App erklärt; außerdem hieß es, die Prüfung der eingereichten Daten werde Zeit brauchen. Das Ganze ist ein Konfigurationslabyrinth, und es ist einfach zu viel verlangt, Nutzer durch die von Google geforderten Hürden springen zu lassen.
Ich frage mich, was derzeit die beste Open-Source-Gmail-Backup-Software ist. Mich interessiert, ob jemand so etwas schon eingerichtet hat, einschließlich der Archivierung von Anhängen.
Zur Info: Es gibt auch https://www.mailstore.com/en/products/mailstore-home/. Das ist zwar nicht Open Source, aber als GUI mit Index gut für die lokale Mail-Suche; Resume funktioniert nur beim Backup, daher scheitern große Wiederherstellungen meist.
Ich lasse mich alle paar Monate daran erinnern, das auszuführen, und aktualisiere mein lokales Backup. Soweit ich mich erinnere, kommt es als gzip-komprimierte mbox-Datei herunter.
Das sollte meiner Meinung nach nicht „Gmail to SQLite“, sondern eher „IMAP to SQLite“ heißen. Ich verstehe nicht, warum man es an einen bestimmten E-Mail-Anbieter bindet.
IMAP ist deutlich schwieriger und viel langsamer und hängt außerdem an Googles Bandbreitenlimits.
Keine Ahnung, ob sie in einem zu kalten Storage lag und deshalb ein Timeout auftrat. Daher kann ich nachvollziehen, dass die Nutzung von Googles proprietärer API besser funktionieren kann.
Heutzutage enthält Google Takeout mbox-Dateien, funktioniert korrekt und ist ziemlich schnell, aber es bietet keine kontinuierlichen Updates. Am Ende bin ich zu einem anderen Mail-Anbieter, Infomaniak, umgezogen und war meinem früheren Ich dankbar, dass ich meine Mail-Domain verwendet hatte.
Es wäre schön, wenn man auch Volltextsuche aktivieren könnte.
Ich habe gestern dasselbe gebaut, weil ich die Empfänger-E-Mails nach Domain auflisten wollte. Der Code ist ein Chaos, aber hier ist er: https://github.com/hugoferreira/gmail-sqlite-db
Das erinnert mich ein wenig an Archiveopteryx, einen PostgreSQL-basierten IMAP-Server: https://github.com/aox/aox
Das Schema von AOX sah für mich immer gut aus, aber ich habe es nie wirklich produktiv genutzt. Der Hauptanwendungsfall war Mail-Analyse und Suche, nicht ein IMAP-Server als Daily Driver.
Ich frage mich, wie hier die Bandbreitenkosten aussehen. Als jemand mit einem Gmail-Konto von über 40 GB würde ich gern wissen, ob mir für die Übertragung mit diesem Tool Gebühren berechnet werden.
Das lässt sich leicht beheben. Google Takeout scheint kostenlos zu sein, also kann man es zuerst herunterladen und dann die Datei parsen. Aus Sicht eines sofortigen Starts dürfte dieses Tool aber trotzdem schneller sein.