メインコンテンツへスキップ
kt-tech.blog

【実装】メールリンクの 30 日失効と戦う — 決済ポータルから領収書 PDF の URL を組み立てる(確定申告自動化 第4回)

実装7分で読めます

この記事でわかること

  • 領収書メールの PDF リンクが失効する条件と、失効しても HTTP 200 が返るための見分け方
  • カスタマーポータルの請求リンクから PDF の URL を組み立てる変換規則
  • 取得後に必要な2段階の検証(型と中身)と、「月初に回す」という運用が逆算される理由

対象の SaaS が決済代行のカスタマーポータルを使っていること(領収書メールの送信元で判別できます)/ file コマンドと Python の pypdf / 自分が支払っているサービスの領収書を自分で取得する用途

概要: SaaS の領収書を機械的に集めようとすると、最初にぶつかるのが「メールの PDF リンクが失効している」問題です。決済代行のカスタマーポータルを使っているサービスなら、請求一覧のリンクから PDF の URL を機械的に組み立てられます。これを知っているかどうかで、領収書収集の自動化難易度が大きく変わります。

はじめに

領収書収集を自動化するとき、一番楽なのはメールに PDF が直接添付されているケースです。しかし現実には、多くの SaaS は本文にリンクを置くだけです。

そしてそのリンクは、しばらくすると失効します。ページを開くと「この URL は期限切れです」という短いメッセージが返るだけになります。

面倒なのは、失効しても HTTP 200 が返ることです。ダウンロードは成功し、ファイルもできます。中身がエラー文字列だけというだけです。これを検証なしで回すと、中身がテキストの「領収書」が会計ソフトに何十件も上がることになります。

本記事では、この問題の回避策として確立した「ポータルから URL を組み立てる」手法を書きます。


1. リンクはいつ失効するのか

実測した結果、領収書メールに入っている PDF リンクは 発行から約 30 日で失効しました。

これが意味するのは、「年末にまとめて 1 年分集める」という運用は成立しないということです。1 月分のメールは 12 月にはとっくに使えなくなっています。

さらに困るのが、失効のシグナルが弱いことです。取得コマンドの終了コードは 0、ファイルも生成されます。成功しているように見えます。しかし型を見るとこうなっています。

Bash
file receipt.pdf
# => receipt.pdf: ASCII text     ← PDF ではない

だからfile コマンドによる検証を必須にする。これが 1 つ目の教訓です。

2. ポータルから URL を組み立てる

では過去分はどうするか。ここで使えるのが、カスタマーポータルに並ぶ請求リンクから、PDF の URL を機械的に導けるという性質です。

多くの SaaS の請求履歴ページには、各行に「表示」リンクが並んでいます。そのリンクは次の形をしています。

表示用(ブラウザで開く):
  invoice.代行ドメイン/i/{アカウントID}/{トークン}

PDF 直リンク(こちらを組み立てる):
  pay.代行ドメイン/invoice/{アカウントID}/{トークン}/pdf

変換規則は 2 つだけです。

変換内容 before after
ホストとパスの先頭 invoice サブドメイン + /i/ pay サブドメイン + /invoice/
末尾 (なし) /pdf を追加

重要なのは、ポータルが発行するリンクは新鮮だということです。メールのリンクは送信時点で固定されているので古びますが、ポータルを今開けば今のトークンが得られます。

つまり、運用はこうなります。

  • 直近 1 ヶ月分 → メールから URL を抽出して取得(早い)
  • 過去分 → ポータルを開いてリンクを取り、変換して取得

どちらも最終的には HTTP クライアントで直接取得するので、ブラウザのダウンロード機能を使わずに済みます。これは大きな利点です(理由は次回の記事で書きます)。

3. メール本文から URL を抽出する

直近分はメールから拾うのが早いです。ただし、メール本文の取得には実務的な問題が 1 つあります。

HTML メールの本文はとにかく大きい。領収書メール 1 通で 5 万文字を超えることもあります。これをそのまま LLM のコンテキストに入れるのは無駄です。

対策は「ファイルに落として、欲しいものだけ抜く」ことです。本文全体を読まず、決済代行のドメインにマッチするリンクだけを拾います。

このとき一緒にやっておくと良いのが、金額と日付の抽出です。あとでファイル名を付けるときに必要になりますし、間違った月のメールを拾っていないかの確認にも使えます。HTML 本文は標準ライブラリの HTML パーサでタグを落としてから、金額表記や支払日の文字列を探します。

4. 取得後の検証は 2 段階でやる

取得したら、必ず 2 段階で検証します。どちらか一方だけでは不十分です。

第 1 段階: それは本当に PDF か。 失効リンク対策です。

Bash
file receipts/2026/07/20260726_918_サブスク.pdf
# => PDF document, version 1.4, 1 pages   ← OK

