自分のGmailに届く注文通知を毎日集計したい、開発中のサービスが送った確認メールを自動でチェックしたい。こうした用途でGmailをプログラムから読むとき、IMAPとアプリパスワードの組み合わせよりも筋が良いのがGmail APIです。権限を「読み取りだけ」に絞れて、Gmailの検索演算子もそのまま使えます。
一方で、最初につまずきやすいのはコードよりも認証まわりです。Google Cloud側の設定を1つ飛ばすだけで「access_denied」や「invalid_grant」が返ってきます。この記事では、自分のアカウントのメールを読む前提で、プロジェクト作成から本文のデコードまでを順番に組み立てます。
この記事でわかること
- Gmail APIを呼ぶ前にGoogle Cloudで用意するもの
- gmail.readonly スコープでの初回認証とtoken.jsonの再利用
- 検索クエリで絞り込み、本文をbase64urlからデコードする方法
- 「テスト」状態のアプリでトークンが切れる理由と対処
全体像:3つの部品で動く
Gmail APIでメールを読むプログラムは、おおまかに次の3つの部品でできています。
- OAuthクライアント(credentials.json):Google Cloudで発行する、アプリの身分証のようなファイル
- ユーザーのトークン(token.json):初回にブラウザで許可したあと保存される、アクセストークンとリフレッシュトークン
- APIの呼び出し:
users.messages.listでIDの一覧を取り、users.messages.getで1通ずつ中身を取る
このうちcredentials.jsonは「どのアプリか」、token.jsonは「誰がそのアプリに何を許可したか」を表します。後者はパスワードと同じくらい大切な情報なので、置き場所には気を配ってください。
Google Cloud 側の準備
Google Cloudコンソールでの作業です。2026年10月時点の画面では、OAuth関連の設定は「Google Auth Platform」の下にまとまっています。表記が変わっていても、やることは同じです。
- 新しいプロジェクトを作成する(既存のものでも可)
- 「APIとサービス」のライブラリから Gmail API を有効にする
- OAuth同意画面(ブランディング)でアプリ名とサポート用メールを設定する
- 対象ユーザー(Audience)を「外部」にし、テストユーザーに自分のGmailアドレスを追加する
- クライアントを作成し、種類は「デスクトップ アプリ」を選ぶ
- 作成したクライアントのJSONをダウンロードし、
credentials.jsonとしてスクリプトと同じ場所に置く
4番目を忘れると、ブラウザで許可しようとした段階で「アクセスをブロック」の画面になります。個人のGmailで試す場合は、ほぼこの設定漏れが原因です。
gmail.readonly はGoogleが「制限付きスコープ」に分類している権限です。自分や限られたテストユーザーだけが使う分には審査は不要ですが、不特定多数に公開するアプリにする場合は、Googleの確認手続き(場合によってはセキュリティ評価)が求められます。詳しくはGoogle Cloud のヘルプを確認してください。
スコープの選び方
スコープは必要最小限が基本です。読むだけなら書き込み系は選びません。
| スコープ | できること | 向いている用途 |
|---|---|---|
| gmail.metadata | 件名・差出人などのヘッダーとラベル。本文は読めない | 件数集計、差出人の傾向分析 |
| gmail.readonly | メールと設定の読み取り | 本文の解析、確認メールのチェック |
| gmail.modify | 既読化・ラベル変更なども可能(完全削除は不可) | 処理済みメールに印をつけたい場合 |
スコープはURL形式で指定します。たとえば読み取り専用なら https://www.googleapis.com/auth/gmail.readonly です。あとからスコープを変えた場合は、古いtoken.jsonを削除して認証をやり直してください。保存済みのトークンには古いスコープしか含まれていないためです。
メール一覧を取得するコード
使うライブラリは公式のクライアントと認証ヘルパーです。
pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
次のコードは、初回はブラウザを開いて許可を求め、以降はtoken.jsonを読み込んで期限切れなら自動更新します。
from pathlib import Path
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
TOKEN = Path("token.json")
def get_credentials() -> Credentials:
creds = None
if TOKEN.exists():
creds = Credentials.from_authorized_user_file(str(TOKEN), SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES)
creds = flow.run_local_server(port=0)
TOKEN.write_text(creds.to_json(), encoding="utf-8")
return creds
def main() -> None:
service = build("gmail", "v1", credentials=get_credentials())
res = service.users().messages().list(
userId="me", q="newer_than:1d -in:spam", maxResults=20
).execute()
for item in res.get("messages", []):
msg = service.users().messages().get(
userId="me", id=item["id"], format="metadata",
metadataHeaders=["From", "Subject", "Date"],
).execute()
headers = {h["name"]: h["value"] for h in msg["payload"]["headers"]}
print(headers.get("Date"), "|", headers.get("From"), "|", headers.get("Subject"))
if __name__ == "__main__":
main()
userId="me" は「認証したユーザー自身」を意味します。messages.list が返すのはIDとスレッドIDだけなので、件名や本文が欲しいときは messages.get を追加で呼ぶ必要があります。一覧で50件以上を扱う場合は、レスポンスの nextPageToken を次のリクエストの pageToken に渡してページをめくります。
検索クエリはGmailの検索窓と同じ
q にはGmailの検索演算子がそのまま使えます。from:no-reply@example.com、subject:注文、is:unread、after:2026/10/01 などを組み合わせれば、サーバー側で対象を絞れるので無駄な取得が減ります。
本文を取り出してデコードする
本文は format="full" で取得します。ここが一番の落とし穴で、本文は payload の中に入れ子のパーツとして格納され、しかもbase64urlでエンコードされています。HTMLメールとテキストメールが両方入った「multipart/alternative」も多いため、パーツを再帰的にたどります。
import base64
def find_text(part: dict, mime: str = "text/plain") -> str | None:
if part.get("mimeType") == mime and part.get("body", {}).get("data"):
raw = base64.urlsafe_b64decode(part["body"]["data"])
return raw.decode("utf-8", errors="replace")
for child in part.get("parts", []):
found = find_text(child, mime)
if found:
return found
return None
msg = service.users().messages().get(userId="me", id=msg_id, format="full").execute()
text = find_text(msg["payload"]) or find_text(msg["payload"], "text/html")
Gmail APIが返す data はパディング付きなので、通常は urlsafe_b64decode でそのまま復号できます。文字コードがUTF-8以外のメールもまれにあるため、厳密に扱うならパーツのヘッダーにある charset を見て切り替えてください。添付ファイルは body.attachmentId だけが入っているので、users.messages.attachments.get で別途取得します。
取り出した本文から確認コードだけを抜き出す方法は、メール本文から認証コードを正規表現で抽出する方法で詳しく扱っています。
よくあるエラーと原因
| access_denied(アクセスをブロック) | テストユーザーに自分のアドレスが追加されていない、または別のアカウントでログインしている |
|---|---|
| invalid_grant | リフレッシュトークンが失効している。パスワード変更、アクセス権の取り消し、テスト状態での有効期限切れなどが原因。token.jsonを消して再認証する |
| 403 insufficientPermissions | コードのスコープとtoken.jsonのスコープが一致していない |
| 403 accessNotConfigured | プロジェクトでGmail APIが有効になっていない |
| 429 / rateLimitExceeded | 短時間に呼びすぎ。間隔を空けて再試行し、一覧取得はqで絞る |
1週間でトークンが切れる問題
公開ステータスが「テスト」のまま外部ユーザー向けに設定したアプリでは、発行されたリフレッシュトークンが7日で失効します。毎週invalid_grantが出るなら、ほぼこれが原因です。自分専用のスクリプトなら定期的に再認証するか、アプリを「本番」に切り替えます。本番化すると制限付きスコープの確認手続きが関わってくるため、用途と照らして判断してください。Google Workspaceのアカウントで組織内だけに使うなら、対象ユーザーを「内部」にする選択肢もあります。
運用するときの注意点
- credentials.jsonとtoken.jsonはGitに含めない。
.gitignoreに追加しておく - サーバーで動かすなら、ファイルの権限を実行ユーザーだけに絞るか、シークレット管理サービスに置く
- 不要になったら、Googleアカウントの「サードパーティ製のアプリとサービス」からアクセス権を削除する
- 読み取ったメールの本文をログにそのまま出さない。個人情報や認証コードが含まれることがある
Googleアカウント側のアクセス権の見直しは、Googleセキュリティ診断のやり方の手順で定期的に行えます。OutlookのメールをAPIで読みたい場合は、Microsoft Graph APIでメールを取得する方法が対応する記事です。
よくある質問
サービスアカウントで個人のGmailを読めますか?
読めません。サービスアカウントによる代理アクセスは、Google Workspaceの管理者がドメイン全体の委任を設定した組織アカウントが対象です。個人の@gmail.comでは、この記事のようにユーザー自身がOAuthで許可する方式を使います。
サーバーにブラウザがないのですが、初回認証はどうすればよいですか?
手元のPCで一度認証してtoken.jsonを作り、それを安全な方法でサーバーに配置するのが手軽です。以前使われていた、コピーした認証コードを貼り付ける方式(OOBフロー)はGoogleがすでに廃止しています。
IMAPとGmail API、どちらを使うべきですか?
Gmailだけを対象にするならAPIの方が検索や権限の絞り込みがしやすく扱いやすいです。複数のメールサービスを同じコードで扱いたいなら、XOAUTH2を使ったIMAPが候補になります。
新着メールをリアルタイムで受け取れますか?
users.watch とCloud Pub/Subを組み合わせると、変更があったときに通知を受け取れます。小規模な用途なら、数分おきにqで新着を検索するポーリングでも十分なことが多いです。
まとめ
- Gmail APIの準備は「API有効化・同意画面・テストユーザー・デスクトップ用クライアント」の4点
- 読むだけなら gmail.readonly、本文が不要なら gmail.metadata まで絞れる
- 一覧はIDだけなので、messages.getで中身を取り、本文はbase64urlを再帰的にデコードする
- テスト状態のアプリではリフレッシュトークンが7日で切れる点に注意する
検証用に自分で管理できるGmailの受信箱を別に用意したいときは、Gmailアカウントのページも参考にしてください。



