GoogleグループのスレッドとGmail APIのスレッドは別物である、Gmailの返信の定義

Gmail APIでメール機能を実装しているとき、Googleグループが絡むと「あれ?」と思う場面が出てくる。GoogleグループのウェブUIで見えているスレッドと、Gmail APIの threads.get で取得したスレッドの中身が一致しない。メッセージの件数が違う。過去のやり取りが取れない。

この違和感の正体は、両者が参照しているデータソースの違いにある。ここを正確に把握しないまま実装を進めると、メッセージの欠損やスレッドの不整合といった厄介なバグに繋がる。

目次

そもそもGoogleグループとはGoogleが提供するメーリングリスト・フォーラムサービスです。

主な用途は3つあります。

  • メーリングリストとしての利用で、1つのメールアドレス(例: team@googlegroups.com)に送ると、グループ全員にメールが届きます。Google Workspaceでは独自ドメインのグループアドレスも作れます。
  • アクセス権管理で、Google Drive、Googleカレンダーなどの共有設定にグループを指定すると、メンバーの追加・削除だけで権限を一括管理できます。個別にユーザーを追加する手間が省けます。
  • ウェブフォーラムとしての利用で、ブラウザ上でスレッド形式の議論ができます。ただし、この用途では現在SlackやTeamsに置き換えられていることが多いです。

GoogleグループのウェブUIが見ているもの

GoogleグループのウェブUI(groups.google.com)は、グループ全体のアーカイブを表示している。

Googleグループのアーカイブとは

Googleグループには、グループアドレス宛てに投稿されたメッセージを蓄積する仕組みがある。これが「アーカイブ」と呼ばれるもので、Gmailの「アーカイブ(受信トレイから非表示にする操作)」とはまったく別の概念

グループのアーカイブは、個人のメールボックスとは独立した場所にグループ単位で保持されている。

グループのウェブUI(groups.google.com)で過去の会話を遡って閲覧できるのは、このアーカイブがデータソースになっているからだ。グループの設定で「アーカイブを有効にする」がオンになっていれば、メンバーの参加時期に関係なく、グループに投稿された全履歴を閲覧できる。

ただし、このアーカイブのメッセージをAPIで直接読み出す手段は用意されていない。関連するAPIとしてGoogle Groups Settings APIとGoogle Groups Migration APIがあるが、前者はグループの設定操作用、後者はメッセージのインポート(書き込み)専用であり、いずれもアーカイブの中身を取得する機能を持っていない。

Gmail APIが見ているもの

一方、Gmail APIはまったく別のデータソースを参照している。Gmail APIは「特定ユーザーのメールボックス」に対して動作するAPIであり、Googleグループのアーカイブに直接アクセスする手段を持っていない。

Googleグループは独立したメールボックスを持たない。グループアドレス宛てに届いたメールを、参加メンバーそれぞれのGmailに配送するメーリングリストとして機能している。Gmail APIの threads.listthreads.get が返すデータは、そのユーザーのGmailに実際に届いたメッセージを、Gmail側のアルゴリズムが threadId で束ねた結果だ。

メール送信者がGoogleグループ宛てに投稿 group@example.com 宛てメール Googleグループ グループのアーカイブ 全メッセージを蓄積 各メンバーのGmail 配送されたメールのみ ウェブUI groups.google.com Gmail API threads.get / messages.get データソースが異なる

この構造の違いが、実装時に様々なギャップを生む原因になっている。

両者の違いを整理する

具体的にどのような差異があるのか、表にまとめた。

GoogleグループのウェブUIGmail API
データソースグループ全体のアーカイブ個人のメールボックス
過去ロググループ設定次第で参加前の投稿も閲覧可能そのアカウントに配送されたメールのみ取得可能
スレッドの単位グループ側の会話単位Gmailが threadId でグルーピングしたもの
網羅性グループ宛ての全投稿ユーザーのメールボックスに届いた分だけ

たとえば、GoogleグループのウェブUIで10件のメッセージが見えるスレッドがあったとする。途中からグループに参加したユーザーがGmail APIで同じスレッドを引くと、参加後に届いた3件しか取れない。これはバグではなく、データソースの違いから生じる正常な挙動だ。

Gmail APIがスレッドを組み立てる仕組み

Gmail APIが複数のメッセージを同一の threadId にまとめる基準は、メールのRFCヘッダーに依存している。関係するヘッダーは3つある。

  • Message-ID … 各メールに付与される一意のID
  • In-Reply-To … 返信先メールの Message-ID
  • References … 会話中の Message-ID 履歴

新規メールがグループに投稿されると、各メンバーのGmailに配送された時点で新しい threadId が生成される。そのメールに対して誰かが返信すると、返信メールの In-Reply-ToReferences に元のメールの Message-ID がセットされる。Gmailはこれを解析して、既存の threadId に返信メールをマージする。

