こんにちは。お久しぶりです。 技術統括本部 DXビジネス開発部の青木です。
本記事は OPTiM TECH BLOG Advent Calendar 2025 Day 15 の記事です。
今回は、Slackでの議論を自動でRedmineチケットに変換するツールを作ってみたので、その内容を共有したいと思います。 日々の業務で「あれ、このSlackのスレッドってチケット化したっけ?」となることが多かったので、そんな悩みを解決するために開発しました。
それでは、早速紹介していきます。
こんなことありませんか?
障害や、なんか違和感のある画面や挙動だなぁと思ったとき...
そのままSlackで議論し続けてしまうことってありませんか?
私はあります。 Redmineのチケットに起票してください。。と言ったときには時すでにお🍣
大量のスレッドや山のような情報をサマライズしてチケット化するだけで十数分かかってしまいますし、、そもそもチケット化を忘れてしまうことも..
....
というときに使うやつです。
一旦結果から
実際の動作フローを説明します。
- Slackに投稿したスレッドや通常の投稿に対して、特定のスタンプ(リアクション)を押す

- Reacji Channeler (リアク字チャンネラー)が特定のスタンプを検知し、自動的に別のSlackチャンネルに投稿を転送する

- 別のSlackチャンネルへの投稿をトリガーとして、AWS API Gateway経由でAWS Step Functions が起動する

- AWS Step Functions の最初のステップで、AWS Lambda (以下Lambda)が実行される
- Slackの投稿内容を取得
- S3からあらかじめ用意しておいたプロンプトテンプレートを読み込む
- AWS Bedrockを使って、投稿内容を要約しチケット情報を抽出する
- 次のステップで、Redmine API連携用のLambdaが起動し、要約された文章やタイトル、カテゴリなどを含めてチケットを自動作成する

このように、スタンプを押すだけでSlackの議論が自動的にRedmineチケット化される仕組みになっています。
構成図

それぞれの解説
Reacji Channeler (リアク字チャンネラー)
これはSlack公式の機能です。 事前に登録しておくと特定のスタンプで別のチャンネルに投稿してくれる機能です。 結構便利。
例えば分報とかで、気になる記事とか、それこそバグとかそういう話題になったときに別のチャンネルにわざわざ引っ越したりすることが多いと思いますが、、そういったときに便利です。
これで、 所定のチャンネルに投稿 します。
Slack Event Subscription
Slackにはチャンネルに投稿したメッセージとかを受け取るOutgoing Webhook的な機能があり、Slack Appを自身で作成している場合に利用できます。

ここに AWS API Gateway のエンドポイントを入力すれば、Slackに投げたテキスト情報を送信 できます。
Verifyと書かれていますが、一度認証をする必要があるので、 AWS Step Functionsを用いている場合は Expressモードにして直接レスポンスを返却するようにしてください。
AWS Step Functions
私の大好きなサービスです。いっぱい使ってます。
AWS Step Functionsは、複数のAWSサービスを組み合わせてワークフローを構築できるオーケストレーションサービスです。 今回のような「Slackから情報取得 → AI処理 → Redmineに投稿」といった一連の処理を、視覚的に管理できるのが大きなメリットです。
WorkflowタイプとSlack認証の課題
当初は Slack Event Subscriptionの認証(Verify)に対応するため Express Workflowsを採用していました。
Slack側は、イベントを送信した際に3秒以内にレスポンスを返すことを要求します。Standard Workflowsでは同期的なレスポンス返却ができないため、Express Workflowsを使って直接レスポンスを返す必要がありました。
しかし、Express Workflowsは実行コストが高いため、検証も兼ねて現在はStandard Workflowsに切り替えています。
今後の改善案 本来であれば、以下のようなアーキテクチャが理想的です。
- Slack認証を受け取る部分だけExpress Workflowsで構成
- Express側からAWS SQSにメッセージを投げて、処理を即座に完了させる
- SQS経由で、AI処理やRedmine投稿などの時間がかかる処理を行うStandard Step Functionを非同期で起動
このように分離することで、Slack認証の要件を満たしつつ、コストを抑えることができます。
ワークフローの構成