第 2 段階: 中身は目的のものか。 別の月・別の金額の領収書を拾っていないかの確認です。

Python
from pypdf import PdfReader

text = PdfReader("20260726_918_サブスク.pdf").pages[0].extract_text()
assert "918" in text
assert "July 26, 2026" in text

第 2 段階を省くと、ポータルの一覧で行を 1 つ間違えたときに気づけません。同じサービスの隔月の領収書は見た目がほとんど同じなので、目視では必ず見逃します。機械的に突合させるのが唯一の解です。

pypdf ならテキスト抽出が瞬時なので、目視確認を完全に置き換えられます。LLM に PDF を見せる必要もありません。

5. 制約から運用を逆算する

ここまでの制約を並べると、運用の形が自動的に決まります。

  • メールのリンクは約 30 日で失効する
  • ポータル経由なら取り直せるが、サービスごとにログインが必要で手間がかかる

だから、月初に前月分をまとめて回すのが最も効率がよい。このときメールはすべて 30 日以内なので、ほぼ全件をメール経由の最短ルートで取得できます。

この逆算は月次締めの skill の先頭に明記してあります。

毎月月初の実行を想定
(領収書メールのリンクが 30 日で失効するため、月初実行が最も効率的)

制約を手順のコメントに残すのは地味ですが効果があります。数ヶ月後の自分が「なぜ月初にやるんだっけ?」と思ったときに答えを返せます。

6. 取得パターンは 4 つに分かれる

すべてのサービスが同じ決済代行を使っているわけではありません。実際には 4 パターンに分かれました。

パターン 取得方法 難易度
メールに PDF 直添付 メールクライアントから保存 最も楽
決済代行リンク型 URL を組み立てて直接取得 楽(本記事)
Web ダッシュボード型 ブラウザ自動操作で取得 中(次回)
HTML しかない型 HTML を保存してローカルで PDF 化 やや面倒(次回)

重要なのは、ベンダーごとの取得方法を台帳に書いておくことです。毎月同じ調査をやり直すのは無駄なので、一度確立した手順は手順書に残します。台帳にはベンダー種別・目安金額・決済日・取得方法・勘定科目の傾向を並べておきます。

金額の目安と決済日を入れておくのがコツです。カード明細を見たときに「この引き落としはこのサービス」と即座に対応付けできます。


Tips

  • 取得の成否を終了コードで判断しない。失効リンクは HTTP 200 でエラー文字列を返すので、file で型を見る
  • ポータル発行のリンクは新鮮。過去分を取り直すときはメールではなくポータルから
  • メール本文はファイルに落として、欲しいものだけ抽出する。HTML メールは 5 万文字を超えることがある
  • リンクと一緒に金額・日付も抽出しておく。ファイル名付けと検証の両方に使える
  • 検証は「型」と「中身」の 2 段階。隔月の領収書は見た目が同じなので目視では必ず見逃す
  • 制約を手順のコメントに残す。「なぜ月初にやるのか」の理由が残っていると、運用が崩れにくい

まとめ

  • 領収書メールの PDF リンクは約 30 日で失効し、しかも HTTP 200 でエラーを返す
  • 決済代行のカスタマーポータルを使うサービスなら、請求リンクから PDF の URL を機械的に組み立てられる
  • 変換規則は「ホストとパス先頭の置換」と「末尾に /pdf を追加」の 2 つだけ
  • 取得後の検証は file による型確認と、PDF テキスト抽出による中身確認の 2 段階
  • 30 日失効という制約から、「月初に前月分をまとめて回す」という運用が逆算される
  • ベンダーごとの取得方法は台帳化する。金額の目安と決済日を入れておくと明細との対応付けが早い

参考リンク

更新履歴

  1. 記事内リンクの修正

確定申告自動化

6

個人事業主の経理を、UIを作らず Claude Code の skill だけで回した記録。領収書の自動取得から電子帳簿保存法の要件、freee への添付、月次締めまで。

  1. 1【設計】UIを作らず Claude Code の skill だけで確定申告を自動化する — プロジェクト全体像(確定申告自動化 第1回)
  2. 2【設計】電子帳簿保存法の検索要件を「ファイル名規約」だけで満たす — DB を作らないという選択(確定申告自動化 第2回)
  3. 3【実装】会計 API とブラウザ自動操作の役割分担 — 同期明細は必ず UI から登録する(確定申告自動化 第3回)
  4. 4表示中【実装】メールリンクの 30 日失効と戦う — 決済ポータルから領収書 PDF の URL を組み立てる(確定申告自動化 第4回)
  5. 5【トラブルシューティング】ブラウザ自動操作でファイルを集めるときの落とし穴集(確定申告自動化 第5回)
  6. 6【設計】月次締めを skill にする — 定型作業を「一言」に畳む設計(確定申告自動化 第6回・完)
この連載の一覧を見る