title: "請求書自動送信:Gmail APIと日本語ファイル名の罠"
date: 2026-02-21
tags: [automation, gmail-api, python, invoice]
請求書自動送信:Gmail APIと日本語ファイル名の罠
フリーランスをやっていると、毎月末の請求書送信が地味にストレスだ。金額の確認、PDFの添付、宛先の確認——作業自体は10分で終わるのに、「忘れたらどうしよう」というプレッシャーが月末に常につきまとう。
今日、ようやくこれを自動化した。
やりたかったこと
シンプルに言えば「毎月最終営業日に、請求書PDFをメールで送る」。ただし条件がある:
- クライアントごとに添付ファイルが違う(A社は請求書+作業報告書、B社は請求書のみ)
- 送信元はGmail(techsfree.comドメイン)
- 絶対に誤送信してはいけない
最後の条件が一番重要だ。自動化の最大の敵は「自動で間違える」こと。
3ステップ安全機構
結局たどり着いたのは、Draft→確認→送信の3ステップ方式だ:
1. Cronがトリガー: 毎月25-31日の平日17:00に起動。当日が本月最終営業日かチェックして、該当する場合のみ処理を開始
2. Draftを生成してTelegramに通知: 「こういうメールを送りますよ」とボタン付きで送る
3. ボタンを押して初めて送信: テスト送信と本番送信を分けて、個別送信も全件送信もできる
callback_dataはinvoice_send:という形式。OpenClawのinlineButtons機能をフル活用している。
日本語ファイル名という地雷
技術的に一番ハマったのが、添付ファイルの日本語ファイル名だ。
最初はRFC 2231方式でエンコードした。標準的なやり方だし、Gmailでは問題なく表示される。ところがOutlookで開くと、ファイル名が完全に化ける。
調べてみると、OutlookはRFC 2231のサポートが中途半端で、RFC 2047のBase64エンコードじゃないとまともに表示されない。2026年にもなって互換性問題かよ、と思いつつ、RFC 2047方式に書き換えた。
from email.header import Header
encoded_name = Header(filename, 'utf-8').encode()
たった2行の違いだが、これでOutlookでもGmailでも正しく「請求書_2026年02月.pdf」と表示されるようになった。
Gmail OAuth scopeの話
もう一つ厄介だったのが、Gmail APIのOAuth scope。既存のトークンはreadonly+sendの権限しかなく、compose scopeが足りなかった。
Google v3ログインページは反自動化検測が厳しく、CDPで自動的にOAuth認証を通そうとしたが、パスワード入力後の画面遷移で毎回ブロックされる。結局「compose scopeなしでもgmail.sendで十分」という結論に落ち着いた。Draft作成はローカルでMIMEメッセージを組み立てて、send APIに直接投げる方式。
実際の動き
月末最終営業日の17:00になると、Telegramにこんなメッセージが届く:
📨 2026年2月 請求書ドラフト準備完了
- エッジテクノロジー: 請求書 + 作業報告書
- anyenv: 請求書
[テスト送信] [本番送信:個別] [本番送信:全件]
ボタンを押すまでは何も送信されない。テスト送信を押せば自分のHotmailに届くので、内容を確認してから本番送信できる。
教訓
自動化で一番大事なのは「何を自動化しないか」の設計だ。メール送信のような不可逆な操作は、最後の一歩だけ人間に残すのが正解。全自動にすると事故ったときのダメージが大きすぎる。
来月の請求書送信が楽しみだ——楽しみというのも変な話だが、自分が作ったシステムが実際に動くのを見るのは、いつだって嬉しい。