ASL(Amazon States Language)というJSON形式で定義しています。主なステップは以下の通りです
- Slack認証チェック - Slackからの正規リクエストかを検証
- 不要なイベントのフィルタリング - bot投稿やリトライイベントなど、処理不要なシグナルを除去
- スレッド情報取得Lambda - Slackのスレッド全体を取得
- AI要約Lambda - S3からプロンプトを読み込み、BedrockでAI処理を実行
- Redmine投稿Lambda - 要約結果をもとにチケットを作成
各ステップでエラーが発生した場合は、リトライ処理や適切なエラーハンドリングを行うように設定しています。
まだ完全に整備できているわけじゃないので、今後も改善を続けていきます。
LambdaとS3とBedrock
ここはまとめて解説しますが、
今回チケットにするにあたって、 プロンプトを自由にカスタマイズしたいな と思ったので, S3にプロンプトを置いておいて、そこからユーザプロンプトを読み込むようにしてみました。
以下のSlackメッセージを解析し、チケット管理システム用の情報を抽出してください。 ## 抽出する情報 1. **件名 (subject)** - 必須 - メッセージの内容から簡潔なタイトルを生成してください - 30文字程度を目安にしてください 2. **詳細説明 (description)** - 必須 - メッセージの内容をチケット用に整形してください - 下記に記載の詳細説明のテンプレートに従って記載してください 3. **優先度 (priority)** - オプション - メッセージから優先度を判断してください(低/中/高/緊急) - 緊急性を示すキーワード(至急、早急、ASAP等)がある場合は考慮してください - 判断できない場合は空欄で構いません 4. **担当者 (assigned_to)** - オプション - メッセージ内に@メンションや担当者指定があれば抽出してください - ない場合は空欄で構いません 5. **カテゴリ (category)** - オプション - メッセージの内容から適切なカテゴリを判断してください - 例: UI/UX、バックエンド、インフラ、ドキュメント等 6. **トラッカー (tracker)** - オプション - メッセージの種類を判断してください - 例: バグ、機能、サポート、タスク - バグ報告であれば「バグ」、新機能の要望であれば「機能」等 7. **開始日 (start_date)** - オプション - メッセージ内に開始日の指定があれば抽出してください(YYYY-MM-DD形式) - 「明日から」「来週月曜から」などの表現は具体的な日付に変換してください - ない場合は空欄で構いません 8. **期限日 (due_date)** - オプション - メッセージ内に期限の指定があれば抽出してください(YYYY-MM-DD形式) - 「今週中」「月末まで」などの表現は具体的な日付に変換してください - ない場合は空欄で構いません ## Slackメッセージ {{slack_message}} ## 詳細説明テンプレート ### 障害やバグなどの場合 \`\`\`markdown ### 事象 {背景や、起こった事象を簡潔にまとめる} ### 再現手順 {再現方法、手順を明確に} ### 影響範囲 {このバグや障害がどう影響しているのか} ### 発生環境 {local, prodなど} ### 原因 {わかった段階で記述する。最初の記述は任意} ### その他 ※このチケットはAIによって自動生成されました \`\`\` ### 新規機能や提案 \`\`\`markdown ### 背景 {その機能や要望の背景情報があれば記載} ### 機能概要 {機能における説明、および概要} ### 完了条件 {どうなっていれば良いか、結論を箇条書きで記載} ### その他 ※このチケットはAIによって自動生成されました \`\`\` ## 注意事項 - 日付表記は必ずYYYY-MM-DD形式で出力してください - オプション項目は情報がない場合は空欄(null)で構いません - メッセージから推測できる場合は積極的に情報を補完してください - 担当者名はSlackのユーザー名をそのまま使用してください(@は除く)
これをS3から取得してAWS Bedrockに流しています。
システムプロンプトは以下です。
あなたはチケット管理システムのアシスタントです。
Slackメッセージからチケット情報を抽出し、以下のJSON形式で出力してください。
出力はJSON形式のみとし、説明文などは含めないでください。
<schema>
{
"subject": "チケットのタイトル(必須)",
"description": "チケットの詳細説明(必須)",
"priority": "優先度(例: 低, 中, 高)",
"assigned_to": "担当者名またはID",
"category": "カテゴリ",
"tracker": "トラッカー(例: バグ, 機能, サポート)",
"start_date": "開始日(YYYY-MM-DD形式)",
"due_date": "期限日(YYYY-MM-DD形式)"
}
</schema>
ユーザーから提供されるプロンプトテンプレートに従い、Slackメッセージから必要な情報を抽出してください。
これらの投げて要約させている感じです。
Redmine
最後のステップは、AI要約された情報をRedmineにチケットとして投稿する部分です。
Redmineには REST API が用意されており、HTTPリクエストでチケットの作成や更新が可能です。
http:// https://www.redmine.org/projects/redmine/wiki/rest_api
Redmine APIの利用方法
APIを利用するには、Redmineのアクセストークン(API Key)が必要です。
- Redmineの個人設定画面からAPIアクセスキーを生成
- このトークンをHTTPリクエストのヘッダーに含めて認証
- JSON形式でチケット情報を送信
トークンの管理
セキュリティ上、トークンをコードに直接書くのは避けるべきです。今回は AWS Secrets Manager を使って安全に管理しています。
- Terraformを使ってSecrets Managerにトークンを登録
- Lambda実行時にSecrets Managerからトークンを取得
- 環境変数に平文で置かないことで、セキュリティを担保
このアプローチにより、トークンの漏洩リスクを最小限に抑えつつ、運用しやすい構成になっています。
Secretとかインフラはどうやってデプロイしてる?
もちろんTerraformです。
今回のシステムでは、以下のAWSリソースをTerraformで管理しています
- AWS Step Functions - ワークフロー定義
- AWS Lambda - 各処理を行う関数
- Amazon ECR - Lambdaのコンテナイメージ保管
- AWS API Gateway - Slackからのイベント受信エンドポイント
- AWS Secrets Manager - RedmineトークンやSlackトークンの管理
- IAM Role/Policy - 各サービスの権限設定
- S3 - プロンプトテンプレートの保管

