【TypeScript】メールAPI 完全ガイド — Microsoft Graph API & Gmail API 2026年5月仕様変更あり

この記事では、Microsoft Graph APIGmail API 両APIを並べて比較しつつ内容をまとめてます

目次

Microsoft Graph API

Microsoft Graph APIではMicrosoftの様々なサービス(Teams、Outlook、Excelなど)のデータや機能を、外部のプログラムから操作ができます

そもそもMicrosoft Graph APIを使うには認証が必須

APIを使用するためには「どのアプリが、どの権限で、誰のデータにアクセスするのか」をMicrosoftに申告する必要がある

そのための場所が Azure Portal(https://portal.azure.com) です。

個人アカウント、組織アカウントの違い

Graph API 組織アカウント Microsoft 365 /users/{id}/messages 他人のメール 共有メールボックス Client Credentials アプリケーション権限 管理者同意が必要 テナントID = 組織固有 ほぼ全API利用可 個人アカウント outlook.com等 /me/messages 自分のメールのみ Authorization Code 委任権限のみ 管理者同意は不要 テナントID = consumers 一部API制限あり ブラウザログイン必須
項目組織アカウント(Microsoft 365)個人アカウント(outlook.com等)
エンドポイント/users/{userId}/messages/me/messages
認証方式アプリケーション権限(Client Credentials)が使える委任権限(Authorization Code)のみ
ユーザー操作不要(サーバー間で完結)ブラウザでログイン・許可が必要
管理者同意必要(Azure AD管理者が許可)不要(自分で許可するだけ)
他人のメールボックスアクセス可能(権限があれば)自分のメールボックスのみ
使えるAPIほぼ全て一部制限あり(共有メールボックス、メールフォルダの一部操作など)
テナントID組織固有の値consumers という固定値を使う
MSALの設定authorityに組織のテナントIDを指定authorityhttps://login.microsoftonline.com/consumers を指定

個人で開発環境を用意する

M365開発者プログラムに参加する方法もある

Microsoft 365の機能を使ったサービスを開発するときのテスト環境として利用ができる

開発者プログラムへの参加自体はできましたが、E5サブスクリプション(組織テナント)はもらえない状態でサンドボックスは取得できません。


Azure無料アカウント作成
https://signup.azure.com/signup

Azure無料アカウント作成 azure.microsoft.com/ja-jp/free 今ここ Azure Portalにログイン portal.azure.com アプリ登録(Entra ID) クライアントID・シークレットを取得 認証(トークン取得) ブラウザでログイン → アクセストークン Graph API(/me/messages)呼び出し メールの取得・送信・下書き作成など

https://azure.microsoft.com/ja-jp

検索バーで「Microsoft Entra ID」と検索
Entra IDにアプリを登録 → 「アプリの認証・認可を管理」

アプリ登録画面で

  • 名前graph-mail-test(任意)
  • サポートされているアカウントの種類任意の組織ディレクトリ内のアカウントと、個人用のMicrosoftアカウント」に変更してください。「シングルテナントのみ」のままだと個人アカウントでのログインができません
  • リダイレクトURI プラットフォームを「Web」にして、http://localhost:3000/auth/callback と入力

登録完了したら、概要画面に以下の情報が表示

  • アプリケーション(クライアント)ID
  • ディレクトリ(テナント)ID

APIのアクセス許可を設定する

  1. アプリ登録の概要画面の左メニューから「APIのアクセス許可」をクリック
  2. アクセス許可の追加」をクリック
  3. Microsoft Graph」を選択
  4. 委任されたアクセス許可」を選択
  5. 検索バーで以下を検索してチェックを入れる
    • Mail.Read
    • Mail.ReadWrite
    • Mail.Send
  6. アクセス許可の追加」をクリック

クライアントシークレットを作成する

  1. 左メニューから「証明書とシークレット」をクリック
  2. 新しいクライアントシークレット」をクリック
  3. 説明(例test-secret
  4. 有効期限「6か月」でOK(学習用なので短くて問題ない)
  5. 追加」をクリック

Graph APIのリソース階層

Graph APIにおける「リソース」とは、操作対象となるデータのこと。URL上で /users/, /messages/, /events/ のように表現される。

リソース
ユーザーの URL には、要求で操作するリソースが含まれます。たとえば、meusergroupdrivesite などです。 多くの場合、最上位のリソースには リレーションシップも含まれます。me/messages または me/drive のように、追加のリソースにアクセスするのに使用できます。
https://learn.microsoft.com/ja-jp/graph/use-the-api?source=recommendations#resource

エンティティ(Entity)と複合型(Complex Type)の違い

どちらもリソースの定義方法だが、決定的な違いは id プロパティの有無

  • エンティティ(Entity)id を持つ。単独で存在し、直接アドレス指定できる。
  • 複合型(Complex Type)id を持たない。エンティティの一部としてのみ存在し、単独でアドレス指定できない。

メール機能での具体例

種類id単独で取得可能か
エンティティmessage, user, mailFolderあり/me/messages/{id} で可能
複合型emailAddress, itemBody, recipientなし不可。親エンティティに含まれる

RDBで言えば、エンティティはテーブルの行(主キーあり)、複合型は構造体・値オブジェクト(主キーなし)に近い。

Microsoft Graph を使用すると、他のユーザーやグループ、グループ メンバーシップ、メール、予定表、ファイル、管理ロールなど、アクセスするリソースなど、ユーザーとその他のオブジェクトとの関係に基づいて魅力的なアプリ エクスペリエンスを構築できます。
https://learn.microsoft.com/ja-jp/graph/api/resources/users?view=graph-rest-1.0

メールはトップレベルリソースではなく、あくまでユーザーリソース

graph.microsoft.com/v1.0
  └── /me  または  /users/{id|UPN}     ← ユーザーリソース
        ├── /mailFolders                ← メールフォルダ
        │     └── /messages             ← フォルダ内のメッセージ
        ├── /messages                   ← 全メッセージ横断
        ├── /calendar                   ← カレンダー
        ├── /contacts                   ← 連絡先
        ├── /drive                      ← OneDrive
        ├── /teams                      ← Teams
        ├── /onenote                    ← OneNote
        └── ...
/me or /users/{id} /mailFolders /messages /calendar /contacts /drive

Graph APIのメールはフォルダ階層構造で管理されている

mailFolders を一覧表示する
https://learn.microsoft.com/ja-jp/graph/api/user-list-mailfolders?view=graph-rest-1.0&tabs=http

/me or /users/{id} /mailFolders /messages 全フォルダ横断 inbox drafts sentItems /messages /messages/{id} /createReply /send /attachments PATCH / DEL
/me/mailFolders/inbox/messages          — 受信トレイ
/me/mailFolders/drafts/messages         — 下書き
/me/mailFolders/sentitems/messages      — 送信済み
/me/mailFolders/deleteditems/messages   — ゴミ箱(削除済みアイテム)
/me/mailFolders/junkemail/messages      — 迷惑メール
/me/mailFolders/archive/messages        — アーカイブ
/me/mailFolders/outbox/messages         — 送信トレイ

共有メールボックス(Shared Mailbox)について

Microsoft 365 の共有メールボックスについて
https://learn.microsoft.com/ja-jp/microsoft-365/admin/email/about-shared-mailboxes?view=o365-worldwide

既読・未読は共有される

message リソースのプロパティで、メールボックス単位で1つの値しか持たない。

「誰が読んだか」をGraph APIだけで追跡するのは不可能。

Outlook(およびExchange Online)では、共有メールボックスと委任メールボックスは既定で既読/未読の状態を共有します。
https://learn.microsoft.com/en-us/answers/questions/4736926/read-unread-issue-in-same-mailbox

Microsoft Graph API のページネーション

基本の仕組み — @odata.nextLink

Microsoft Graph APIでは、レスポンスに次ページが存在する場合、@odata.nextLinkというプロパティに次ページの完全なURLが返ってくる。

クライアントはこのURLに対してそのままGETリクエストを送ることで、次のページを取得する。最終ページに到達すると@odata.nextLinkは含まれなくなる。

レスポンスの構造はこうなっている。

// レスポンス例
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#me/messages",
  "@odata.nextLink": "https://graph.microsoft.com/v1.0/me/messages?$skip=10",
  "value": [
    { "id": "AAMk...", "subject": "会議のお知らせ", ... },
    { "id": "AAMk...", "subject": "請求書送付", ... },
    // ...
  ]
}

$top1ページあたりの取得件数を指定するパラメータ

$topはメールの場合、デフォルトは10件で最大999件まで指定できる。

エンドポイントによって違う。統一されていない。

messagesの場合 — 1〜1000の範囲でカスタマイズ可能 Microsoft Learnという記述がある。SDKのページングドキュメントでは「999まで」と書かれている。

usersの場合$top=1000を指定するとエラーになり、1〜999の範囲でなければならないと明示されている Microsoft Learn

一般論 — APIによってデフォルトと最大ページサイズが異なる Microsoft Learn。一律の上限値はない。

// 1ページ50件でメールを取得
GET https://graph.microsoft.com/v1.0/me/messages?$top=50

Graph APIメール取得の注意事項をまとめ

$search$orderbyは併用できない 結果は送信日時順で返るが、明示的なソート指定は不可。 https://learn.microsoft.com/en-us/graph/search-query-parameter
https://learn.microsoft.com/en-us/answers/questions/656200/graph-api-to-filter-results-on-from-and-subject-an

$searchの結果は最大1,000件 https://learn.microsoft.com/en-us/graph/search-query-parameter

$filter$searchは併用できない https://learn.microsoft.com/en-us/graph/query-parameters

$filter$orderbyの併用にはプロパティ順序の制約がある $orderbyのプロパティは$filterにも同じ順序で先に含める必要がある。違反すると"The restriction or sort order is too complex for this operation."エラー。 https://learn.microsoft.com/en-us/graph/api/user-list-messages?view=graph-rest-1.0

toRecipients / ccRecipients / bccRecipients$filterできない fromは可。to/cc/bccで絞りたい場合は$searchを使うしかない。https://learn.microsoft.com/en-us/answers/questions/1665637/how-to-query-emails-by-recipients-email-address

Microsoft Graph API で $search クエリ パラメーターを使用する
https://learn.microsoft.com/ja-jp/graph/search-query-parameter?tabs=http

$filterでドメイン部分一致(endsWith)はメッセージのemailAddressで使えない containsは使えるがNOTとの組み合わせ不可。ドメイン除外は$filterでは実現困難。 https://learn.microsoft.com/en-us/answers/questions/1168280/office-365-mail-graph-api-)-is-there-a-way-to-filt

from/emailAddress/address eqが一部の送信者で正しく動かないケースがある startsWithなら動く場合がある。 https://learn.microsoft.com/en-us/answers/questions/2153305/when-filtering-messages-on-ms-graph-rest-api-using

Microsoft Graph API で $search クエリ パラメーターを使用する

$search="domain.com" のようにプロパティ指定なしで検索すると、from, subject, body のみが対象で、to や cc は含まれません。

https://learn.microsoft.com/ja-jp/graph/search-query-parameter?tabs=http

to/cc も含めて検索するには、ドキュメントの表にある通り

  • participants: — from, to, cc, bcc すべて
  • recipients: — to, cc, bcc(from を除く)

を明示的に指定する必要があります。

$search クエリの書き方について

下記の公式リファレンスが参考になります

キーワード照会言語 (KeyQL) 構文リファレンス
https://learn.microsoft.com/ja-jp/sharepoint/dev/general-development/keyword-query-language-kql-syntax-reference
– AND OR NOTなど
– 式の間に演算子が指定されていない自由形式の式が複数ある場合、クエリ動作は AND 演算子を使用した場合と同じになります。

Graph Explorerで$searchのNOT挙動を検証する手順。

1. Graph Explorerにアクセス

https://developer.microsoft.com/en-us/graph/graph-explorer

ブラウザで開く。

2. サインイン

左上の「Sign in to Graph Explorer」からMicrosoftアカウント(自分のテストアカウントか業務アカウント)でログイン。初回は権限の同意画面が出る。Mail.Read権限が必要なので、出てきたら同意する。権限が足りない場合は左ペインの「Modify permissions」タブからMail.Readを追加する。

3. ベースラインのクエリを試す

まず除外なしで、対象ドメインのメールが何件ヒットするかを確認しておく。GETメソッドで以下を実行。

https://graph.microsoft.com/v1.0/me/messages?$search="participants:example.com"&$select=id,subject,from,receivedDateTime&$top=10

example.comの部分は自分のメールボックスに実在するドメインに置き換える(テスト結果が0件だと検証にならないため)。レスポンスのvalue配列に何件か入っていることを確認する。

4. NOT付きクエリを試す

次に同じドメインを除外する形で実行。

https://graph.microsoft.com/v1.0/me/messages?$search="NOT participants:example.com"&$select=id,subject,from,receivedDateTime&$top=10

このときの結果を3パターンに分類して観察する。

conversations と messages の違い

Graph APIでメールのスレッドを取得したい場合、/me/messagesとは別に/me/mailFolders/{id}/messagesや、Outlook固有のconversationIdフィールドを利用する方法がある。

/me/messagesはメールボックス内の全メッセージをフラットに返す。スレッド単位でまとめたい場合は、取得したメッセージのconversationIdでグルーピングする。

Graph APIにはスレッド一覧を直接返すエンドポイントがないため、クライアント側でのグルーピングが必要になる。Gmail APIのthreadsエンドポイントとは対照的な設計だ。

Google API

Googleが提供する様々なサービス(Google Drive、Google Maps、YouTube、Googleカレンダー、Gmailなど)をプログラムから操作するためのAPI

Google Workspace と 個人Googleアカウント

項目Google Workspace個人Googleアカウント
主な対象企業、組織、学校個人
料金有料(1ユーザーごとの月額/年額)基本無料
メールアドレス独自ドメイン(例)name@yourcompany.com@gmail.com
アカウント管理管理者が一括管理(作成、削除、権限設定)個人で管理

API利用における違い

OAuth同意画面を設定するとき、User Typeの選択肢が2つあります。

  • 内部(Internal)
    Google Workspaceの組織内ユーザーだけがアプリを使える。Workspace契約がないとこの選択肢はそもそも表示されない。
  • 外部(External)
    個人アカウント含め誰でも使える。個人アカウントで開発する場合はこれ一択。

個人Googleアカウントで開発環境を用意する

  • Google Cloud Consoleでプロジェクトを作成する
  • Gmail APIを有効化する
    • 左メニュー「APIとサービス」→「ライブラリ」をクリック
    • 検索バーに「Gmail API」と入力
    • 「Gmail API」をクリック
    • 「有効にする」をクリック
  • OAuth同意画面を設定する
    • 左メニュー「APIとサービス」→「OAuth同意画面」をクリック
    • User Type「外部」を選択して「作成」
      • アプリ名適当でOK(例) gmail-test
      • ユーザーサポートメール 自分のGmailを選択
      • デベロッパーの連絡先メールアドレス 自分のGmailを入力
    • 「保存して次へ」
  • OAuthクライアントIDを作成する
    • アプリケーションの種類「ウェブアプリケーション」を選択
    • 名前適当でOK(例)gmail-test-client
    • 「承認済みのリダイレクトURI」の「URIを追加」をクリック
    • http://localhost:3000/api/auth/google/callback を入力
    • 「作成」をクリック
    • 作成後、クライアントIDクライアントシークレット が表示されるので、メモ
  • テストユーザー登録
    • 「APIとサービス」→「OAuth同意画面」

googleapis

googleapisはGoogleが公式提供しているNode.js向けクライアントライブラリで、google.gmail()はその中のGmail APIクライアント生成関数。

主要なメソッド階層

google.gmail()はクライアントインスタンスを返す関数で、引数にversionauthを渡す。versionは現状"v1"一択。authは認証済みのOAuth2Clientや、サービスアカウントの認証情報など。

返ってくるgmailオブジェクトに、Gmail APIの全エンドポイントがメソッドチェーンで生えている。

REST APIのパス構造(users.messages.listなど)がそのままメソッド名として表現されている。

userIdは必須で、"me"を渡すと認証中のユーザー自身を指す。

gmail.users.getProfile({ userId: "me" })
gmail.users.messages.list({ userId: "me", q: "...", maxResults: 100 })
gmail.users.messages.get({ userId: "me", id: "..." })
gmail.users.messages.send({ userId: "me", requestBody: { ... } })
gmail.users.messages.modify({ userId: "me", id: "...", requestBody: { ... } })
gmail.users.messages.trash({ userId: "me", id: "..." })
gmail.users.messages.delete({ userId: "me", id: "..." })
gmail.users.threads.list({ userId: "me", q: "..." })
gmail.users.threads.get({ userId: "me", id: "..." })
gmail.users.labels.list({ userId: "me" })
gmail.users.labels.create({ userId: "me", requestBody: { ... } })
gmail.users.history.list({ userId: "me", startHistoryId: "..." })
gmail.users.drafts.list({ userId: "me" })
gmail.users.settings.filters.list({ userId: "me" })
users.messages
┣ list ........... メッセージ一覧を取得(ID と threadId だけ)
┣ get ............ 1通の詳細を取得(format で粒度を指定)
┣ send ........... 送信
┣ insert ......... 既存メールを直接挿入(インポート用途)
┣ import ......... SPF等チェック付きでインポート
┣ trash .......... ゴミ箱へ
┣ untrash ........ ゴミ箱から戻す
┣ delete ......... 完全削除
┣ batchDelete .... 複数まとめて削除
┣ batchModify .... 複数のラベルをまとめて変更
�apply ┗ modify ........... ラベル付与/削除

gmail.users.messages.list()はGmailのメッセージ一覧を取得するメソッド

パラメータ

パラメータ説明
userIdstring(必須)ユーザーのメールアドレス。"me"で認証中ユーザー
qstringGmail検索構文によるフィルタクエリ
maxResultsnumber1ページあたりの最大件数(デフォルト100、最大500)
pageTokenstring次ページ取得用のトークン
labelIdsstring[]ラベルIDで絞り込み(複数指定はAND)
includeSpamTrashbooleanSPAMとTRASHを含めるか(デフォルトfalse)
list レスポンス
┣ messages[]
┃  ┣ {id, threadId}
┃  ┣ {id, threadId}
┃  ┗ ...
┣ nextPageToken ...... ページング用
┗ resultSizeEstimate

重要な点として、レスポンスに含まれるのはメッセージIDとスレッドIDだけ

件名や送信者などの内容は含まれない。詳細が必要ならmessages.getを別途呼ぶ必要がある。これがGraph APIの/me/messagesとの大きな違い。

listID しか返さないので、本文やヘッダーが欲しければ list → 各IDで get という2段構えになります。

Graph APIの「フォルダー」という概念、階層構造になっていて直感的でわかりやすいですよね。

一方、GoogleのGmail APIでは、フォルダーではなく「ラベル(Label)」という概念を採用しています。

Gmailの世界では、メールはシステム上に「1つ」だけ存在し、そこに複数の「ラベル(付箋のようなもの)」を付け外しすることで、どの画面(受信トレイ、送信済みなど)に表示するかを制御しています。

Graph APIのフォルダーとGmail APIのラベルの対応関係と、操作の違いについてまとめました。

システムラベルの対応表

Graph APIの主要なフォルダーは、Gmail APIではシステムが予約している「システムラベル(大文字)」として定義されています。

概念Graph API (フォルダー名)Gmail API (ラベルID)
受信トレイInboxINBOX
送信済みSentItemsSENT
下書きDraftsDRAFT
ゴミ箱DeletedItemsTRASH
迷惑メールJunkEmailSPAM
未読(プロパティで管理)UNREAD
重要/スター(プロパティで管理)STARRED, IMPORTANT

users.messages.get が返すのは 1通分の Message オブジェクト です。format パラメータで中身の詳しさが変わります。

format 別の返却内容

minimal
┣ id
┣ threadId
┣ labelIds[]
┣ snippet
┣ historyId
┣ internalDate
┗ sizeEstimate
   (payload なし/ヘッダーも本文もなし)

metadata
┣ minimal の全部
┗ payload
   ┣ mimeType
   ┣ headers[] ← ここが入る(metadataHeadersで絞れる)
   ┗ ※ body.data は入らない(本文は取れない)

full  ★デフォルト
┣ minimal の全部
┗ payload(MIMEツリー丸ごと)
   ┣ mimeType
   ┣ filename
   ┣ headers[]
   ┣ body
   ┃  ┣ data ........ Base64urlの本文
   ┃  ┗ attachmentId  添付の場合
   ┗ parts[] ........ 子パート(再帰)

raw
┣ minimal の全部
┗ raw ............... RFC822生データ(Base64url文字列1本)
   (payload は基本入らない/自分でMIMEパースする)

Googleグループについて

Google グループでは、スレッドの既読 / 未読ステータスはユーザーごとに個別に保存されます。
https://developers.google.com/workspace/admin/groups-migration/v1/guides/manage-email-migrations?hl=ja

Gmail API 基本の仕組み — pageToken と nextPageToken

Gmail APIでは、users.messages.listのレスポンスにnextPageTokenが含まれていれば次ページが存在する。

次のリクエストでpageTokenパラメータにこの値を渡す。Graph APIの@odata.nextLinkが完全なURLを返すのに対して、Gmail APIは不透明なトークン文字列だけを返す点が異なる。

// レスポンス例
{
  "messages": [
    { "id": "18f3a...", "threadId": "18f3a..." },
    { "id": "18f2b...", "threadId": "18f2b..." },
    // ...
  ],
  "nextPageToken": "09876543210987654321",
  "resultSizeEstimate": 342
}

q パラメータによる検索フィルタ

Gmail APIのqパラメータはGmailのWeb UIの検索バーと同じ構文が使える。メールのフィルタリングに強力な機能だ。よく使うパターンをいくつか紹介する。

// 送信者で絞り込み
const query1 = "from:notifications@example.com";

// 日付範囲で絞り込み
const query2 = "after:2025/01/01 before:2025/04/01";

// 特定ドメインからのメールを除外
const query3 = "-from:@spam-domain.com";

// 複数ドメインを除外
const query4 = "-from:@marketing.example.com -from:@newsletter.example.com";

// ラベルと未読を組み合わせ
const query5 = "label:work is:unread";

// 件名に特定のキーワードを含む
const query6 = "subject:請求書";

// 添付ファイル付き + 特定サイズ以上
const query7 = "has:attachment larger:5M";

// 組み合わせた実践的な例:
// 今年の未読メールで、社内ドメインを除外し、添付ファイル付き
const practicalQuery =
  "is:unread has:attachment after:2025/01/01 -from:@mycompany.com";

https://developers.google.com/workspace/gmail/api/guides/filtering?hl=ja

ドメイン除外は-from:@domain.comの形式で書く。ハイフン(-)がNOT演算子として機能する。複数ドメインを除外したい場合はスペース区切りで-from:を並べればよい。

このパラメータは、Gmail ウェブ インターフェースと同じ高度な検索構文のほとんどをサポートしています。
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.messages/list?hl=ja

注意点として、Gmail APIのqパラメータはWeb UIと完全に同じではない。公式ドキュメントにも記載されているが、エイリアスの展開がAPI側では行われないケースがある。Google Workspaceでアカウントのエイリアスを設定している場合、Web UIでは検索にヒットするがAPIでは返らないという差異が生じうる。

Gmail API のスレッド取得

Gmail APIにはスレッド単位で取得する専用エンドポイントusers.threads.listが用意されている。Graph APIのように手動でグルーピングする必要がない。

ページネーションの仕組みはmessages.listと同じで、nextPageToken / pageTokenのパターンで全スレッドを順次取得する。threads.getを呼ぶと、そのスレッドに属する全メッセージがmessages配列に含まれて返ってくる。

実装で気をつけるべきこと

レートリミット対策

Gmail API を使用する場合、1 つのメール メッセージあたりの受信者の上限は 500 人です。

https://developers.google.com/workspace/gmail/api/reference/quota?hl=ja

Gmail API には使用量上限があり、API のメソッドを呼び出すことができる頻度が制限されます。上限は割り当て ユニットで定義されます。割り当てユニットは、 Gmail リソースの使用量を表す抽象的な測定単位です。使用量上限には、プロジェクトごとのレート制限とユーザーごとのレート制限の 2 種類があり、これらが同時に適用されます。

受信者の最大合計数は 500 です。

https://learn.microsoft.com/ja-jp/graph/api/resources/message?view=graph-rest-1.0

Exchange Online メールボックスから送信される 1 つの電子メール メッセージのための toRecipientsccRecipients、および bccRecipients プロパティに含まれる受信者の最大合計数は 500 です。 詳細については、「送信の制限」を参照してください。

From、Toでメールアドレスは取れるけど名前部分が取れない場合

Gmail API

理由: Gmail APIはメールの中身をRFC 2822準拠の生ヘッダー文字列で返す。FromTo ヘッダーの形式は送信者のメールクライアントに依存し、"表示名" <address>address のみの両方がありえる。アドレスだけで送信された場合、名前部分は存在しない。Gmail API自体には名前を補完する仕組みがない。

ドキュメント/エビデンス

Microsoft Graph API

理由: Graph APIは from.emailAddress.namefrom.emailAddress.address を構造化データとして返す。nameString 型で「The display name of the person or entity」と定義されている Microsoft Learnが、required/optionalの明示がなく、実際に空文字やアドレスと同値で返ってくるケースがある。特に組織外の送信者でAzure AD/Exchangeのディレクトリに登録がない場合に起きる。

ドキュメント/エビデンス

Graph APIのドキュメントには「name が空になる場合がある」とは明記されていない。ただし型定義上 name は nullable な String であり、必須とも書かれていないので、空やアドレスと同値になる可能性は排除されていない。実際に実運用で確認できているなら、それ自体がエビデンスになる。

Gmail API で messages.list と messages.get を分離する理由

Gmail APIが一覧取得でIDしか返さない設計は、最初は不便に感じるかもしれない。しかしこの分離にはメリットがある。

一覧取得の段階ではレスポンスが非常に軽い。

GmailのthreadIdもMicrosoft Graph APIのconversationIdも、「同一スレッドに属するメッセージをグルーピングするID」という同じ意味的役割を持っている。共通の型にまとめるのは妥当。

値の形式が異なる。 GmailのthreadIdは18e5a...のような16進文字列、MicrosoftのconversationIdはAAQkAD...のようなBase64風文字列。共通型では string にしておけば問題ないが、バリデーション(max長やregex)をプロバイダごとに変える必要がある場合、共通型のレイヤーではバリデーションを持たせず、Zodスキーマ側ですのが自然か?

https://developers.google.com/gmail/api/reference/rest/v1/users.messages

https://developers.google.com/workspace/gmail/api/guides/threads

https://learn.microsoft.com/en-us/graph/api/resources/message?view=graph-rest-1.0

https://learn.microsoft.com/en-us/graph/api/message-get?view=graph-rest-1.0

統一可  変換して統一  個別管理

識別子

概念 Gmail MS Graph 判定
メッセージID id : string
16進 例: “18e5a3b…”
id : string
Base64風 例: “AAMkAG…”
統一可
スレッドID threadId : string
先頭メッセージのidと同値
conversationId : string
自動生成・不変
統一可

宛先情報

概念 Gmail MS Graph 判定
From payload.headers[]
RFC2822文字列をパース要
from.emailAddress
{ name, address } オブジェクト
個別
To / CC / BCC payload.headers[]
カンマ区切り文字列をパース要
toRecipients[]
{ emailAddress: { name, address } }[]
個別

本文・件名

概念 Gmail MS Graph 判定
件名 payload.headers[]
name:”Subject” の値
subject : string 変換統一
本文 payload.parts[] 再帰構造
base64urlデコード要
body.content : string
デコード済み
個別
プレビュー snippet : string bodyPreview : string 統一可

日時

概念 Gmail MS Graph 判定
受信日時 internalDate : string
epoch ms
receivedDateTime : string
ISO 8601
変換統一
送信日時 payload.headers[]
name:”Date” の値
sentDateTime : string
ISO 8601
変換統一

メッセージ状態

概念 Gmail MS Graph 判定
既読 labelIds[]
“UNREAD”の有無で判定
isRead : boolean 変換統一
下書き labelIds[]
“DRAFT”の有無で判定
isDraft : boolean 変換統一
ラベル/カテゴリ labelIds : string[]
INBOX, SENT, STARRED 等
categories : string[]
ユーザー定義カテゴリ
個別

添付ファイル・ページネーション

概念 Gmail MS Graph 判定
添付有無 payload.parts[]
filenameの有無で判定
hasAttachments : boolean 変換統一
ページネーション nextPageToken : string
pageTokenパラメータに渡す
@odata.nextLink : string
完全URL、そのままGET
統一可

プロバイダ固有フィールド

API フィールド 説明
Gmail固有 sizeEstimate RFC822全体のバイト数概算
raw RFC2822全文 (base64url, format=RAW時)
historyId 変更履歴の同期ポイント
MS Graph固有 importance “low” | “normal” | “high”
webLink Outlook Web上のメールURL
parentFolderId 所属フォルダID
flag フォローアップフラグ

参照
Gmail API – users.messages / Gmail API – threads / MS Graph – message resource

Gmail API vs Microsoft Graph API ― メッセージ構造比較
============================================================

■ エビデンス
├── Gmail    : https://developers.google.com/gmail/api/reference/rest/v1/users.messages
├── Gmail    : https://developers.google.com/workspace/gmail/api/guides/threads
├── MS Graph : https://learn.microsoft.com/en-us/graph/api/resources/message?view=graph-rest-1.0
└── MS Graph : https://learn.microsoft.com/en-us/graph/api/message-get?view=graph-rest-1.0


■ メッセージ全体構造
│
├── Gmail API (users.messages)
│   ├── id                : string (16進, 例: "18e5a3b...")
│   ├── threadId          : string (16進, スレッドの先頭メッセージIDと同値)
│   ├── labelIds          : string[]
│   ├── snippet           : string
│   ├── historyId         : string
│   ├── internalDate      : string (epoch ms, 例: "1704067200000")
│   ├── sizeEstimate      : number
│   ├── raw               : string (RFC2822 base64url, format=RAW時のみ)
│   └── payload           : MessagePart (後述)
│
└── Microsoft Graph API (message resource)
    ├── id                : string (Base64風, 例: "AAMkAG...")
    ├── conversationId    : string (Base64風, 例: "AAQkAD...")
    ├── conversationIndex : string (binary)
    ├── subject           : string
    ├── bodyPreview       : string
    ├── body              : { contentType: string, content: string }
    ├── from              : { emailAddress: { name: string, address: string } }
    ├── toRecipients      : { emailAddress: { name, address } }[]
    ├── ccRecipients      : { emailAddress: { name, address } }[]
    ├── bccRecipients     : { emailAddress: { name, address } }[]
    ├── receivedDateTime  : string (ISO 8601, 例: "2026-01-01T00:00:00Z")
    ├── sentDateTime      : string (ISO 8601)
    ├── createdDateTime   : string (ISO 8601)
    ├── lastModifiedDateTime : string (ISO 8601)
    ├── isDraft           : boolean
    ├── isRead            : boolean
    ├── hasAttachments    : boolean
    ├── importance        : string ("low" | "normal" | "high")
    ├── categories        : string[]
    ├── webLink           : string
    ├── parentFolderId    : string
    └── flag              : { flagStatus: string }


■ スレッドID
│
├── Gmail
│   ├── フィールド名 : threadId
│   ├── 型           : string (16進)
│   ├── 意味         : 同一会話のメッセージをグルーピング
│   └── 特徴         : スレッド先頭メッセージのidと同一値
│                      ユーザーごとに固有(送信者と受信者で異なる)
│
└── MS Graph
    ├── フィールド名 : conversationId
    ├── 型           : string (Base64風)
    ├── 意味         : 同一会話のメッセージをグルーピング
    └── 特徴         : 自動生成・不変
                       ユーザーごとに固有

    → 役割は同一。共通型では threadId: string に統一可能


■ 宛先情報の取得方法
│
├── Gmail
│   └── payload.headers[] から文字列パース
│       ├── { name: "From",  value: "John Doe <john@example.com>" }
│       ├── { name: "To",    value: "alice@example.com, Bob <bob@example.com>" }
│       └── { name: "Cc",    value: "..." }
│       → RFC 2822 形式の文字列をMIMEパーサーで分解する必要あり
│
└── MS Graph
    └── トップレベルにオブジェクト構造で提供
        ├── from:           { emailAddress: { name: "John Doe", address: "john@example.com" } }
        ├── toRecipients:   [{ emailAddress: { name, address } }, ...]
        └── ccRecipients:   [{ emailAddress: { name, address } }, ...]
        → パース不要、そのまま使える


■ 本文の取得方法
│
├── Gmail
│   └── payload (MessagePart) の再帰構造
│       ├── mimeType  : string (例: "multipart/alternative", "text/plain", "text/html")
│       ├── body      : { data: string(base64url), size: number }
│       ├── parts     : MessagePart[] (再帰)
│       └── 取得手順:
│           1. payload.parts を再帰的に走査
│           2. mimeType が "text/plain" or "text/html" の part を探す
│           3. body.data を base64url デコード
│
└── MS Graph
    └── トップレベルにフラット構造で提供
        ├── body.contentType : string ("text" | "html")
        ├── body.content     : string (デコード済み本文)
        └── bodyPreview      : string (プレビュー)
        → デコード不要、そのまま使える


■ 日時
│
├── Gmail
│   └── internalDate : string (epoch ms)
│       例: "1704067200000"
│       → new Date(Number(internalDate)).toISOString() で変換
│
└── MS Graph
    └── receivedDateTime : string (ISO 8601)
        例: "2026-01-01T00:00:00Z"
        → そのまま使える


■ 既読・下書き状態
│
├── Gmail
│   └── labelIds で判定
│       ├── "UNREAD" が含まれる → 未読
│       ├── "DRAFT"  が含まれる → 下書き
│       └── 専用フィールドなし
│
└── MS Graph
    └── 専用booleanフィールド
        ├── isRead  : boolean
        └── isDraft : boolean


■ 添付ファイル
│
├── Gmail
│   └── payload.parts[] 内で判定
│       ├── filename が空でない part → 添付ファイル
│       └── body.attachmentId → messages.attachments.get で取得
│
└── MS Graph
    └── 専用フィールド + 別エンドポイント
        ├── hasAttachments : boolean
        └── /messages/{id}/attachments で一覧取得


■ ページネーション
│
├── Gmail
│   ├── レスポンス : nextPageToken: string
│   └── リクエスト : pageToken パラメータに渡す
│
└── MS Graph
    ├── レスポンス : @odata.nextLink: string (完全URL)
    └── リクエスト : そのURLをそのままGETする

Microsoft Graph の @odata.nextLink vs Google の nextPageToken

観点Microsoft GraphGoogle API
位置づけOData プロトコルのアノテーション(@ 付きメタ情報)レスポンス本体のフィールド
中身完全な URLトークン文字列のみ
使い方URL をそのまま GET元のクエリに pageToken= を付与して再送
終端判定プロパティが消えたら終了同左(ただし空で返ることもある)
差分取得@odata.deltaLink で統一historyId + history API で別建て

API制限

Microsoft Graph API(メール)

メールボックスあたりの制限
https://learn.microsoft.com/ja-jp/graph/throttling-limits#outlook-service-limits

「ユーザー」はここではアクセス先のメールボックスの所有者を指す。ログインしているユーザー(操作者)ではない。

つまり、アプリAがメールボックスBにアクセスする場合、10分10,000リクエストの制限はメールボックスBに対してカウントされる。同じアプリAがメールボックスCにもアクセスしていれば、Cには別枠で10,000リクエストが使える。

Gmail API (2026年5月以降の新仕様)

Gmail API の割り当て
https://developers.google.com/workspace/gmail/api/reference/quota?hl=ja

2026年5月以降の新規プロジェクトは新仕様

以前は下記記事の通り毎秒単位の制限

http://xn--unipile-jv0lp571c.com/read-email-api/
レート制限とバックオフ
Gmailは、ユーザー1人あたり1秒あたり250クォータ単位(リスト表示に5単位、データ取得に5単位)の制限を設けています。Microsoft Graphは、テナント1つあたりアプリ1つにつき10分間に10,000リクエストに制限されています。どちらも、ジッターを伴う指数バックオフを必要とする429エラーを返します。リンクされたアカウントが1,000個になると、レート制限の管理は本格的なエンジニアリング問題となります。

https://stackoverflow.com/questions/52542789/check-specific-user-account-quota-usage-for-gmail-api
ユーザーごとのレート制限:1ユーザーあたり1秒あたり250クォータユニット(移動平均、短時間のバーストは許容)

messages.sendこのメソッドは100クォータユニットを消費します

messages.getこのメソッドは5クォータ単位を消費します

messages.listこのメソッドは5クォータ単位を消費します

messages.attachments.getこのメソッドは5クォータ単位を消費します

比較内容以前2026年5月以降
ユーザーごとの制限250 units / 6,000 units /
プロジェクトごとの制限1日あたり 1,000,000,000(10億) units1,200,000 units / 分
80,000,000(8,000万) units / 日
messages.send100100
messages.get520
messages.list55
messages.attachments.get520

たとえば1回のAPI呼び出しでリストを取得し、そのリストに含まれるメールをすべて get で取得する場合

以前(旧仕様)「1秒間に最大49件」
まず list を1回実行で残り 245 units(250 – 5)残り枠で get(1件5 units)を実行で 245 ÷ 5 = 49 件

2026年5月以降(新仕様)「1分間に最大299件」
まず list を1回実行で残り 5,995 units(6,000 – 5)残り枠で get(1件20 units)を実行で 5,995 ÷ 20 = 299.75 件

目次