毎朝の情報収集を、自分のサイトにつなげたくなった
AWSやAI、セキュリティのニュースは追いたいものの、毎朝いくつものサイトを順番に開く運用は続きそうにありませんでした。気になった日にまとめて読むことはできても、忙しい日はそのまま流れてしまいます。
そこで考えたのが、一次情報だけを集め、短い日報にして自分へ届ける仕組みです。メールを開けば概要が分かり、もう少し読みたい日はSAMEKORO LABの詳細ページへ進める形にしました。
最初は「Lambdaでニュースを取ってメールを送ればよさそう」と考えていました。実際に形にすると、収集、文章生成、検証、保存、公開、送信、定時実行はそれぞれ別の役割です。動くコードだけでなく、途中で止まった時にどこまで終わったかを残す必要もありました。
メールは入口、サイトはあとから読み返す場所にした
SAMEKORO Dailyには、メール版とWeb版があります。同じ日報を二つ作りたかったわけではありません。
メールは、朝に数分で読むダイジェストです。注目ニュースと短報、AWSの小ネタを絞り、詳しい出典一覧や過去分はサイトへ任せます。Web版は日付ごとに残し、あとから読み返せる詳細版にしました。
初回のHTMLメールは、PCでは暗い見た目になった一方、スマートフォンでは背景の見え方が崩れました。Web用のHTMLをそのままメールへ持ち込めばよいと思っていたのですが、メールクライアントでは使えるCSSに制約があります。
そこでメールは白背景、最大幅600px、tableレイアウトとインラインCSSへ分けました。サイトの深海デザインを無理に再現するより、受信箱で読みやすいことを優先しています。動いた瞬間を完成にせず、読む場所に合わせて役割を分けた判断でした。
Codexで作る部分と、AWSコンソールで決める部分を分けた
実装はCodexと一緒に進めました。ニュースURLの正規化、重複排除、日付確認、OpenAI Responses APIとの連携、JSON Schema検証、Webとメールの生成、テスト、Lambda用ZIPまではリポジトリ側で作っています。
一方、AWSのリソースは自分でコンソールから設定しました。Lambdaの実行ロール、S3の保存先、Secrets Manager、SESの認証、EventBridge Schedulerは、実際のアカウントと既存サイトを見ながら決める必要があるからです。
AIが提案した構成を、そのままAWSへ置いたわけではありません。既存のS3とCloudFrontを使うこと、メールの送信元と送信先を限定すること、毎朝5時にすること、失敗時に自動再送しない条件は、自分の環境で確認しながら選びました。
最終的な構成はこうなった
サービスが増えるほど、コンソールの画面だけでは処理の順番を見失いやすくなりました。今回の通信経路と保存先を図にすると、次の流れです。
EventBridge Scheduler(毎朝05:00 / Asia/Tokyo)
↓
AWS Lambda
├─ 公式ニュースフィードを収集
├─ OpenAI Responses APIで日報を生成
├─ JSON Schemaと独自ルールで検証
├─ S3へ日報・一覧・stateを保存
├─ CloudFrontのキャッシュを更新
└─ Amazon SESでメールを送信
Secrets Manager ─ OpenAI APIキー ─→ Lambda
S3 ─→ CloudFront ─→ samekorolab.com/daily/
SES ─→ 独自ドメインの送信元 ─→ 検証済みの受信先
Lambdaが全部を抱えているように見えますが、秘密情報はSecrets Manager、公開ファイルと実行状態はS3、Web配信はCloudFront、メールはSESが担当します。Schedulerはその処理を毎朝始める係です。
図へ戻ると、どこで失敗したかも追いやすくなりました。たとえば日報がS3へ保存されているのにメールが来ないなら、収集から公開までは進み、確認先をSES側へ絞れます。
一次情報だけを使い、生成後にも止める仕組みを入れた
ニュースはAWS、CISA、OpenAI、GitHubなどの公式フィードを入口にします。URLから追跡用パラメータを外し、同じURLや同じタイトルを重複排除し、対象日より未来の記事や古すぎる記事を候補から外します。
候補を選んだあと、OpenAI Responses APIへ渡します。利用モデルはリポジトリの設定とAWS手順書で gpt-5.6-terra にそろえました。ただし、モデルが文章を返したからそのまま公開するわけではありません。
必須項目、URL、公開日、重複、禁止語、出典をローカル側でも検証します。条件に合わなければ、S3公開とメール送信の前に停止します。朝の自動処理だからこそ、「たぶん大丈夫」で先へ進めない仕組みにしました。
最初は、生成できないことを失敗だと思っていました。作ってみると、怪しい内容を配らずに止まることも、日報機能の大事な動作でした。
同じ日の二重配信をstateで止めた
自動実行に加えて手動テストもするため、同じ日の日報が二度公開されたり、メールが二通届いたりする可能性があります。そこでS3の _daily/state/YYYY-MM-DD.json に実行状態を残します。
最初の実行は条件付き書き込みでロックを取り、同じ日のstateがすでにあれば daily.already_processed として終了します。メール送信直後に処理が落ち、送れたか分からない場合は unknown-after-send-attempt を残し、自動では送り直しません。
この仕組みは安心材料になりましたが、構築中は逆に再試験を止められました。失敗原因を直してすぐ実行しても、前回のstateがあるため先へ進まなかったのです。stateを消してよい条件は、次の記事で失敗の流れと一緒に整理します。
実際にメールと日報ページまでつながった
ローカルでは2026年7月22日分のDRY RUNを作り、外部のS3、SES、CloudFrontへ接続せずにWeb版、HTMLメール、テキストメールを確認しました。DRY RUNの出力は本番成功の証跡ではないため、実データとは分けて扱っています。
そのあとAWS上のLambdaを手動実行しました。CloudWatch Logsで daily.started から daily.completed まで進んだことを見て、S3の日報ページとstate、受信箱を順番に確認しています。ログだけで成功と決めず、公開ファイル、実行状態、受信結果の三つが同じ対象日でそろったところまで確認しました。
ただし、Schedulerによる翌朝5時の初回自動実行は別の確認です。スケジュールは有効にしましたが、手動実行の成功と定時実行の成功は同じにしません。次回の実行後にCloudWatch Logs、受信箱、日報ページをそろえて確認します。
個人開発だから、小さく始めることにした
企業で運用するなら、監視、通知先、DLQ、再試行、障害対応、承認、送信先管理までチームで決めるはずです。今回は自分専用の日報なので、まずは失敗をログとstateで追え、二重送信を避けられるところから始めました。
EventBridge SchedulerのDLQは初回構築では付けていません。メールの文章や選定基準も、実際に毎朝読んでから調整します。最初から全部を決め切るより、自分が読み続けられるかを見ながら育てるほうが、この仕組みには合っています。
今回ハマったポイント
最初のHTMLメールは、PCでは暗い見た目になり、スマートフォンでは背景が白く崩れました。最初はCSSの読み込み漏れを疑いましたが、生成されたHTMLをWeb版と見比べると、Web用のレイアウトをメールへそのまま持ち込んでいたことが原因でした。
メール側のレンダラーを分け、tableレイアウト、インラインCSS、最大幅600pxへ直しました。次に同じ配信を作るなら、SESへつなぐ前にPCとスマートフォンの両方でメールHTMLを開き、Web版とは別の表示物として確認します。
今回の学び
SAMEKORO Dailyは、公式ニュースの収集、OpenAIによる日報生成、検証、S3公開、CloudFront更新、SES送信をLambdaでつないだ仕組みです。作ってみて難しかったのは、Lambda単体のコードより、サービスから次のサービスへ渡る境目でした。
GUIで実行ロール、保存先、Identity、Schedulerを一つずつ開き、CloudWatch Logsとstateを照らし合わせたことで、処理がどこまで進んだかを追えるようになりました。自動化では、成功時の出力だけでなく、止まった位置を残すことも設計の一部だと分かりました。
次回は、IAM・S3・Secrets Managerで実際に遭遇した404、AccessDenied、failed stateを詳しく振り返ります。