Secrets Managerによる機密情報管理
機密情報(RedmineのAPIトークン、Slackのトークンなど)は、AWS Secrets Manager で安全に管理しています。
- TerraformでSecrets Managerリソースを定義
- 機密情報をsopsで暗号化してSecrets Managerに登録
- Lambda関数には、Secrets Managerへのアクセス権限(IAM Policy)を付与
- Lambda実行時に、AWS SDKを使ってSecrets Managerから機密情報を取得
このアプローチにより、インフラ構成をバージョン管理しつつ、機密情報は安全に管理できています。
Lambdaはどうやって?
Lambdaの実装には Golang を採用し、クリーンアーキテクチャ で設計しています。

2つのLambda Handlerと責務の分離
今回のシステムでは、以下の2つのLambda Handlerを用意しています:
- event-handler - Slackイベントの受信と検証、AI要約処理
- ticket-processor - Redmineチケット作成
重要なのは、同じソースコードベースから、エントリーポイントを変更するだけで2つのLambdaを生成している 点です。
cmd/
├── event-handler/
│ └── main.go # エントリーポイント1
└── ticket-processor/
└── main.go # エントリーポイント2
それぞれに対応するDockerfileを用意し、ビルド時にエントリーポイントを切り替えています:
- Dockerfile.event-handler
- Dockerfile.ticket-processor
クリーンアーキテクチャによる構成
主要なディレクトリ構成は以下の通りです:
lambda/ ├── cmd/ # エントリーポイント │ ├── event-handler/ │ └── ticket-processor/ ├── internal/ │ ├── domain/ # ドメイン層 │ │ ├── entity/ # エンティティ │ │ └── repository/ # リポジトリインターフェース │ ├── usecase/ # ユースケース層 │ │ ├── generate_ticket.go # チケット生成ロジック │ │ └── post_ticket.go # チケット投稿ロジック │ ├── infrastructure/ # インフラ層 │ │ ├── ai/ # Bedrock実装 │ │ ├── secrets/ # Secrets Manager実装 │ │ ├── storage/ # S3実装 │ │ └── ticket/ # Redmine実装 │ └── interface/ # インターフェース層 │ └── handler/ # Lambda Handler ├── Dockerfile.event-handler └── Dockerfile.ticket-processor
このように、ビジネスロジックを共通化しつつ、責務ごとにLambdaを分離することで、保守性とテスタビリティが大幅に向上 しています。
AIの使用率は?
このシステムは、約90%をAI(Claude Code)を使って実装 しました。
仕様書もAIで作成
実装を始める前に、spec/ ディレクトリを用意し、Markdownで仕様書を作成しました。
この仕様書自体もAIを活用して作成しています。仕様を書くのに時間をかけたくなかったため、AIに概要を伝えて生成してもらい、それをベースに実装を進めました。
以下は、仕様書の冒頭部分です
# Slack投稿からチケット自動生成システム 仕様書 ## 概要 Slack Event Subscribe 機能で特定のチャネルに投稿されたSlackメッセージをフックに AWS API GatewayをたたいてAWS Step Functionsを実行し、Lambda内でAWS Bedrockを実行して Slackメッセージをチケットとして整形、整備した後、各種チケット管理ツールに投稿できる機能。 ## システム構成図 Slack (Event Subscriptions API) ↓ AWS API Gateway (POST /slack/events) ↓ Lambda (Event Handler) - URL Verification & Signature検証 ↓ AWS Step Functions ↓ Lambda1 (チケット生成) ├→ AWS Bedrock (AI要約・整形) └→ S3 (プロンプトテンプレート取得) ↓ Lambda2 (チケット投稿) ├→ Redmine API └→ Backlog API (スコープ外)
このように、仕様書を起点にAIと対話しながら実装を進めることで、短期間で完成させることができました。
制作時間
ClaudeCodeなどのAIをフル活用し、開発を始めてから動作できるようになるまで1日程度で実現できました。
さいごに
開発で感じたこと
今回、Terraformを使って仰々しくインフラ管理をしていますが、Claude Codeで作っていくなら、AWSコンソールを使うより遙かに早い です。 IaCのメリット(バージョン管理、再現性)を享受しつつ、AIによる高速な実装が可能になります。
また、Lambda実装についても、ボイラーテンプレートがあればもっと早く実装できそうだと感じました。 次回からはテンプレート化して、さらなる効率化を図りたいと思います。
AWS Step Functionsが便利すぎる というのも実感しました。 最近、LambdaでAWS Step Functionsのように振る舞う新しい機能も実装されたので、それも活用していきたいですね。
利用チームからの声
実際にこのツールを使っているチームからは、非常に好評でした
- 「これまでチケット化が億劫だった。チケット化のストレスが激減した。」
- 「チケットを作るのが楽しくなった。小さいことだからチケットにするまでもないかとスルーしてしまっていたような課題を、しっかりタスクに入れて遂行できるようになった。」
- 「管理側として、小さいタスクもチケットで計測できると予実管理が適切にできるようになった。」
ユーザーの声を聞くと、作って良かったなと思います。
ありがとうございました。
オプティムでは、一緒に働く仲間を募集しています。興味のある方は、こちらをご覧ください。