本文へスキップ

Microsoft Graph API でメール取得|Python サンプル付き

Python スクリプトが msal でトークンを取得し、Microsoft Graph の /me/messages からメール一覧を受け取る構成図

届いた確認コードを自動で読み取りたい。毎朝の注文通知を集計したい。こうした目的で Outlook のメールをプログラムから扱うなら、今の第一候補は Microsoft Graph API です。IMAP と違ってメールの検索や絞り込みを HTTP の API でそのまま書けるため、スクリプトとの相性が良いのが特長です。

ここでは、個人用 Microsoft アカウント(@outlook.com・@hotmail.com など)を対象に、Python で受信メールの一覧を取得するまでを最小限のコードで組み立てます。業務用アカウントでも、認証先(authority)を変えれば流れはほぼ同じです。

この記事でわかること

  • Graph API でメールを読むために必要な準備
  • msal を使ったデバイスコード認証と、トークンキャッシュの保存
  • /me/messages の取得・絞り込み・ページング
  • 既存の refresh_token を使う方法と、トークンを守るための注意点

事前準備:アプリ登録と権限

Graph API を呼び出すには、自分のアプリを Microsoft Entra ID に登録して client_id を用意します。2026年10月時点の画面では、次の手順になります。

  1. Microsoft Entra 管理センター(または Azure ポータル)で「アプリの登録」→「新規登録」を開く
  2. 「サポートされているアカウントの種類」で、個人用 Microsoft アカウントを含む選択肢を選ぶ
  3. 登録後、「概要」に表示される「アプリケーション (クライアント) ID」を控える
  4. 「認証」で「パブリック クライアント フローを許可する」をオンにする(デバイスコード認証に必要)
  5. 「API のアクセス許可」で Microsoft Graph の委任されたアクセス許可 Mail.Read を追加する

client_id と refresh_token の関係は、Outlook の refresh_token と client_id とはで詳しく説明しています。ライブラリは次の2つだけで足ります。

pip install msal requests
Python スクリプトが msal でサインイン先からトークンを受け取り、Graph API の /me/messages を呼び出してメール一覧の JSON を受け取る流れ
msal がトークンの取得と更新を担当し、requests で Graph API を呼び出します。

受信メールを取得する Python コード

初回はデバイスコード認証でサインインします。2回目以降は、ファイルに保存したトークンキャッシュから自動的にトークンを更新します。

import os
import msal
import requests

CLIENT_ID = os.environ["MS_CLIENT_ID"]          # アプリの client_id
AUTHORITY = "https://login.microsoftonline.com/consumers"  # 個人用アカウント
SCOPES = ["Mail.Read"]                          # offline_access は msal が自動で付与
CACHE_FILE = "token_cache.bin"

cache = msal.SerializableTokenCache()
if os.path.exists(CACHE_FILE):
    with open(CACHE_FILE, encoding="utf-8") as f:
        cache.deserialize(f.read())

app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY, token_cache=cache)

result = None
accounts = app.get_accounts()
if accounts:
    result = app.acquire_token_silent(SCOPES, account=accounts[0])

if not result:
    flow = app.initiate_device_flow(scopes=SCOPES)
    if "user_code" not in flow:
        raise SystemExit(flow.get("error_description", "device flow failed"))
    print(flow["message"])          # 表示された URL とコードでサインイン
    result = app.acquire_token_by_device_flow(flow)

if "access_token" not in result:
    raise SystemExit(result.get("error_description", "token error"))

if cache.has_state_changed:
    with open(CACHE_FILE, "w", encoding="utf-8") as f:
        f.write(cache.serialize())
    os.chmod(CACHE_FILE, 0o600)

headers = {"Authorization": f"Bearer {result['access_token']}"}
params = {
    "$top": "10",
    "$select": "subject,from,receivedDateTime,isRead",
    "$orderby": "receivedDateTime desc",
}
r = requests.get("https://graph.microsoft.com/v1.0/me/messages",
                 headers=headers, params=params, timeout=30)
r.raise_for_status()

for m in r.json()["value"]:
    sender = m.get("from", {}).get("emailAddress", {}).get("address", "-")
    print(m["receivedDateTime"], sender, m["subject"])

ポイントは3つあります。offline_access は msal が自動で付けるため、スコープに書かないこと。トークンキャッシュを保存して、毎回のサインインを避けること。そして $select で必要な項目だけを取得し、通信量を抑えることです。

絞り込み・本文の取得・ページング

未読だけ、特定の差出人だけを取得する

$filter を使うと、条件に合うメールだけを取得できます。

# 未読メールだけ
params = {"$filter": "isRead eq false", "$top": "25",
          "$select": "subject,from,receivedDateTime"}

