届いた確認コードを自動で読み取りたい。毎朝の注文通知を集計したい。こうした目的で 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月時点の画面では、次の手順になります。
- Microsoft Entra 管理センター(または Azure ポータル)で「アプリの登録」→「新規登録」を開く
- 「サポートされているアカウントの種類」で、個人用 Microsoft アカウントを含む選択肢を選ぶ
- 登録後、「概要」に表示される「アプリケーション (クライアント) ID」を控える
- 「認証」で「パブリック クライアント フローを許可する」をオンにする(デバイスコード認証に必要)
- 「API のアクセス許可」で Microsoft Graph の委任されたアクセス許可
Mail.Readを追加する
client_id と refresh_token の関係は、Outlook の refresh_token と client_id とはで詳しく説明しています。ライブラリは次の2つだけで足ります。
pip install msal requests
受信メールを取得する 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 にはクエリが含まれている
既存の 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対応)から選べます。



