AIと一緒に「メールから課金情報を整理するソフト」を作った
今回は、Gmail、Hotmail、iCloudに届いているメールを読んで、課金情報だけを一覧にするソフトを作った。
サブスクとかネット通販とか、アプリの購入とか、カードの利用通知とか、そういうメールを全部まとめて見られるやつ。
俺は昔からいろんなサービスに登録してるけど、正直どこに何円払ってるのか完全には把握してない。
Gmailだけならまだしも、Hotmailにも届くし、Apple関係はiCloudに届く。メールアドレスが三つに分かれているせいで、確認するだけでも面倒くさい。
カードの明細を見れば金額は分かるけど、「これは何に使った金なのか」まではすぐ分からないこともある。
だったらメールを全部読ませて、課金らしいものだけ自動で整理したらいいんじゃないかと思った。
というわけで、今回もCodexと一緒に作った。
最初に考えたもの
最初に欲しかった機能は単純だった。
- Gmailに対応
- Hotmail、Outlookに対応
- iCloudメールに対応
- 課金メールを探す
- 金額、日付、サービス名を一覧にする
- サブスクと単発購入を区別する
- 毎月の固定費と年間予測を出す
完成した画面だけ見ると、それほど複雑なソフトには見えない。
でも実際に作り始めると、メールを読むところがかなり面倒だった。
特にOAuth認証。
GmailのOAuth認証
Gmailを読むには、Google CloudでOAuth用のクライアントIDとクライアントシークレットを作る必要がある。
この時点で、普通の人が気軽に使えるソフトではなくなっている気がする。
Google Cloudの管理画面を開いて、Gmail APIを有効にして、OAuth同意画面を設定して、リダイレクトURIを登録する。
今回のリダイレクトURIはこれ。
http://localhost:3741/auth/gmail/callback認証そのものはできたんだけど、最初はOAuthの途中で、
認証セッションが無効ですと表示された。
原因は、OAuth開始時に作った認証セッションがサーバーの再起動で消えていたことだった。
認証画面を開いている間にソフトを再起動すると、戻ってきたときには「お前誰だよ」状態になる。
そこで認証セッションを暗号化ファイルへ保存するように修正した。
これで途中でサーバーを再起動しても、OAuthの続きができるようになった。
最終的にはGmailとの接続に成功した。
最初の同期では116通ほど確認して、課金候補を取り込めた。
その後、「1年分だけじゃなくて、記録を全部読んでほしい」と思ったので、ページネーションにも対応させた。
Gmail APIは一度にすべてのメールを返してくれない。何百件かずつ取得して、次のページがある限り繰り返す必要がある。
さらに、一気に大量取得するとGoogle側に怒られる可能性があるので、メール本体は少数ずつ取得する形にした。
HotmailのOAuth認証
次はHotmail。
こっちはMicrosoft Entra、昔のAzure ADでアプリを登録する必要がある。
Googleより分かりづらかった。
まずMicrosoftの認証画面を開いたら、
選択されたユーザーアカウントは、このテナントに存在しませんみたいなエラーが出た。
個人のHotmailアカウントで使いたいのに、組織用テナントへ入ろうとしていたらしい。
そこで個人Microsoftアカウント用としてアプリを登録し直した。
リダイレクトURIはこれ。
http://localhost:3741/auth/microsoft/callbackここでも問題があった。
Azureのアプリ登録画面には「Web」と「シングルページアプリケーション」がある。
最初にシングルページアプリケーションとして登録したら、認証後のトークン交換でエラーになった。
このソフトはブラウザだけで動くSPAではない。
ローカルでNode.jsのサーバーが動いていて、サーバー側で認証コードをトークンへ交換する。
だから「Web」として登録する必要があった。
一度間違った設定を削除して、WebのリダイレクトURIとして登録し直した。
クライアントシークレットも作り、アプリへ保存した。
それでようやくHotmailのOAuth認証に成功した。
いや、OAuth、面倒くさすぎるだろ。
GoogleとMicrosoftの両方で、似たようなことをするのに管理画面も用語も微妙に違う。
一般人がここを自力で設定するのはかなり厳しいと思う。
iCloudはOAuthではなかった
iCloudメールは、GoogleやMicrosoftと同じOAuth方式ではなく、IMAPで接続した。
ただし、Apple Accountの普通のパスワードは使えない。
Appleの設定画面で「アプリ用パスワード」を発行する必要がある。
最初は正しいパスワードを入力したつもりなのに接続できなかった。
調べていくと、iCloudのIMAP認証では、ユーザー名としてメールアドレス全部を使う場合と、@より前だけを使う場合があるらしい。
さらに、アプリ用パスワードのハイフンが原因になる可能性もあった。
そこで、
- メールアドレス全部
- @より前の部分
- ハイフン付きのアプリ用パスワード
- ハイフンを除いたパスワード
この組み合わせを順番に試すようにした。
それでiCloudにも接続できた。
Gmail、Hotmail、iCloud。
三つとも接続できたときは、もうソフトの本体より認証設定の方が大変だった気がした。
課金情報を抽出する
メールを読めるようになったので、次は課金情報の抽出。
最初は件名、送信者、本文から、こういう単語を探していた。
- 請求
- 領収書
- 購入
- 決濟
- 注文
- payment
- receipt
- invoice
- charged
- subscription
同時に、金額らしい表記も探す。
¥1,200
1,200円
$19.99
USD 44.02課金関係の言葉と金額があれば、課金メールの可能性が高い。
そこからサービス名、日付、通貨、カテゴリ、サブスクか単発購入かを推測する。
最初はこれで結構うまくいった。
ChatGPT Plus、Google Play、Apple、Amazon、DMM、Nintendoなどの購入情報を一覧にできた。
ところが、実際に全メールを読ませると、とんでもない問題が出た。
AIが賢いと思っていたら、全然そんなことはなかった
一覧の二行目に、bizoceanのメールが課金情報として表示されていた。
件名はこんな内容だった。
【製造・建設建築業界の方へ】
採用でも正しいツール選びが必要?
AI活用で「自社に合う採用ツール」の見つけ方【bizocean】どう見ても課金情報ではない。
ただの広告メール。
でも本文のどこかに「購入」「月額」「1,200円」みたいな言葉が入っていたため、課金メールだと誤判定していた。
ここで分かった。
このソフトはAIがメールの意味を理解していたわけではない。
単語と金額のパターンを探していただけだった。
人間なら一瞬で広告だと分かる。
でも単純な判定ロジックだと、
購入という言葉がある
月額という言葉がある
1,200円と書いてあるというだけで課金扱いしてしまう。
さらに調べると、誤判定はbizoceanだけじゃなかった。
- 株式市況
- クーポンのお知らせ
- ポイントキャンペーン
- 新築マンション情報
- iPhoneの予約案内
- セール情報
- 無料ガイド
- メールマガジン
こういうメールまで課金情報に混ざっていた。
特に株式市況は本文に大量の数字が出てくるので、金額判定との相性が最悪だった。
株価を請求金額だと思っていた。
人工智能、あまりにも素直すぎる。
判定を厳しくした
そこで判定方法を変更した。
単に「本文のどこかに課金っぽい言葉と金額がある」だけでは登録しない。
金額の近くに、実際の取引を示す表現があるか確認するようにした。
さらに件名も重視する。
課金として強い件名はこんなもの。
- 領収書
- ご請求
- 請求額
- お支払い完了
- カード利用のお知らせ
- 注文内容の確認
- 購入完了
- Payment receipt
- Order confirmation
反対に、次のような件名は広告の可能性が高い。
- クーポン
- キャンペーン
- ポイント進呈
- プレゼント
- 無料ガイド
- セール
- 新着
- 予約
- 市況
- ニュース
- メールマガジン
送信者が「○○事務局」「○○通信」「Newsletter」になっている場合も慎重に扱う。
bizoceanの広告を除外しつつ、「楽天カードのカード利用のお知らせ」は残るようにテストも追加した。
bizoceanから誤登録されていた項目は57件あった。
全部アプリ内の一覧から削除した。
もちろん元のメールは削除していない。
100%自動判定は無理だと思った
判定を厳しくすれば、広告は減る。
でも厳しくしすぎると、本物の課金メールまで消える。
例えば、カード会社やサービスによって件名の書き方は全部違う。
「ご利用のお知らせ」としか書いていないメールもある。
本文にだけ金額が書いてある場合もある。
逆に、広告メールの件名に「購入」「請求」「月額」という言葉が入ることもある。
つまり、完全自動で100%正確に分類するのは無理がある。
だったら、分からないものを勝手に課金へ入れない方がいい。
そこで三段階にした。
- 確実な課金情報
- 要確認
- 課金ではない
確信度が高いものだけを支出額へ集計する。
曖昧なものは「要確認・集計外」に置く。
要確認の項目には、
✓ 課金
課金でないというボタンを付けた。
俺が「課金」を押せば確定する。
「課金でない」を押せば一覧から除外する。
しかも、その判断を記憶するようにした。
同じ送信元、同じ形式の件名が再び届いたときは、前回の判断を利用する。
AIに全部任せるというより、人間が少しずつルールを教えていく形。
こっちの方が現実的だと思う。
どのメールアドレスに届いたか分からない問題
一覧を作ったあと、別の問題に気づいた。
その課金情報がGmail、Hotmail、iCloudのどこから取得されたものなのか分からない。
複数のメールアドレスをまとめるソフトなのに、取得元が見えないのは不便だった。
そこで一覧に「メール」列を追加した。
- Gmailは青
- Hotmail / Outlookは水色
- iCloudはグレー
- サンプルは紫
さらに受信したメールアドレスも表示する。
これで、どのアカウントに届いたメールなのか一目で分かるようになった。
送信元も表示するようにした。
ただし、ここでも一つバグがあった。
メールの送信元は、こういう形式で入っていることがある。
OpenAI <billing@example.com>最初の処理では、<billing@example.com>の部分をHTMLタグだと勘違いして削除していた。
そのため、一覧には「OpenAI」しか残っていなかった。
メールアドレスなのにHTML扱いされて消えていた。
これも修正して、<>の中身を送信元メールアドレスとして保存できるようにした。
古いデータについては、再同期するとアドレスが補完される。
送信元を一括削除
誤判定された広告が何十件もあると、一件ずつ削除するのは大変。
そこで「送信元を一括削除」ボタンを付けた。
例えば「楽天カード通信」が広告ばかりなら、その送信元から抽出された項目をまとめて削除できる。
ただし、削除するのはこのソフトの抽出結果だけ。
Gmail、Hotmail、iCloudにある元メールは消さない。
最初はこのボタンも動かなかった。
原因は送信元名に入っていた引用符。
"Anthropic, PBC"この文字列をそのままHTMLのデータ属性へ入れたせいで、ボタンの属性が途中で壊れていた。
見た目は普通のボタンなのに、押しても送信元が空になっていた。
送信元文字列をHTMLへ直接埋めるのをやめて、課金情報のIDから安全に取得する方式へ変更した。
修正後は、実際の画面で確認ダイアログが出るところまでテストした。
元のメールを開く
課金一覧だけを見ても、本当に正しい金額なのか確認したいことがある。
そこで「元メール」ボタンを追加した。
最初、GmailはメッセージIDを使ったURLを作り、HotmailはMicrosoftから取得したWebリンクを開くようにした。
Hotmailはかなりうまくいった。
Microsoft Graphから個別メールのURLを取得すると、Outlookの該当メールを直接開ける。
ところが、iCloudには同じ仕組みがない。
IMAPのUIDとiCloud WebメールのURLを直接対応させる方法が見つからなかった。
そのため最初は「元メール」を押しても、iCloudの受信トレイが開くだけだった。
これでは元メールを確認できたことにならない。
GmailもWeb画面の状態によっては、目的のメールではなく檢索画面や受信トレイを開く可能性がある。
そこで最終的には、メールサービスのWeb画面へ直接飛ばす方式をやめた。
「元メール」を押すと、このソフト自身がGmail API、Microsoft Graph、iCloud IMAPから該当メールを取得する。
そしてアプリ内の専用画面に表示する。
確認画面には次の情報が出る。
- 件名
- 送信元
- 受信日時
- 受信したアカウント
- メール本文
本文はデータベースへ保存しない。
ボタンを押した瞬間だけメールサービスから取得する。
これなら余計な個人情報をローカルへ大量保存せずに濟む。
GmailとiCloudの実データを使って、対象メールの本文が開けるところまで確認した。
カテゴリを編集できるようにした
課金情報には自動でカテゴリを付ける。
最初に用意したのは、
- 買い物
- エンタメ
- クラウド
- ソフトウェア
- 未分類
など。
ただ、自動分類だけでは足りない。
同じサービスでも仕事用と個人用を分けたいことがある。
通信費、ゲーム、電子書籍、食費、投資など、自分の好きなカテゴリも使いたい。
最初はカテゴリ欄を直接入力できるようにした。
でも毎回文字を入力するのは面倒。
そこで最終的には選択式にした。
既存カテゴリをプルダウンから選べる。
新しいカテゴリが必要なときだけ、
+ 新しいカテゴリを選び、名前を入力する。
一度作ったカテゴリは、ほかの課金情報でも選べるようになる。
画面が切れた
機能を追加していくと、今度は画面の横幅が足りなくなった。
一覧には、
- 日付
- サービス
- 件名
- 送信元
- 受信メール
- 種類
- カテゴリ
- 金額
- 操作ボタン
が並んでいる。
さらに右側には接続濟みメールアカウントの一覧がある。
カテゴリ編集欄と操作ボタンを追加したら、「送信元を一括削除」の部分が右端で切れてしまった。
そこで表の列幅を固定し、長い文章は折り返すようにした。
操作ボタンも縦に並べ、一覧の中へ必ず収まるように調整した。
見た目は地味な変更だけど、こういう最後の調整が意外と時間を使う。
機能が動くだけでは駄目で、普通に押せないと意味がない。
現在できること
今回作ったソフトでは、現在こういうことができる。
- GmailをOAuthで接続
- Hotmail、OutlookをOAuthで接続
- iCloudをアプリ用パスワードで接続
- 各メールアカウントの全期間を同期
- 課金らしいメールを抽出
- サービス名、日付、金額、通貨を表示
- サブスク、単発購入、返金を分類
- Gmail、Hotmail、iCloudの取得元を表示
- 受信メールアドレスを表示
- 送信元と送信元メールアドレスを表示
- 確実な項目だけを支出へ集計
- 曖昧な項目を「要確認」へ分離
- 課金かどうかを手動で判断
- 判断結果を次回以降へ反映
- 一件だけ削除
- 同じ送信元を一括削除
- 元メールの本文をアプリ内で確認
- カテゴリを選択して変更
- 新しいカテゴリを追加
- 今月の支出を表示
- 毎月の固定費を表示
- 年間予測を表示
最初に考えていたものより、かなり機能が増えた。
作ってみて思ったこと
今回一番大きかったのは、「AIがメールを読めば自動で正しく分類できるだろう」という考えが甘かったこと。
実際には、どれだけルールを増やしても誤判定は出る。
本物のAIモデルへ本文を渡せば、単純な正規表現より精度は上がると思う。
でも、その場合はメール本文を外部のAIサービスへ送ることになる。
メールには住所、氏名、注文内容、カード情報の一部などが含まれる可能性がある。
精度だけを理由に全部外部へ送るのは少し怖い。
だから現時点では、
ローカルのルールで候補を絞る
確実なものだけ自動確定する
曖昧なものは人間が確認する
判断結果を少しずつ記憶するという構成にした。
たぶん、この手のソフトでは「AIに全部任せる」より、「AIが候補を出して、人間が最後に決める」方が正しい。
今回、8,000件以上が要確認に回った。
数字だけ見ると多すぎる。
でも、それまでその8,000件近くを課金情報として合計していたと思うと、そっちの方が恐ろしい。
間違った数字を自信満々に表示するくらいなら、「分からない」と言ってくれた方がいい。
人工智能にも謙虚さが必要だった。
とりあえず、たたき台は完成
まだ改善したいところはある。
例えば、送信者名だけしか残っていない古いメールの再取得、同じ購入が複数メールに出た場合の重複処理、カテゴリごとのグラフ、月別の推移、CSV出力など。
本物の課金かどうかの判定も、まだ完璧ではない。
でも、Gmail、Hotmail、iCloudを全部つないで、課金情報を一覧にするという最初の目的は達成できた。
OAuth認証で何度も止まり、URLを間違え、セッションが無効になり、SPA設定で失敗し、正しいパスワードなのにiCloudへ入れず、広告を課金だと思い込み、一括削除ボタンも動かず、元メールを押しても受信トレイしか開かなかった。
だいたい一回では動かなかった。
でも、そのたびに直して、ようやく使える形になった。
とりあえず今回はここまで。
たたき台としては十分できたと思う。
これから実際に使ってみて、変な課金情報が出てきたら、また人工智能を教育することにする。
( ´ノω`)
