- Gmail to SQLiteは、GmailのメッセージをローカルのSQLiteデータベースに同期し、分析と保存に活用するためのPythonアプリケーション
- 基本動作は新しいメッセージだけを取得する増分同期で、フル同期オプションによりすべてのメッセージを取得し、削除の有無も検出できる
- メッセージ取得にはマルチスレッド並列処理を使用し、指数バックオフに基づく自動リトライやCTRL+C処理など、エラー・終了時の対応を含む
- 実行にはPython 3.8以上、Gmail APIが有効化されたGoogle Cloud Project、OAuth 2.0の
credentials.jsonファイルが必要
- 保存されたデータには送信者、受信者、ラベル、本文、サイズ、既読状態、送信済みかどうか、削除済みかどうかなどが含まれ、SQLでGmailの利用パターンを直接分析できる
Gmailメッセージのローカル同期ツール
- Gmail to SQLiteは、GmailのメッセージをローカルのSQLiteデータベースに保存するPythonアプリケーション
- 目的はGmailデータを分析し、保存できるようにすること
- コードベース全体に型ヒントを適用し、型安全性を備えている
同期方式と安定性
- 基本同期は増分同期として動作し、新しいメッセージだけをダウンロードする
--full-syncオプションを使うと、全メッセージを同期し、Gmailで削除されたメッセージを検出する
- メッセージ取得はマルチスレッド並列処理で実行し、性能を高める
- エラー処理には自動リトライと指数バックオフが含まれる
- CTRL+Cを押すと、グレースフルな終了手順が実行される
- 新しいタスクの受け付けを停止する
- 実行中のタスクが終わるのを待つ
- 完了したタスクの進行状態を保存する
- 正常終了する
- CTRL+Cをもう一度押すと、即時終了する
インストールと前提条件
- 実行環境にはPython 3.8以上が必要
- Gmail APIが有効化されたGoogle Cloud Projectが必要
- OAuth 2.0認証ファイル
credentials.jsonがプロジェクトルートに存在する必要がある
- インストール手順は、リポジトリをクローンした後、
uv syncで依存関係をインストールする方式
- Gmail APIの認証設定は、Google Cloud Consoleでプロジェクトを作成または選択し、Gmail APIを有効化したうえで、Desktop application用のOAuth 2.0認証情報を作成して
credentials.jsonとして保存する
コマンドの使い方
python main.py sync --data-dir ./data
# or: uv run main.py sync --data-dir ./data
- フル同期と削除検出には
--full-syncを使用する
python main.py sync --data-dir ./data --full-sync
- 特定のメッセージだけを同期するには、
sync-messageと--message-idを使用する
python main.py sync-message --data-dir ./data --message-id MESSAGE_ID
- 削除されたメッセージだけを検出して表示するには、
sync-deleted-messagesを使用する
python main.py sync-deleted-messages --data-dir ./data
- ワーカースレッド数は
--workersで指定でき、デフォルト値はCPUコア数
python main.py sync --data-dir ./data --workers 8
- コマンドライン引数は次のとおり
command: 必須で、sync、sync-message、sync-deleted-messagesのいずれか
--data-dir: 必須で、SQLiteデータベースが保存されるディレクトリ
--full-sync: 任意で、フル同期を強制する
--message-id: sync-messageで必須で、同期する特定メッセージのID
--workers: 任意で、ワーカースレッド数
--help: コマンドとオプションのヘルプを表示
SQLiteスキーマと分析例
- 生成されるSQLiteデータベースの
messagesテーブルには、Gmailメッセージ分析に必要なフィールドが含まれる
message_id: 一意のGmailメッセージID
thread_id: GmailスレッドID
sender: 名前とメールアドレスを含むJSON形式の送信者情報
recipients: to、cc、bccの種類別の受信者JSON
labels: Gmailラベルの配列
subject: メッセージ件名
body: プレーンテキストのメッセージ本文
size: バイト単位のメッセージサイズ
timestamp: メッセージ時刻
is_read: 既読状態
is_outgoing: ユーザーが送信したメッセージかどうか
is_deleted: Gmailで削除されたメッセージかどうか
last_indexed: 最終同期時刻
- 送信者別のメール数を集計できる
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
GROUP BY sender->>'$.email'
ORDER BY count DESC
- 未読メールを送信者別に集計し、興味のないメールを多く送ってくる送信者を確認できる
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
WHERE is_read = 0
GROUP BY sender->>'$.email'
ORDER BY count DESC
strftimeを使い、年、月、日、曜日、時間単位でメール数を集計できる
SELECT strftime('%Y', timestamp) AS period, COUNT(*) AS count
FROM messages
GROUP BY period
ORDER BY count DESC
- 本文に
newsletterまたはunsubscribeが含まれるメールを探し、ニュースレターを送信者別にまとめられる
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
WHERE body LIKE '%newsletter%' OR body LIKE '%unsubscribe%'
GROUP BY sender->>'$.email'
ORDER BY count DESC
- 送信者別のメール総サイズと、大きなメールの送信者をMB単位で確認できる
SELECT sender->>'$.email', sum(size)/1024/1024 AS size
FROM messages
GROUP BY sender->>'$.email'
ORDER BY size DESC
- 自分宛てに送ったメール数を、
recipients JSONとsenderのメールアドレス条件で計算できる
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'
- 受信したメールのうち、送信者別の総容量が大きい順に確認できる
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
- 削除済みメッセージは
is_deleted=1条件で照会する
SELECT message_id, subject, timestamp
FROM messages
WHERE is_deleted=1
ORDER BY timestamp DESC
1件のコメント
Hacker News のコメント
スキーマで特定のヘッダーを別に切り出した理由が気になる。
recipients、subject、senderを JSON フィールドにすることもできるが、headers1つに全部入れて、メッセージの残りのヘッダーまで含めることもできる性能のためなら、
headersを単一の JSON blob にして、必要なフィールドは生成カラムにすればよい。たとえばsubjectはjson_extract("headers", '$.Subject')で作り、インデックスを張れるこのモデルは、ユーザーが自分のクエリに必要なインデックス付き生成カラムを
ALTER TABLEで追加できるので強力だった。DKIM の状態も"Dkim-Signature"を取り出してカラムとインデックスを作り、その後GROUP BYすればよいCREATE INDEX subjectidx ON messages(json_extract(headers, '$.Subject'))のように作れば、その式を参照する箇所でインデックスが使われるこうしてインデックスを作ったうえで、メインテーブルを
ALTERして生成カラムを追加するより、その式を使うVIEWを作るほうが有用だったたいてい、継続的に使うことになるカラムは別に切り出すほうを好む。メールヘッダーのように安定した対象ならなおさらで、
headersカラムはスキーマ変更を少し楽にできても、書き込み時の苦痛を読み取り時の苦痛に置き換えるだけで、静かに失敗する余地も残す2か月ほど経って実際に必要なフィールドが見えてきたら、JSON から埋め込み、API が常に最新に保つようにしたり、ビューを作ったりする。「とりあえず全部 MongoDB に入れよう」や「とりあえずファイルシステムに置こう」から来る成長痛を避けるのにかなり役立ったし、コストも大きくなかった
dkimカラムをNOT NULLと定義しているが、メールメッセージにDkim-Signatureヘッダーがなかったらどうなるのか気になる数年前に Gmail のような大量メール可視化ツールを作った: https://github.com/terhechte/postsack
サイズのオプションもあるのか気になる。どの送信者が自分のストレージを最も多く使っているのか見たい。それと、Web サイトの SSL 証明書が期限切れになっている
一方で qdirstat のキャッシュファイルは作りやすいので、ファイルのように見えるさまざまな対象を可視化するのに使える
今ではアプリ専用パスワードでもログインできず、OAuth クライアントを作って OAuth フローを通らなければならないのが本当に残念だ。自分のメールなのに、Google が自分がアクセスするための公開標準を取り上げてしまった
特に、自分のフリーランス用メールが受信者側のシステムでスパムに入るという情報をますます多く受け取っている。ただ、Google エコシステムに慣れた習慣をどう捨てるかは途方に暮れる
最近、自分のアプリ https://github.com/rumca-js/Django-link-archive に Gmail を統合しようとしたが、時間を使いすぎ、Gmail 対応は価値がないと判断した
Gmail to SQLite は認証情報の設定を6ステップで説明しているが、自分の場合はそうではなかった。6ステップの後でも、Google はアプリが公開されていないと言って公開を求め、Workspace ユーザーではないので内部アプリにはできないと言い、外部アプリに変えると検証前には使えないと言った
検証プロセスでは、ドメイン、住所、その他の詳細情報、スコープに対する正当化、アプリの使い方を説明する動画まで要求され、提出したデータの検証に時間がかかると言われた。全体が設定の迷路のようで、ユーザーに Google が要求する障害物を越えさせるのはあまりに過剰だ
現時点で存在する最高のオープンソースの Gmailバックアップソフトウェア は何なのか気になる。添付ファイルの保存まで含めて、こういう構成を作ったことがある人がいるのか知りたい
参考までに https://www.mailstore.com/en/products/mailstore-home/ もある。オープンソースではないが、インデックス付きのGUIなのでローカルメール検索に向いており、再開はバックアップでしか使えないため、大容量の復元はたいてい失敗する
数か月ごとにこれを実行するようリマインダーを設定し、ローカルバックアップを更新している。記憶では、gzip圧縮されたmboxファイルとしてダウンロードされる
これは「Gmail to SQLite」ではなく「IMAP to SQLite」のような名前であるべきではないかと思う。なぜ特定のメールプロバイダー1つに結び付けるのか分からない
IMAPははるかに難しく、はるかに遅く、Googleの帯域幅制限にも縛られる
あまりにもコールドなストレージにあってタイムアウトしたのかどうかは分からない。だから、Googleの独自APIを使うほうがうまく動く可能性があるというのは理解できる
最近はGoogle Takeoutにmboxが含まれていて、きちんと動作し、かなり速いが、継続的な更新はできない。結局、別のメールプロバイダーであるInfomaniakに移行し、以前に自分の メールドメイン を使っておいた自分に感謝した
全文検索 も有効にできるとよさそう
昨日、自分も同じものを作った。受信者メールを ドメイン別に一覧表示 したかったからだ。コードはひどいが、ここにある: https://github.com/hugoferreira/gmail-sqlite-db
PostgreSQLベースのIMAPサーバーArchiveopteryxを少し思い出す: https://github.com/aox/aox
AOXのスキーマはいつも良さそうに見えたが、実際にきちんと使えたことはない。主な用途はデイリードライバーのIMAPサーバーではなく、メール分析と検索だった
ここで帯域幅コストがどうなるのか気になる。40GB以上のGmailアカウント を持っている立場として、このツールで転送すると課金されるのか知りたい
修正は簡単だ。Google Takeoutは無料のようなので、まずダウンロードしてファイルをパースすればいい。それでも、すぐに始めるという観点では、このツールのほうが速そうだ