スレッド構成の流れ 1. Aさんが新規投稿 Message-ID: <msg-001@a.com> → Gmail側で threadId: t-100 が生成 2. グループが全メンバーに配送 各メンバーのGmailに届く 各自のメールボックスで threadId 付与 3. Bさんが返信 In-Reply-To: <msg-001@a.com> References: <msg-001@a.com> → 既存の threadId: t-100 にマージ Gmail API のレスポンス threads.get(id=”t-100″) messages[0]: Aさんの投稿 Message-ID: <msg-001@a.com> messages[1]: Bさんの返信 In-Reply-To: <msg-001@a.com> ヘッダーの参照関係により、別々のメールが1つのスレッドに束ねられる

この仕組み自体はGoogleグループに限った話ではなく、通常のメールの返信でも同じロジックが使われている。ただし、Googleグループが間に入ることで、配送タイミングやメンバーの参加時期によってスレッドの中身に差が出る、という点が実装上の落とし穴になる。

過去ログが取得できない問題への対処

Gmail APIで最も頻繁に問題になるのが、ユーザーがGoogleグループに参加する前のメールを取得できないケースだ。

GoogleグループのウェブUIでは見えるのに、Gmail APIでは取れない。この現象を初めて経験すると「APIのバグでは?」と思いがちだが、前述のとおりデータソースが異なるので当然の挙動となる。

過去ログも含めた完全な同期が要件にある場合、Gmail API単体では実現不可能だ。代替手段として以下のAPIが候補になる。

  • Google Workspace Admin SDK … 管理者権限でワークスペース全体のデータにアクセスできる
  • Google Groups Migration API … グループのメッセージデータを移行・取得する用途で設計されたAPI

いずれもGmail APIとは権限モデルが異なるため、アーキテクチャの設計段階で検討しておく必要がある。「あとからGmail APIに過去ログ取得機能を足す」という対応は構造上できない。

自分が送信したメールがスレッドから抜け落ちる問題

もう一つ実装時にハマりやすいのが、自分がGoogleグループ宛てに送信したメールの扱いだ。

Googleグループの設定には「自分がグループ宛てに送ったメールを自分には再配送しない」というオプションがある。この設定が有効な場合、送信したメールは送信済みトレイには入るが、受信トレイには届かない。

messages.list で受信メールだけを対象にスキャンしていると、自分が開始したスレッドの1通目を取りこぼすことになる。スレッドの全体像を正しく構築するには、受信メールと送信メールの両方をスキャンして threadId ベースでマージする必要がある。

具体的には、q パラメータを以下のように組み合わせる。

# グループ宛てメールの取得(受信分)
q = "list:group@example.com"

# 自分が送信した分も含める場合
q = "list:group@example.com OR (to:group@example.com from:me)"

あるいは、label:SENT を別途スキャンして threadId でマージする方法もある。どちらの方法を取るかはアプリの要件次第だが、受信メールだけの監視では不十分であるという認識は最初から持っておくべきだ。

Googleグループ経由のメールを識別する方法

Gmail APIで取得したメッセージがGoogleグループ経由で届いたものかどうかは、メールヘッダーから判別できる。messages.get のレスポンスに含まれる payload.headers の中で、以下のヘッダーの有無を確認する。

  • List-Id … グループの識別子が格納されている(例 group-name.googlegroups.com
  • Mailing-List … 配信リストの情報

グループ単位でメッセージをフィルタリングしたい場合は、messages.listq パラメータに list:group-address@domain.com を指定するのが最も効率的だ。ヘッダーを個別にパースする手間が省ける。

実装に入る前に確認しておくこと

Gmail APIとGoogleグループの組み合わせで機能を実装する際は、コードを書き始める前に以下を確認しておくと手戻りが少ない。

まず、実装中のアプリケーションで、グループ宛てに届いている適当なメッセージを messages.get で1件取得してみること。レスポンスの payload.headers から List-IdIn-Reply-To がどう入っているかをデバッガーやコンソールで確認するのが、仕様を体感する最も早い方法だ。

次に、取得したメッセージの threadId を使って threads.get を呼び出し、1つのスレッド内に「誰の」「どの発言が」「どういう順番で」messages 配列に格納されているかを観察する。この構造が掴めれば、あとは過去ログの制限や送信メールの取りこぼしといった既知の落とし穴を意識しながら設計を進めるだけだ。

Gmailは返信の認識をどのようにしている?

正直に言うと、公式ドキュメントで「この3つを設定すれば返信として認識される」と明記した1ページは存在しないと思います。根拠は複数のソースにまたがっています

  • threadId → Gmail API 公式ドキュメントの Threads に「drafts.create で threadId を指定するとそのスレッドに追加される」と記載がある
  • In-Reply-To / References → これは Gmail 固有ではなく RFC 2822(メールの標準仕様) のヘッダー。メーラーが返信ツリーを構築するための標準的な仕組み

  • Gmail のスレッドに入れる → threadId(Gmail API の仕様)
  • メーラー上で返信として表示させる → In-Reply-To / References(RFC 2822 の仕様)

参考リンク

目次