# 特定の差出人から
params = {"$filter": "from/emailAddress/address eq 'no-reply@example.com'",
          "$top": "25"}

$filter と $orderby を組み合わせると、Graph が「InefficientFilter」というエラーを返すことがあります。並べ替えに使うプロパティを、フィルターの条件にも含める必要があるためです。迷ったら、まずフィルターだけで取得し、並べ替えは Python 側で行うのが確実です。

本文を取得する

本文は $select=body で取れます。既定では HTML 形式です。リクエストヘッダーに Prefer: outlook.body-content-type="text" を付けるとテキスト形式で返るので、確認コードの抽出などに便利です。

ページングに対応する

結果が多い場合、レスポンスに @odata.nextLink が含まれます。この URL をそのまま次のリクエストに使えば、続きを取得できます。

url = "https://graph.microsoft.com/v1.0/me/messages"
while url:
    res = requests.get(url, headers=headers, params=params, timeout=30).json()
    for m in res["value"]:
        handle(m)
    url = res.get("@odata.nextLink")
    params = None   # nextLink にはクエリが含まれている
1ページ目の value 配列と @odata.nextLink をたどって、2ページ目、3ページ目を順に取得していくページングの図
@odata.nextLink がなくなるまでたどれば、全件を取得できます。

既存の refresh_token を使う場合

別の手段で取得済みの refresh_token と client_id がある場合は、msal の acquire_token_by_refresh_token でアクセストークンに交換できます。交換後はキャッシュに新しいトークンが入るので、以降は acquire_token_silent で更新できます。

app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY, token_cache=cache)
result = app.acquire_token_by_refresh_token(os.environ["MS_REFRESH_TOKEN"], scopes=SCOPES)

この方法は、refresh_token が発行されたときと同じ client_id で、かつ Mail.Read に同意済みの場合にだけ成功します。IMAP 用のスコープにしか同意していないトークンでは Graph のトークンを取得できないので、その場合はOutlook IMAP OAuth2 接続方法の手順で IMAP から読み取ります。

トークンを守るための注意点

アクセストークン・refresh_token・トークンキャッシュのファイルは、パスワードと同じくらい重要な情報です。漏れると、第三者にメールボックスを読まれるおそれがあります。

  • client_id や refresh_token はコードに直接書かず、環境変数やシークレット管理サービスから読み込む
  • トークンキャッシュのファイルは、所有者だけが読めるように権限を絞る(上のコードの chmod 0o600)
  • Git リポジトリに含めないよう、.gitignore に追加する
  • 例外やログにトークンが出力されていないか確認する
  • 権限は必要最小限にする(読むだけなら Mail.Read、送信が不要なら Mail.Send は付けない)

また、短時間に大量のリクエストを送ると、Graph は HTTP 429 を返して一時的に制限をかけます。レスポンスの Retry-After ヘッダーの秒数だけ待ってから再試行してください。詳しい仕様は Microsoft の公式ドキュメント「メッセージを一覧表示する」にまとまっています。

よくある質問

IMAP と Graph API のどちらを使うべきですか?

新しく作るなら Graph API がおすすめです。検索や絞り込みを API で簡潔に書けて、JSON で扱えます。既存のメールソフトや IMAP 前提の仕組みと連携する場合は IMAP を選びます。

サーバーで動かすので、デバイスコード認証ができません。

初回のサインインだけ手元の PC で行い、作成されたトークンキャッシュを安全な方法でサーバーに配置する方法があります。以降は acquire_token_silent で更新されます。

AADSTS65001 というエラーが出ます。

Mail.Read への同意が得られていない状態です。アプリ登録で権限を追加したうえで、もう一度サインインして同意画面で許可してください。

メールを既読にしたり削除したりできますか?

できます。ただし Mail.ReadWrite の権限が必要です。読むだけの用途なら、権限は Mail.Read に留めておくほうが安全です。

まとめ

  • アプリ登録で client_id を取得し、Mail.Read の委任アクセス許可を付ける
  • msal のデバイスコード認証とトークンキャッシュで、2回目以降は自動更新
  • /me/messages は $select・$filter・@odata.nextLink で効率よく取得する
  • トークンとキャッシュは環境変数・権限設定・.gitignore で確実に守る

Graph での読み取りに対応した Outlook アカウントは、Hotmail・Outlook(OAuth2対応)から選べます。

KeyCenter編集部

デジタルアカウントの安全な使い方や設定手順を、実際の画面の流れにそってまとめています。内容は公開時点の情報です。各サービスの最新の仕様は公式ヘルプもあわせてご確認ください。

関連する商品

一覧を見る

あわせて読みたい

記事一覧