こんにちは、グロースエクスパートナーズの@gxp-nyakyamuraです。
普段の開発業務に加えて、社内のテックブログ運営にも関わっています。記事をレビューしたり、公開された記事を社内に紹介したりする仕事です。
一つ一つの作業は、そこまで大変ではありません。でも、本業の合間にやるとなると、「地味に面倒」が積み重なるんですよね。
そこで昨年、記事のAIレビューと、社内向けの告知文生成・投稿を行うSlackアプリを作りました。使ったのは Deno Slack SDKとAzure OpenAI。企画・技術選定から実装・リリース・保守まで、自分一人で担当しています。
2025年のアドベントカレンダーに間に合うように導入してから、約1年が経ちました。今回は、何を自動化したのか、なぜこの構成にしたのか、実際に運用してどうだったのかをまとめます。
😵 作る前:AIは使っていたけど、その前後が手作業だった
レビュー依頼先も、レビューのやり方も、人に依存していた
もともと社内Slackにはテックブログ用のチャンネルがあり、記事を書いた人が運営担当者にメンションして、レビューを依頼していました。
ただ、「誰が運営担当者なのか」が十分に周知されているわけでもなく、執筆者が依頼先に迷うこともありました。レビュー基準も明確には決まっておらず、担当者ごとに確認の仕方が違う状態でした。
運営側では、各々がAIに記事を読ませてレビューし、その結果を確認してSlackで返信していました。AI自体は役に立っていましたが、毎回プロンプトを考え、記事を渡し、結果をSlackに貼るのはまだまだ地味に面倒です。
そこで、まずは運営用の標準レビュープロンプトを作りました。Qiita記事のURLに .md を付けてMarkdownを取得し、共通の観点でレビューしてもらう、というものです。
実際に使っていたプロンプトは少し長めなので、ここでは折りたたんで載せます。記事のURLから本文を取り、七つの観点で確認し、概要・指摘・推奨修正をMarkdownで出力する内容です。
実際に使っていたレビュープロンプト
# タスク
Qiita記事をレビューしてください。以下の手順と出力仕様に従ってください。
## 入力
- 記事URL: {ここにURLを入力する!}
- 記事本文は `curl "{URL}.md"` で取得してください。認証が必要な場合や取得に失敗した場合は理由を明記し、レビューではなくエラー報告を行ってください。
## レビュー観点
1. 誤字・脱字、文法の誤り
2. 用語・表現の統一(専門用語、固有名詞、敬体/常体など)
3. Markdown構文の適切さ(見出し階層、リスト、コードブロック)
4. コンプライアンス(著作権・機密情報・引用元の明記)
5. 内容面の妥当性(学び・知見・再現性・価値が伝わるか)
6. タイトルとセクション構成の分かりやすさ
7. 会社として社外公開できる品質か
## 出力
`article_review.md` を生成してください。
1. `# レビュー結果`
2. `## 概要`(記事のサマリーと総評)
3. `## 指摘`(各観点の指摘。なければ「特になし」)
4. `## 推奨修正`(修正案・追記案。なければ「特になし」)
- 根拠を示す場合は該当箇所を引用してください。
- 生成したファイルはプロジェクト直下に保存してください。
でも、まだ人間がやることは残っています。
- 記事をAIに渡してレビューを実行する
- 出力を読んで、Slackに貼る内容を選ぶ
- 執筆者に返信し、運営メンバーに確認を依頼する
「これ、Slackからそのまま実行して、結果もSlackに返してくれればよくない?」と思ったのが、アプリ開発のきっかけです。
特にアドベントカレンダー期間は毎日記事が出ます。通常業務の合間にこの作業を毎日続けるのは大変です。
記事公開後の「紹介文を考えて投稿する」も地味に重い
もう一つの課題が、公開された記事の社内展開です。
当時は、公開に気づいた運営担当者が、全体チャンネル向けの紹介文を手で書いて投稿していました。気づいた人がやる運用なので、展開が漏れることもありました。
紹介文は数行ですが、意外と頭を使います。
「どこを見どころとして紹介しよう?」
「この書き方だと、執筆者に失礼じゃないかな?」
「技術に詳しくない人にも伝わるかな?」
もちろん、AIに書いてもらうこともできます。ただ、その都度記事を渡し、プロンプトを入力して、出力を整えてSlackに貼るのも、やっぱり少し面倒です。
どうせ作るなら、こちらも一緒に自動化することにしました。
⚙️ 技術選定:Deno Slack SDKがちょうどよかった
主に比較したのは、社内の既存Slackアプリと同じようにBoltで開発する方法と、Deno Slack SDKを使う方法です。
Boltなら通常のWebアプリと同じように自由度高く設計できます。一方で、今回は記事を取得してAIに渡し、結果をSlackへ返すのが中心です。軽量な社内ツールには少し大がかりに感じました。ホスティングするサーバーを用意して運用する必要があり、費用や社内申請も発生します。クラウド環境の構築に慣れていない自分には、その準備もハードルでした。
そこで、Slackの中で動く範囲に用途を絞り、Deno Slack SDKを選びました。今回必要な機能は、SDKのトリガー・ワークフロー・関数で十分組み立てられます。自由度よりも、サーバーを用意せず、慣れたTypeScriptですぐ開発を始められることを優先しました。
サーバーを自分で用意しなくていい
Slack側が用意してくれたクラウドのサンドボックス環境に、Slack CLIの slack deploy コマンド一発でデプロイできます。自分でサーバーを立てないので、デプロイしている感覚すらないほど手軽でした。Slack公式:Deploying to Slack
当社では既存のSlack契約の範囲で利用でき、このアプリのためのホスティング費用は追加でかかっていません。クラウドや本番環境の準備に時間をかけず、必要な開発のみに集中して素早くリリースできたのは大きなメリットでした。
TypeScriptで書けるし、Denoも触ってみたかった
普段からJavaScript / TypeScriptを使っているので、慣れた言語で開発できることも決め手でした。
Node.jsやBunは業務で使ったことがあり、以前Bunを業務で使って感じたことも記事にしています。一方で、Denoにはまだ触れる機会がありませんでした。
JS/TSのエコシステムには興味があるので、「社内の困りごとを解決しながら、Denoも触れる」というのも個人的な動機の一つでした。会社の実案件では、Denoに触れられる機会はなかなかありません。
ちなみにDenoには、ネットワークやファイルなどへのアクセスを明示的に許可する権限モデルがあります。Slack側のクラウドサンドボックス環境とDenoは、セキュリティ面でも相性が良さそうだと感じました。Deno公式:Security and permissions
🧩 仕組み:トリガー・ワークフロー・関数に分けて書く
Deno Slack SDKで個人的に分かりやすかったのが、「何をきっかけに」「どの順番で」「何をするか」を分けて書けることです。
| 要素 | 役割 | 今回の例 |
|---|---|---|
| トリガー | ワークフローを起動するきっかけ | リンクトリガー(SDK上はShortcut型)、🚀リアクション |
| ワークフロー | 関数を順番に呼び、入出力をつなぐ | フォーム → 記事取得 → AIレビュー → 結果投稿 |
| 関数 | 一つ一つの具体的な処理 | 記事取得、AI呼び出し、Slack投稿 |
ワークフローには、SDKが用意するフォーム表示やメッセージ送信の関数と、自作の関数を組み合わせられます。Slack公式:Creating workflows
トリガー、ワークフロー、関数と、書く場所もSDKの構成に沿って自然に分かれています。自分でディレクトリ構成やアーキテクチャを一から決めなくても、それぞれの役割に合わせて実装を置けるので、コードの見通しを保ちやすいのも良かったです。
リポジトリも、おおむね次のような構成になっています。主要部分だけ抜粋しています。
manifest.ts # ワークフロー・関数・権限・外部通信先
triggers/
qiitaReviewTrigger.ts # レビュー用のリンクトリガー(Shortcut型)
reactionAnnounceTrigger.ts # 告知用のリアクショントリガー
workflows/
qiitaReviewWorkflow.ts # レビューの処理順
techBlogAnnounceWorkflow.ts # 告知の処理順
functions/
fetchArticle.ts # 記事本文の取得
reviewArticleWithAI.ts # AIレビュー
generateAnnouncementTexts.ts # 告知文の生成
postAnnounce.ts # 社内告知とSNS担当への依頼
utils/
generateAnnouncementWithAI.ts # 告知生成のプロンプト・スキーマ
hatenaArticle.ts # はてなブログ本文の抽出
reviewChecks.ts # 機械的な記事チェック
reviewPolicy.ts # 指摘の検証・判定
formatReviewResult.ts # レビュー結果の整形
📝 機能1:記事のAIレビュー
レビューの起動方法は、Slackの用語ではリンクトリガーです。実装では TriggerTypes.Shortcut 型のトリガーを作り、Slack CLIから作成したShortcut URLをチャンネルに共有したり、ブックマークに登録したりして使います。そのリンクを開くとフォームが表示され、記事URLと任意の追加メモを入力できます。SDKの型名が Shortcut なので少し紛らわしいですが、Slack公式ドキュメントでもこの形式をリンクトリガーとして説明しています。Slack公式:Creating link triggers
Slack上では、作成したレビュー用ワークフローをチャンネルの「ワークフロー」タブから呼び出せます。
フォームではQiita記事のURLと、必要であれば追加プロンプトを入力します。
もともとの標準プロンプトで行っていた「Markdownを取得して、共通の観点でレビューする」を、そのままアプリの処理にした形です。アプリ内では curl ではなく fetch を使っています。
レビュー観点は次の七つです。
- 誤字・脱字 / 文法
- 用語・表現の統一
- Markdown構文の適切さ
- コンプライアンス
- 内容妥当性
- タイトル / セクションの分かりやすさ
- 社外公開可否
結果はSlackのスレッドに投稿され、レビューを依頼した人と運営チームにメンションが飛びます。運営が確認して承認したら、執筆者がQiitaで全体公開します。
レビュー結果には、AIによる評価であることを明記し、概要・技術的フィードバック・観点別の評価・指摘・改善案をまとめています。
公開の判断は、今も人間が行っています。 AIの出力に「公開可」とあっても、自動でQiitaを公開する仕組みにはしていません。
AIの応答は、自然言語だけで受け取らない
実装で大事だったのが、AIから受け取るデータの構造を決めておくことです。
人間がチャットで読むなら、自由な文章でもよいと思います。でも、アプリ側で結果を整形してSlackに投稿するとなると、「今回は見出しが違う」「必要な項目がない」では扱いづらい。
そこで、Chat Completionsの response_format にJSON Schemaを指定し、strict: true を設定しています。Structured Outputsは、応答を指定したJSON Schemaに沿わせる仕組みです。OpenAI公式:Structured model outputs
レビュー結果の構造は、概略として次のようになっています。
{
"summary": "記事全体のレビュー要約",
"technical_feedback": "技術的な価値や良かった点などのフィードバック",
"findings": [
{
"aspect": "用語・表現の統一",
"severity": "Needs Fix",
"quotes": ["問題箇所の原文"],
"problem": "何が問題なのか",
"recommendation": "具体的な修正方針"
}
]
}
取得した応答は JSON.parse し、さらにZodで検証してから使います。出力形式を指定していても、空の応答やAPIエラーなどの処理は必要です。また、形式が揃うことと、内容が正しいことは別です。
現在の実装では、その点を補うために次の処理も入れています。
- AIが引用した文字列が、実際の記事本文またはタイトルに存在するか確認する
- 見出し階層や一部の秘密鍵・トークン形式などは、コードで機械的にチェックする
- AIの指摘と機械的なチェック結果を統合し、重複する指摘を整理する
- 統合した指摘から、アプリ側で観点別の評価と公開に関する推奨を導出する
これでも、引用箇所に対する解釈の正しさまで保証できるわけではありません。ただ、AIの文章をそのまま表示するより、執筆者が「どこを、どう直すのか」を確認しやすくなります。
🚀 機能2:記事の社内告知
記事を公開したら、Slackの対象メッセージに 🚀(:rocket:)リアクションを付けます。これが告知ワークフローの起点です。
ただし、リアクションを付けた瞬間に全体チャンネルへ投稿するわけではありません。運営が必要な情報を入力し、生成された案を確認・選択するステップを挟んでいます。
記事の見どころは、一つだけ生成して終わりにはしていません。最初から三つの案を出して、そこから運営が選択する構成にしました。一通りだけだと、それがよい案なのか判断しにくく、再生成処理を別途用意するのも手間がかかります。三通りほどあれば、その中から少なくとも一つは使いやすい案を選べることが多い、という考え方です。
現在は、三つの見どころ案に加えて、全案共通の情報も生成しています。
- 🎯 こんな人向け
- ⏱️ 読了目安
- 💡 この記事で分かること
運営は内容を確認し、見どころ案を一つ選ぶと、決められたフォーマットで全体チャンネルに投稿できます。読了目安はAIによる推定なので、あくまで目安です。
実際の告知投稿はこのようになります。執筆者へのメンションや記事情報、見どころ、読者の目安が決められたフォーマットでまとまり、投稿後はSNS掲載の依頼まで続けて行われます。
さらに、投稿のスレッドで広報チームへメンションし、SNS掲載を依頼します。SNSへの投稿自体は、広報チームが行う運用です。
アドベントカレンダー用には「何日目か」を入力する欄も用意しました。普段の告知と同じ流れで、アドカレ向けのヘッダーに切り替えられます。
🔧 運用しながら、必要なところだけ拡張した
はてなブログにも対応
GxP社員のテックブログ投稿先は基本的にQiitaのみなので、アプリ側でもQiitaのみに対応していました。しかしアプリをリリースしたあと、グループ会社Graat代表の鈴木雄介さんが、個人のはてなブログから記事を公開していることに気づきました。それまでは、雄介さんの記事だけ運営担当者が社内に展開していました。
せっかくの代表の記事ですし、毎年アドベントカレンダーにも参加してくださる常連なので、これもアプリで対応して同じ宣伝フローに載せたいと考えました。そこで今回、雄介さんのはてなブログに対応しました。
はてなブログの記事も、Qiitaと同様に fetch で取得して本文を取り出せば、同じ告知フローに載せられます。コードとしても無理なく対応できそうでした。
QiitaならURLに .md を付けて取得できますが、はてなブログでは同じ方法を使っていません。対象ページのHTMLを取得し、記事タイトルと本文部分を抽出して、読みやすいテキストにしてAIへ渡します。
対応したのははてなブログだけです。「あらゆるブログに対応する汎用ツール」までは作り込まず、運営で必要になった対象に絞りました。
今回対応した鈴木雄介さんの最新記事は、Jevが示す「ソフトウエア部品としてのAIモデル」という考え方を紹介しています。こちらもぜひ読んでみてください!
外部通信先は、manifestにも登録する
Slackにホストするアプリでは、アクセスする外部ドメインを manifest.ts の outgoingDomains に宣言します。Slack公式:Controlling permissions for Admins
今回の設定は、次のような形です。
outgoingDomains: [
Config.API_DOMAIN, // Azure OpenAI
Config.QIITA_DOMAIN, // qiita.com
Config.HATENA_BLOG_DOMAIN, // 対象のはてなブログ
],
Denoには、ネットワーク権限を特定のホストに絞って実行する仕組みがあります。Slackにデプロイするアプリでは、さらに outgoingDomains に外部通信先を宣言します。このアプリでは、Azure OpenAI、Qiita、そして対象のはてなブログだけを登録しました。Deno公式:Security and permissions Slack公式:Controlling permissions for Admins
🧑💻 AIを使って、一人で一通り作った
このアプリは、企画・技術選定・設計・実装・リリースまで、自分一人で進めました。実装にはAIコーディングをかなり活用しています。
SDK固有の書き方は、公式ドキュメントを確認しながらAIに実装を手伝ってもらい、自分は「どういう入力を受けて、どんな結果を返すか」「運営でどう使うか」に集中できました。
結果として、本業に特に支障を出すことなく、業務の合間に開発し、2025年のアドベントカレンダーに間に合うようにリリースできました。
小さな開発では、意思決定と実装を近づける効果が大きかった
AIを使うと、コードを書く速度はかなり上がります。一方で、複数人で開発すれば、タスクの分担、認識合わせ、ミーティング、PRレビューなどは必要になります。
今回は、作りたいものも運用上の困りごとも自分が把握していました。判断して、そのまま実装して、Slackで試せる。この距離の短さが効いたと思います。
今回は、社内の運営作業を少し便利にする、小さなアプリです。顧客向けの大規模システムと同じ進め方をする必要はありませんでした。
ドキュメントは、後から保守するために必要最小限の実行方法や環境設定を残しています。実際、約1年ぶりにアプリを改修したときには、細かい手順を自分では忘れていました。それでも、残してあった説明をAIに読み込ませることで、環境構築やデプロイなどをフォローしてもらえました。ドキュメントを最小限にしていても、こうした手がかりがあると、AIと一緒に作業を再開しやすいと感じました。
このツール自体が直接売り上げを生むわけではないので、開発・保守にかける労力も含めて、必要最小限のコストに抑えるべきものだと考えました。形の整った綺麗なドキュメントを作る必要性は、全くありません。
今回の経験から、範囲が明確な小規模開発なら、一人ないし少人数でAIを活用し、一通り進めるスタイルはかなり相性がよいと感じています。対クライアント案件でも、品質や引き継ぎなどの条件を満たせる範囲で、この進め方を取り入れる余地はありそうです。
🌱 約1年運用して、どうだった?
レビュー:依頼先に迷わず、最初のフィードバックをすぐ受け取れる
まず、執筆者が「誰にレビューを頼めばいいんだっけ?」と迷うことが減りました。レビュー用の入口から記事を送れば、AIレビューの結果が返り、運営にも通知されます。
以前は、運営の誰かの手が空くまで、レビュー依頼が数日止まってしまうこともありました。今は少なくとも、最初のフィードバックを人の着手待ちにせず受け取れます。
運営側では、個別にプロンプトを投げ、出力を選別してSlackに貼る作業がなくなりました。誤字脱字などの基本的な確認をAIに手伝ってもらえることで、人間は内容や公開判断に集中しやすくなっています。
AIがすべての誤りや機密情報を見つけられるわけではありませんが、人間がゼロから全部確認する負担を減らすという点では、十分役に立っています。
2025年のアドベントカレンダーでは、レビュー待ちが原因で記事を公開できない、ということは発生しませんでした。毎日記事が出る時期に、この流れが用意されていたのは助かりました。
社内告知:文章を考える手間と、投稿する心理的な負担が減った
公開後は、リアクションを付け、情報を入力し、生成された案を選ぶ。これで社内投稿と広報への依頼まで進められます。
紹介文を一から考え、投稿フォーマットを手で整える作業はなくなりました。運営は生成された案を確認して、一つ選ぶだけです。
そして、個人的に思った以上に効いたのが、自分のアカウントで数百人が参加するチャンネルに投稿する、という心理的な負担が減ったことです。
自分の文章として紹介すると、「この表現で大丈夫かな」と気になってしまいます。今は運営が内容を確認したうえで、Slackアプリのアカウントから投稿し、宣伝文がAI生成であることも明記しています。
確認する責任は残りますが、毎回ゼロから言葉を選び、自分の名前で送り出す負担は軽くなりました。作業時間だけでなく、こういう頭のリソースも減らせたのは嬉しかったです。
完全自動化しなくても、十分に効果があった
二つの機能とも運営に定着し、約1年、大きな問題なく使い続けられています。本業の合間に運営作業を進めやすくなり、ストレスも減りました。
ただ、AIレビューには今も「ここはもう少し見てほしいな」と思う部分があります。そこは人間がレビューすればよいと考えています。通常時の投稿は月に1〜2本程度なので、最終確認を人間が担う運用で無理なく回せています。
| 作業 | 導入前 | 導入後 |
|---|---|---|
| レビュー依頼 | 担当者を探してメンション | 共通のリンクから依頼 |
| AIレビュー | 運営が個別に実行 | フォーム送信後に自動実行 |
| 結果の共有 | 運営がSlackに転記 | スレッドに自動投稿・メンション |
| 公開判断 | 運営が確認・承認 | 運営が確認・承認 |
| 社内告知 | 紹介文を考えて手で投稿 | AIの案を確認・選択して自動投稿 |
| SNS掲載依頼 | 別途依頼する | 告知スレッドで広報へ自動メンション |
まとめ
今回作ったのは、技術的には「記事を取得して、AIに渡して、Slackに投稿する」小さなアプリです。でも、運営で繰り返していた作業を一つの流れにまとめるだけで、かなり楽になりました。
Deno Slack SDKは、今回のようにSlack内で完結する用途にはちょうどよく、ホスティングを自分で用意せず、慣れたTypeScriptで開発できたのがよかったです。
AIの出力を人間が毎回コピーして運んでいるなら、その前後にも自動化できる作業があるかもしれません。
本業の合間の「地味に面倒」を減らして、無理なく運営を続けられるようにする。 自分にとっては、それを実現できたことが、このアプリを作って一番よかった点です。
