
給与明細が、毎月メールに添付されたパスワード付きPDFで送られてきます。開くたびにパスワードを入力するのも少し面倒ですし、いずれは明細の内容を家計簿にも取り込めるようにしたいところ。
せっかくなので、AWS Lambdaの学習がてら、PDFをS3にアップロードすると、自動でパスワードを解除して保存してくれる仕組みを作ってみました。今回は、作ったものと構築中に学んだことをまとめます。
⚠ 注意事項
設定内容やプログラムの内容は、用途・環境に応じて適切なものが変わります。
各種設定値や環境情報についてよく理解を深め、壊れてもよい環境で十分に検証してください。
なるべく正確に書くよう心掛けていますが、本投稿内容を実施される際には自己責任の下でお願いいたします。
まずは「S3に置いた後」を自動化する
最終的にはメールが届いたところから自動化したいのですが、いきなり全部をつなぐと確認することが増えそうです。
まずは、メールの添付PDFを手動でS3へアップロードし、その先の処理をLambdaに任せるところまでを作りました。
給与明細のメールが届く ↓ 添付PDFを保存し、手動でアップロード S3:incoming/給与明細.pdf ↓ ファイル作成イベント Lambda:qpdfで復号 ↓ 自動で保存 S3:decrypted/給与明細.pdf
ここでいうパスワード解除は、自分が受け取った給与明細を、手元で分かっているパスワードを使って復号する処理です。
現在は1種類の固定パスワードを設定して使う構成にしています。
使ったもの
- Amazon S3:元のPDFと復号後のPDFの保存先
- AWS Lambda:S3へのアップロードをきっかけに処理を実行
- Python 3.13/boto3:S3からのダウンロードとアップロード
- qpdf:PDFの復号
- Docker/Amazon ECR:qpdfを含む実行環境の作成と保管
- IAM/CloudWatch Logs:実行権限の設定とログの確認
構築作業はWindows上のWSL2とDebianで進めました。
今回はPythonから外部コマンドのqpdfを実行するので、qpdfも一緒に入れたコンテナイメージをLambdaで動かす形にしています。
各サービスの連携図
各サービスの関係を、PDFを処理するときの流れと、実行環境をデプロイするときの流れに分けると、次のようになります。
PDFをアップロードしてからの流れ
[メール:給与明細PDFを添付]
|
| (1) PDFを保存して手動アップロード
v
+--------------------------+
| Amazon S3 |
| incoming/給与明細.pdf |
+--------------------------+
|
| (2) ファイル作成イベント
v
+----------------------------------+
| AWS Lambda |
| |
| Python / boto3 |
| (3) incoming/から取得(GetObject) |
| | |
| v |
| /tmp:入力PDF |
| | |
| v |
| qpdf + PDF_PASSWORD |
| | |
| v |
| /tmp:復号済みPDF |
+----------------------------------+
| |
| (4) 保存(PutObject) | ログ
v v
+---------------------+ +-----------------+
| Amazon S3 | | CloudWatch Logs |
| decrypted/ | | 実行ログ |
| 給与明細.pdf | +-----------------+
+---------------------+
図の上下にあるS3は同じバケットです。イベント通知でPDFそのものが渡されるわけではなく、通知を受けたLambdaがboto3で元のPDFを取得します。PDF_PASSWORDはLambdaの環境変数で、出力先のdecrypted/は今回の通知対象に含めていません。
コンテナイメージのデプロイと権限の関係
[Windows/WSL2/Debian]
|
| Dockerイメージをビルド
v
[コンテナイメージ:Python + qpdf + ハンドラー]
|
| コンテナイメージを登録
v
[Amazon ECR]
|
| イメージを指定して関数を作成・更新
v
[AWS Lambda] --------------------> [S3/CloudWatch Logs]
^ 実行ロールを使ってアクセス
|
| 実行ロール
[IAM]
[S3イベント] -------------------> [AWS Lambda]
呼び出し許可:
Lambdaのリソースベースポリシー
ECRは実行用イメージの保管先で、給与明細PDFの保存先はS3です。LambdaがS3やCloudWatch Logsへアクセスするための実行ロールと、S3からLambdaを呼び出すための許可は、図のように別々に設定しています。
qpdfを入れたコンテナをLambdaで動かす
Dockerfileはかなり短く、Lambda用のPythonベースイメージにqpdfをインストールし、処理用のPythonファイルをコピーしています。
FROM public.ecr.aws/lambda/python:3.13
RUN dnf install -y qpdf \
&& dnf clean all
COPY lambda_function.py ${LAMBDA_TASK_ROOT}
CMD ["lambda_function.lambda_handler"]
最初はPDFの処理まで入れず、qpdfのバージョンを返すだけの関数で動作確認しました。
コンテナの中で動くことを確かめてからECRへ登録し、Lambda上でも実行できることを確認。
その後、S3からPDFを取得する処理を追加しています。
Lambdaでコンテナイメージを利用する流れは、AWS公式ドキュメントにもまとまっています。
イメージをECRに置き、そのイメージをLambdaの実行環境として使う構成です。
Lambdaの中でやっていること
処理そのものはシンプルです。S3イベントからバケット名とファイルのキーを取得し、incoming/配下のPDFだけを対象にします。そのPDFをLambdaの/tmpへダウンロードし、qpdfで復号。
できたファイルを同じバケットのdecrypted/へアップロードします。
パスワードはコードへ直接書かず、Lambdaの環境変数PDF_PASSWORDから取得しています。
復号にはqpdfの--passwordと--decryptを使い、Pythonのsubprocess.run()で実行しています。
アップロード元のPDFはそのまま残るので、S3にはパスワード付きの元ファイルと復号済みファイルがそれぞれ保存されます。
コード以外の設定も勉強になった
今回、整理する必要があったのがそれぞれのサービスが利用する権限の内容です。
「LambdaがS3を読み書きする権限」と「S3がLambdaを呼び出す権限」をそれぞれ設定します。
関数を作って通知先に指定するだけではなく、サービス同士をどうつなぐのかも含めて、よい練習になりました。
もう一つは、S3の通知対象を絞ること。
今回は入力をincoming/、出力をdecrypted/に分け、incoming/配下の.pdfだけでLambdaを起動するようにしています。
出力したファイルでもLambda関数が起動すると処理が繰り返され無限ループになってしまうため結構重要です。
権限と通知の考え方は、S3イベントとLambdaについてのAWS公式ドキュメントを参照しながら作業しました。
S3にアップロードしたら、復号済みPDFができた
構築時には、パスワード付きPDFをincoming/にアップロードし、Lambdaが起動してdecrypted/にファイルが生成されるところまで確認できました。
処理の途中経過はCloudWatch Logsで確認しています。
まだメールから添付ファイルを取り出すところは手動ですが、「S3に置くと、その先は勝手に処理される」という形にはなりました。
身近な作業を題材にすると、Lambdaをどこで使うのかがイメージしやすくなりますね。
料金はどのくらいかかる?
今回の用途は、基本的に月1回、給与明細のPDFを処理する程度です。常時サーバを動かす構成ではないので実行回数は少ないのですが、Lambdaの実行料だけでなく、PDF・コンテナイメージ・ログを保存する料金も考える必要があります。
| サービス | 主な課金対象 | 今回、見ておきたいところ |
|---|---|---|
| AWS Lambda | リクエスト数、割り当てメモリ量と実行時間 | 月1回の処理に加え、構築中のテストや再実行も利用量に含まれる |
| Amazon S3 | 保存容量、PUT・GETなどのリクエスト、条件に応じたデータ転送 | 元のPDFと復号済みPDFの両方を残すので、保存期間が長いほど容量が増える |
| Amazon ECR | コンテナイメージの保存容量、条件に応じたデータ転送 | Lambdaを実行しない月でも、イメージを保管していれば保存料の対象になる |
| CloudWatch Logs | ログの取り込み、保存、Logs Insightsなどでの分析 | ログの量と保持期間。調査でログを分析する場合は、その利用量も確認する |
月1回の処理を仮定してみる
今回の実測値ではありませんが、Lambdaのメモリを512MBに設定し、課金対象の実行時間が1回10秒、月1回の実行だったとすると、使用量は0.5GB × 10秒 × 1回 = 5GB秒/月です。
2026年9月3日に確認したLambdaの公式料金ページでは、月100万リクエストと40万GB秒の無料枠が案内されています。
現状他にLambdaを動かしているわけではないので、無料枠の範囲内で動かせそうです。
一方、ECRのプライベートイメージの保存料は、公式料金ページで1GBあたり月0.10米ドルと案内されています。
仮に課金対象の保存容量が1GBで、それを1か月保管すると、無料枠やクレジットを差し引く前の保存料は0.10米ドルです。
これはECRの保存料だけの例で、S3やログなどを含めた合計額ではありません。
ここで使う容量は、ローカルのDockerで表示されるイメージサイズではなく、ECR側の保存量で確認します。
今回のように実行頻度が低い用途では、処理そのものの料金に加えて、イメージを置き続ける費用を見ておくとよさそうです。
使い続けるなら、保存するものも整理する
費用を抑えるためにも、不要になったECRの古いイメージを整理し、S3のPDFとCloudWatch Logsの保持期間を決めておきたいところです。
特に、構築中に何度もビルド・テストした月は、普段の月1回運用とは利用量が変わります。
上の内容は2026年9月3日時点の公式情報をもとにした目安で、実際の請求実績ではありません。
メール取得やPDFの読み取り、家計簿アプリとの連携を追加する際には、その部分の費用も別途見積もるつもりです。
今の実装で残っていること
まず動くところを優先したので、継続して使うために整えたい点もあります。
例えば、出力先には元のファイル名だけを使っているため、同じ名前のPDFをアップロードすると復号済みファイルが上書きされます。
年月を含めた保存先にするなど、給与明細を蓄積する前に見直したいところです。
また、/tmpの一時ファイルの削除、処理に失敗したときの通知、パスワードの管理方法も今後の改善項目です。
給与明細を扱うので、S3のアクセス範囲と、復号したPDFをどのくらい残すかも含めて運用を整えていきたいと思います。
最終的には、メール受信から家計簿への連携まで
やりたいのは、給与明細のメールが届いたら、自動で添付PDFを取り出し、パスワードを解除して、必要な項目を家計簿アプリへ渡すところまでです。
【今後作りたい流れ】 給与明細のメールを受信 ↓ 対象メールから添付PDFを取得 ↓ S3へ自動アップロード ↓ Lambdaで復号(今回作成した部分) ↓ PDFから支給額・控除額などを抽出 ↓ 内容を確認して家計簿アプリへ連携
メールの取得方法や家計簿アプリへの連携方法はこれから検討します。
PDFの内容を読み取る処理もまだ作っていません。
まずは抽出した金額を確認できるようにして、同じ明細を二重に登録しない仕組みも考えながら、少しずつつないでいくつもりです。
毎月のパスワード入力を少し楽にしたい、というところから始めましたが、メールと家計簿までつながると、なかなか便利になりそうです。
今回はその最初の一歩として、S3とLambdaでPDFを処理する部分を作ってみました。
ソースコード
今回のコードはGitHubで公開しています。LambdaのPythonコード、Dockerfile、AWS設定用のJSONひな形を置いています。
GitHub:k636174/pdf-decryppt-lambda