【AWS】ZennのトレンドをSlackに流すBotをLambda + Serverless Frameworkで作ってみた
はじめに
Zenn に記事を書く回数が増えてきて、「どうすればトレンドに入りやすいんだろう?」と気になるようになりました。
毎日欠かさずチェックできるよう、Slackに自動通知されるBotを作ることにしました。
Slack には RSS を購読する標準の機能がありますが、今回は AWS サーバーレスアーキテクチャの学習を目的として、Lambda + Serverless Framework v4 で実装しました。
Lambda 中心の小さな構成なので、設定がシンプルな Serverless Framework を選択しています。
DynamoDB で通知履歴を管理し、EventBridge で定期実行する仕組みです。
この記事では、Bot の構成と実装のポイント、そして実際に作ってみて得られた学びをまとめていきます。
あくまで個人用に作った小さな仕組みなので、「こんな感じで作れるんだな」というサンプルとして読んでもらえるとうれしいです。
Serverless Framework とは
Serverless Framework は、サーバーレスアプリケーションを簡単に開発・デプロイするためのオープンソースツールです。
YAML ファイル(serverless.yml)の設定だけで、関数の定義、イベント(トリガー)、関連するリソースなどを一元管理し、まとめてデプロイできます。
全体構成と完成イメージ
できること
- 決まった時間に Zenn のトレンド記事が届く
- 一度通知された記事は届かないので、新着だけを追える

システム構成
全体の構成は次のとおりです。

