本文へスキップ

Gmail APIでメール取得するPython入門|OAuth認証と本文デコード

PythonスクリプトがOAuth同意画面で許可を得てtoken.jsonを保存し、Gmail APIのmessages.listとmessages.getでメールを取得する流れ

自分の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は「誰がそのアプリに何を許可したか」を表します。後者はパスワードと同じくらい大切な情報なので、置き場所には気を配ってください。

credentials.jsonを使ってブラウザの同意画面で許可し、token.jsonを保存したあと、messages.listとmessages.getでメールを取得する流れ
初回だけブラウザで許可し、2回目以降はtoken.jsonから自動でトークンを更新します。

Google Cloud 側の準備

Google Cloudコンソールでの作業です。2026年10月時点の画面では、OAuth関連の設定は「Google Auth Platform」の下にまとまっています。表記が変わっていても、やることは同じです。

  1. 新しいプロジェクトを作成する(既存のものでも可)
  2. 「APIとサービス」のライブラリから Gmail API を有効にする
  3. OAuth同意画面(ブランディング)でアプリ名とサポート用メールを設定する
  4. 対象ユーザー(Audience)を「外部」にし、テストユーザーに自分のGmailアドレスを追加する
  5. クライアントを作成し、種類は「デスクトップ アプリ」を選ぶ
  6. 作成したクライアントの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 で別途取得します。

取り出した本文から確認コードだけを抜き出す方法は、メール本文から認証コードを正規表現で抽出する方法で詳しく扱っています。

Gmail APIのメッセージがpayloadの中にmultipart/alternativeとしてtext/plainとtext/htmlのパーツを持ち、それぞれbase64urlでエンコードされている構造
本文はパーツの入れ子になっているため、mimeTypeを見ながら再帰的に探します。

よくあるエラーと原因

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アカウントのページも参考にしてください。

KeyCenter編集部

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

関連する商品

一覧を見る

あわせて読みたい

記事一覧