各要素の役割を整理すると、以下のようになります。
| サービス | 役割 |
|---|---|
| EventBridge | Lambda を定期実行(今回は1日3回:9時、12時、18時) |
| Lambda(Ruby) | RSS 取得 → 通知済チェック → Slack 通知 |
| SSM Parameter Store | 設定値と Webhook URL を安全に保管 |
| DynamoDB | 通知済み記事を記録し、重複を防止 |
| Slack Incoming Webhook | 通知の送信先 |
動作環境
- Ruby ランタイム: AWS Lambda
ruby3.3 - Serverless Framework: v4 系
- 実行環境: AWS Lambda(ap-northeast-1)
Lambda の処理の流れ
Lambda がどのように記事を取得して通知するかを、順を追って見ていきます。
Lambda 起動時に、SSM Parameter Store から設定と Webhook URL を読み込みます(詳細は後述)。その後、以下の流れで記事の取得と通知を行います。
Zenn トレンド記事の取得
RSS フィードから記事を取得し、タイトル・URL・公開日時を Article オブジェクトに格納します。
fetched = @rss_client.fetch_all(config.feeds)
parsed_articles = fetched.flat_map do |feed|
@rss_parser.parse(feed_name: feed[:name], xml: feed[:xml]).map do |item|
Article.new(
title: item.title,
url: item.link,
published_at: item.published_at,
feed_name: item.feed_name
)
end
end
通知済みチェック
DynamoDB に保存された通知履歴をもとに、既に通知済みの記事を除外します。
各記事の URL を ID として、DynamoDB に存在するかチェックします。通知済みの記事を除外することで、未通知記事だけが残ります。
Slack への通知
未通知記事が 0 件なら何もせず終了します。1 件以上ある場合、記事を 1 つのメッセージにまとめて通知します。
メッセージは以下のような形式です:
Zennトレンド(N件)です
- タイトル1
https://zenn.dev/xxxx
- タイトル2
https://zenn.dev/yyyy
Slack の Incoming Webhook に JSON で POST することで、指定チャンネルに投稿されます。
通知履歴の保存
Slack に通知した記事を DynamoDB に保存します。次回以降の実行時に、この履歴をもとに重複通知を防ぎます。
各記事の URL、タイトル、通知日時を保存します。これで 1 回の実行サイクルが完了です。
SSM Parameter Store の設定
AWS Systems Manager(SSM)の Parameter Store は、設定情報を安全に管理できるサービスです。
今回は、RSS フィードの一覧と Slack Webhook URL を SSM に保存し、Lambda 起動時に読み込むようにしています。
SSM を使う理由
SSM Parameter Store を使うことで、以下のメリットがあります:
-
セキュリティ:Webhook URL を
SecureStringで暗号化して保存できる - 変更の容易さ:通知対象のフィードを変更する際、Lambda を再デプロイせずに SSM の値を更新するだけで済む
保存するパラメータ
SSM には2つのパラメータを保存します。
1. RSS フィードの設定
RSS フィードの一覧を JSON 形式で保存します:
{
"feeds": [
{ "name": "trend", "url": "https://zenn.dev/feed" }
]
}
トレンド全体(https://zenn.dev/feed)だけでなく、特定トピック(例:https://zenn.dev/topics/aws/feed)に変更することも可能です。SSM の JSON を更新するだけで、通知対象を切り替えられます。
2. Slack Webhook URL
以下のURLにアクセスし、「Create an App」→「From scratch」→ アプリ名とワークスペースを指定、で作成できます。
取得したWebhook URL は Parameter StoreにSecureString として暗号化して保存します。これにより、秘密情報を安全に管理できます。
Lambda での読み込み
Lambda では、環境変数から SSM のパラメータパスを取得し、Aws::SSM::Client で値を読み込みます。
config_param_name = ENV.fetch('CONFIG_PARAM_NAME')
slack_param_name = ENV.fetch('SLACK_WEBHOOK_PARAM_NAME')
config_json = get_parameter(config_param_name)
slack_webhook_url = get_parameter(slack_param_name, with_decryption: true)
parsed = JSON.parse(config_json)
Config.new(
feeds: parsed['feeds'] || [],
slack_webhook_url: slack_webhook_url
)
with_decryption: true を指定することで、SecureString として暗号化された Webhook URL を復号化して取得できます。
通知履歴テーブルの設定
通知済み記事を管理するために、DynamoDB に単一のテーブルを用意します。
テーブル構成
-
テーブル名:
zenn_trend_articles_${stage} -
パーティションキー:
article_id(記事の URL) -
課金モード:オンデマンド(
PAY_PER_REQUEST) - その他:TTL、GSI なし
パーティションキーとは、DynamoDB で各アイテムを一意に識別するためのキーです。データの保存場所を決定し、アイテムの検索に使用されます。今回は記事の URL を article_id として使用しています。
保存するデータ
各記事について、以下の情報を保存します。
-
article_id(パーティションキー):記事の URL -
title:記事のタイトル -
notified_at:通知日時
テーブル定義
serverless.yml では、以下のように定義します。
resources:
Resources:
ZennTrendArticlesTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: zenn_trend_articles_${self:provider.stage}
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: article_id
AttributeType: S
KeySchema:
- AttributeName: article_id
KeyType: HASH
Lambda からのアクセス
Lambda では、環境変数からテーブル名を取得してアクセスします。
repo = Repository.new(table_name: ENV['ARTICLE_TABLE_NAME'])
# 通知済み記事を除外
new_articles = repo.filter_new_articles(parsed_articles)
# 通知後、記事を保存
repo.save_articles(new_articles)
Serverless Framework の設定
Serverless Framework での設定について、重要なポイントをまとめます。
ランタイムと環境変数
まずはベースとなる provider セクションです。
service: zenn-trend-notifier
frameworkVersion: '4'
provider:
name: aws
runtime: ruby3.3
region: ap-northeast-1
stage: ${opt:stage, 'dev'}
environment:
CONFIG_PARAM_NAME: /zenn-trend/${self:provider.stage}/config
SLACK_WEBHOOK_PARAM_NAME: /zenn-trend/${self:provider.stage}/slack-webhook-url
ARTICLE_TABLE_NAME: zenn_trend_articles_${self:provider.stage}
ランタイムは ruby3.3、リージョンは東京(ap-northeast-1)を指定しています。
stage は --stage dev / --stage prod で切り替えられ、SSM のパスや DynamoDB テーブル名も stage に応じて自動で変わります。
Lambda 関数の定義
Lambda 関数は以下のように定義します。
functions:
zennTrendNotifier:
handler: src/handler.main
timeout: 30
memorySize: 256
logRetentionInDays: 30
ハンドラは src/handler.rb の main メソッドを指定しています。
RSS 取得など外部 HTTP 通信を含むため、timeout は 30 秒、メモリは 256MB に設定しています(余裕を持たせた設定です)。
IAM ロール
Lambda 実行ロールには、SSM と DynamoDB に対する最小限の権限だけを付与します。
provider:
iam:
role:
statements:
- Effect: Allow
Action:
- ssm:GetParameter
Resource:
- arn:aws:ssm:${self:provider.region}:${aws:accountId}:parameter/zenn-trend/${self:provider.stage}/*
- Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:BatchGetItem
- dynamodb:PutItem
- dynamodb:BatchWriteItem
Resource:
- arn:aws:dynamodb:${self:provider.region}:${aws:accountId}:table/zenn_trend_articles_${self:provider.stage}
SSM は /zenn-trend/{stage}/* 配下のみ GetParameter を許可し、DynamoDB は対象テーブルに対する Get/Put/Batch 操作のみを許可しています。
CloudWatch Logs の権限は、Serverless Framework が自動で付与します。
定期実行の設定
EventBridge のスケジュール実行は、prod ステージのみで有効化します。
resources:
Conditions:
IsProd:
Fn::Equals:
- ${self:provider.stage}
- prod
Resources:
ZennTrendNotifierSchedule:
Type: AWS::Events::Rule
Condition: IsProd
Properties:
ScheduleExpression: cron(0 0,3,9 * * ? *)
State: ENABLED
Targets:
- Arn:
Fn::GetAtt:
- ZennTrendNotifierLambdaFunction
- Arn
Id: ZennTrendNotifierTarget
ZennTrendNotifierPermissionForEvents:
Type: AWS::Lambda::Permission
Condition: IsProd
Properties:
FunctionName:
Ref: ZennTrendNotifierLambdaFunction
Action: lambda:InvokeFunction
Principal: events.amazonaws.com
SourceArn:
Fn::GetAtt:
- ZennTrendNotifierSchedule
- Arn
IsProd という条件を使って、prod ステージのときだけ EventBridge によるスケジュール実行を有効化しています。
dev ステージではスケジュールを作らず、手動 Invoke で動作確認する運用です。
DynamoDB テーブル
DynamoDB テーブルも resources セクションで定義します。
resources:
Resources:
ZennTrendArticlesTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: zenn_trend_articles_${self:provider.stage}
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: article_id
AttributeType: S
KeySchema:
- AttributeName: article_id
KeyType: HASH
デプロイと動作確認
デプロイと動作確認の手順をまとめます。
前提条件
以下が準備できていることを前提としています:
- AWS CLI がインストール・設定済み
- AWS プロファイルが設定済み
- Slack Webhook URL を取得済み
SSM パラメータの作成
パラメーターの設定と Slack Webhook URL を SSM に登録します。
# RSS フィードの設定
aws ssm put-parameter \
--name /zenn-trend/prod/config \
--type String \
--value '{"feeds":[{"name":"trend","url":"https://zenn.dev/feed"}]}' \
--profile your-profile \
--region ap-northeast-1
# Slack Webhook URL
aws ssm put-parameter \
--name /zenn-trend/prod/slack-webhook-url \
--type SecureString \
--value 'https://hooks.slack.com/services/xxx/yyy/zzz' \
--profile your-profile \
--region ap-northeast-1
Webhook URL は実際の値に置き換えてください。
デプロイ
Serverless Framework でデプロイします。
sls deploy --stage prod --aws-profile your-profile
デプロイが成功すると、Lambda 関数、DynamoDB テーブル、EventBridge ルールが作成され、AWS コンソールから各リソースを確認できます。
動作確認
手動実行での確認
まず、手動で Lambda を実行して動作を確認します。
sls invoke --stage prod --aws-profile your-profile -f zennTrendNotifier
正常に動作していれば、以下の状態になります:
- Slack の指定チャンネルに Zenn トレンド記事が投稿される
- DynamoDB テーブル(
zenn_trend_articles_prod)に通知済み記事が保存される - CloudWatch Logs にログが出力される
自動実行(EventBridge)の確認
デプロイ後、EventBridge によるスケジュール実行が自動で有効化されます。
スケジュール設定:
-
cron 式:
cron(0 0,3,9 * * ? *) - 実行時刻:UTC 0:00, 3:00, 9:00(= JST 9:00, 12:00, 18:00)
次の実行時間以降に次の2点を確認します。
- CloudWatch Logs で Lambda が自動実行されているか
- Slack にトレンド記事が届いているか

まとめ
Zenn のトレンド記事を Slack に自動通知する Bot を作りました。
実際に運用してみると情報収集が楽になり、Serverless Framework v4 や AWS サービスを組み合わせたサーバレス構成を実践できたのも良い学びでした。
今回のコードは GitHub で公開しています。何かの参考になれば嬉しいです。
最後までご覧いただき、ありがとうございました。
Discussion