🎃

「返信どうしよう…」を助ける!n8n×AIでSlack秘書をつくってみた

に公開

はじめに

スケジュール調整や一時的な返信って、地味に手間がかかりますよね。
少なくとも私は「誰か代わりに返事してくれないかな…」と何度も思ってきました。

そこで今回は n8n を使って、Slackでメンションがついたら自動で「返信案」を提案してくれるAI秘書Botを作ってみました。さらに、Google Calendarと連携して簡単な日程調整も考えてくれるようにしています。

さすがにいきなり相手に送るのは怖いので(笑)、まずは返信案を自分宛てにDMするようにしています。
(※精度はまだ怪しく、動かない時もあります。改善の余地ありです!)

今回作ったもの

  • Slackで自分宛てのメンションが来ると、AIが返信候補を提案してくれる
  • Google Calendarと連携し、予定を踏まえた返信も生成

n8nとは?

n8n は、ノーコード/ローコードでワークフローを自動化できるOSSのツールです。
Zapierのようなサービスのセルフホスト版で、自由度が高いのが特徴。
例えば今回のように Slack → AI → Google Calendar といった複数サービスを簡単につなげることができます。

作成手順

今回作成するワークフローは、次のステップで作成できます。
「細かい設定を見ている時間がない!」という方は、用意したJSONをそのまま貼り付ければすぐに枠組みを作成することができます。詳しくは こちら をご覧ください。

ステップ1:n8nアカウントの準備

まずは n8n Cloud でアカウントを作成しましょう。

ステップ2:Slackアプリの作成と設定

SlackでBotを動かすためのアプリを作ります。

  1. アプリの作成Slack API — Your Apps にアクセスし、「Create New App」から「From scratch」を選びます。アプリ名は「AI秘書」など、わかりやすい名前をつけましょう。

  2. 権限(Scopes)の設定
    左メニューの「OAuth & Permissions」を開き、「Bot Token Scopes」に必要な権限を追加します。まずは、公開チャンネルでのメンションにBotが返信するための最低限の権限を付与してください。

    • app_mentions:read:メンションを読み取る
    • channels:history:公開チャンネルのメッセージを取得する
    • chat:write:メッセージを送信する

    注意: 上記の権限だけでは動かない場合もあります。より幅広い用途で使う場合は、必要に応じて権限を追加しましょう。

  3. アプリのインストール
    ページ上部の「Install to Workspace」をクリックして、アプリをワークスペースにインストールします。完了後、「Bot User OAuth Token」をコピーして控えておきます。このトークンは後でn8nに貼り付けます。

  4. Events API の有効化
    左メニューの「Event Subscriptions」を開き、「Enable」をONにします。
    Subscribe to bot events」に message.channels を追加し、保存してください。この画面にある「Request URL」は、後ほどn8nから取得したURLを貼り付けます。

ステップ3:n8nでSlackトリガーを設定

  1. ワークフローの作成
    n8nで新しいワークフローを作成し、「Slack Trigger」ノードを追加します。
  2. Slack認証
    ノード内の「Credentials」に、先ほど控えておいた「Bot User OAuth Token」を使ってSlack認証情報を設定します。
  3. Webhook URLの検証
    ノード右側に表示される「Webhook URL」をコピーし、ステップ2で開いたSlackの「Event Subscriptions > Request URL」に貼り付けて検証します。
  4. 動作確認
    SlackでBotを対象チャンネルに招待し、「こんにちは」などと投稿して、n8n側でメッセージが受信できるか確認します。

ステップ4:自分宛のメンションだけを抽出

If ノードを追加し、Bot宛のメンションだけを次のステップへ進めるように設定します。

  • 左辺: メッセージ本文(text
  • 演算子: contains(含む)
  • 右辺: <@あなたのSlackユーザーID>

ステップ5:メッセージ情報の整理

Set ノードを追加し、以降のステップで使いやすいようにメッセージ情報を整理します。

  • original_message:受信したメッセージ本文
  • channel_id:チャンネルID
  • user_id:送信者ID
  • timestamp:メッセージのタイムスタンプ

ステップ6:AIエージェントの配置

AIエージェントノードを追加し、OpenAIやGoogleカレンダーと連携させます。

  1. AI Agentノードの設定
    ノードに original_message を入力として渡し、Botにどのような返信をさせるかプロンプトで指示します。
  2. OpenAIとの連携
    Language Model」で「OpenAI」を選択し、APIキーを設定します。
  3. Googleカレンダーとの連携
    日程を確認したい場合は、「Tools」に「Google Calendar」を追加し、認証を済ませます。

ステップ7:Slackへの返信テスト

Slack: Send Message ノードを追加して、返信が正しく機能するかテストします。

  • 送り先: 「User」を選択し、あなたのユーザーIDを指定(まずは自分へのDMで確認するのがおすすめです)。
  • メッセージ本文: AIエージェントの出力結果をここに差し込みます。

JSONを使ってワークフローを簡単に作成

「自分でゼロから設定するのは大変そう…」と感じた方もご安心ください!
実はこのワークフローは、JSONを貼り付けるだけでも手軽に再現できます。

作成手順

  1. n8nのワークフロー作成画面を開く。
  2. 以下のJSONコードをコピーして、Ctrl + V(Windows)または Cmd + V(Mac)で貼り付ける。
  3. 表示されたワークフローに必要な認証情報を設定する。

JSONコードはこちら

実際にn8nで作成したワークフローをJSON形式でエクスポートしました。ぜひご活用ください!

作成したワークフロー
{
  "nodes": [
    {
      "parameters": {
        "trigger": [
          "any_event"
        ],
        "watchWorkspace": true,
        "options": {}
      },
      "type": "n8n-nodes-base.slackTrigger",
      "typeVersion": 1,
      "position": [
        -432,
        240
      ],
      "id": "27ec441f-27ba-490a-8dad-9d6874454902",
      "name": "Slack Trigger",
      "webhookId": "{{YOUR_WEBHOOK_ID}}",
      "credentials": {
        "slackApi": {
          "id": "{{YOUR_SLACK_CREDENTIAL_ID}}",
          "name": "{{YOUR_SLACK_ACCOUNT_NAME}}"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "cc267284-b13b-4899-8584-9d7be379ad72",
              "leftValue": "={{ $json.text }}",
              "rightValue": "<@{{YOUR_SLACK_USER_ID}}>",
              "operator": {
                "type": "string",
                "operation": "contains"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -224,
        240
      ],
      "id": "d3a905e0-945d-4373-b298-39eed3bf2caf",
      "name": "If"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        80,
        384
      ],
      "id": "1d02681a-70e6-47aa-aa92-3d4e850ca551",
      "name": "No Operation, do nothing"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "name": "original_message",
              "value": "={{ $('Slack Trigger').item.json.text }}",
              "type": "string",
              "id": "b6475be5-a3c0-4e79-9d73-bd6d2fcd1f28"
            },
            {
              "name": "channel_id",
              "value": "={{ $('Slack Trigger').item.json.channel }}",
              "type": "string",
              "id": "a8f4aa58-7248-488c-a1a4-8cfc2cac5c37"
            },
            {
              "name": "user_id",
              "value": "={{ $('Slack Trigger').item.json.user }}",
              "type": "string",
              "id": "dd8b7c0f-06ee-445c-9fa1-c241efb7cdcd"
            },
            {
              "name": "timestamp",
              "value": "={{ $now }}",
              "type": "string",
              "id": "c6cd9f03-3c2f-4a8e-b8b1-66b34da2685e"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        80,
        176
      ],
      "id": "7b9c55fd-2782-40b3-aee0-ae52d5ce4236",
      "name": "メッセージ情報抽出"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=## 秘書AI - 返信案生成プロンプト\n\nあなたは**秘書AI**です。受信メッセージに対して、**2種類の返信案**を生成してください。\n\n---\n\n### 📥 受信メッセージ\n```\n{{ $json.original_message }}\n```\n\n---\n\n### ✅ 返信案の生成ルール\n\n#### 1. 基本フォーマット\n```\nお疲れ様です。(または「お世話になっております。」)\n[本文]\nご確認の程、よろしくお願いいたします。\n```\n\n#### 2. 特徴的な表現(例)\n- **依頼時**: 「〜していただけますと幸いです」「〜していただけると助かります」\n- **時間配慮**: 「お手すきの際に」「お忙しいところ恐縮ですが」\n- **確認依頼**: 「ご確認の程」「ご対応の程」\n- **感謝**: 「ありがとうございます!」\n- **謝罪**: 「申し訳ございません」(必要最小限に)\n\n#### 3. 構成要素\n- **メンション**: @ユーザー名 で対象者を明確化\n- **箇条書き**: 複数項目は「•」で整理\n- **URL**: 関連リンクは本文の後に配置\n- **絵文字**: 適度に使用(1〜2個まで)\n\n---\n\n### 📝 出力形式\n```\n【短い返信】\n[30〜60文字程度の簡潔な返信]\n\n【標準的な返信】\n[120〜220文字程度の丁寧な返信]\n```\n\n---\n\n### 🎯 返信パターン例\n(承諾・質問回答・依頼受領・PRレビュー・作業完了報告・問題報告・会議後 など)\n\n---\n\n### 現在時刻\n{{ $now }}",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 2.1,
      "position": [
        320,
        176
      ],
      "id": "6ea26c52-426f-4666-970d-846a14c2a75c",
      "name": "AI Agent"
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-4o",
          "mode": "list",
          "cachedResultName": "gpt-4o"
        },
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        320,
        432
      ],
      "id": "b8150d9c-c4e1-4e52-bc21-21f5afc9463d",
      "name": "OpenAI Chat Model",
      "credentials": {
        "openAiApi": {
          "id": "{{YOUR_OPENAI_CREDENTIAL_ID}}",
          "name": "{{YOUR_OPENAI_ACCOUNT_NAME}}"
        }
      }
    },
    {
      "parameters": {
        "select": "user",
        "user": {
          "__rl": true,
          "value": "{{YOUR_SLACK_USER_ID}}",
          "mode": "id"
        },
        "text": "={{ $json.output }}",
        "otherOptions": {}
      },
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        784,
        176
      ],
      "id": "1b9b0bd5-9920-4f3a-ad38-a1131d2acd97",
      "name": "Send a message",
      "webhookId": "{{YOUR_WEBHOOK_ID}}",
      "credentials": {
        "slackApi": {
          "id": "{{YOUR_SLACK_CREDENTIAL_ID}}",
          "name": "{{YOUR_SLACK_ACCOUNT_NAME}}"
        }
      }
    },
    {
      "parameters": {
        "operation": "getAll",
        "calendar": {
          "__rl": true,
          "value": "{{YOUR_GOOGLE_CALENDAR_EMAIL_1}}",
          "mode": "list",
          "cachedResultName": "{{YOUR_GOOGLE_CALENDAR_EMAIL_1}}"
        },
        "limit": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Limit', ``, 'number') }}",
        "timeMin": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('After', ``, 'string') }}",
        "timeMax": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Before', ``, 'string') }}",
        "options": {}
      },
      "type": "n8n-nodes-base.googleCalendarTool",
      "typeVersion": 1.3,
      "position": [
        736,
        432
      ],
      "id": "d26f7d74-70a5-43cf-aca4-c5059f011426",
      "name": "Get many events in Google Calendar",
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "{{YOUR_GOOGLE_CALENDAR_CREDENTIAL_ID_1}}",
          "name": "{{YOUR_GOOGLE_CALENDAR_ACCOUNT_NAME_1}}"
        }
      }
    },
    {
      "parameters": {
        "operation": "getAll",
        "calendar": {
          "__rl": true,
          "value": "{{YOUR_GOOGLE_CALENDAR_EMAIL_2}}",
          "mode": "list",
          "cachedResultName": "{{YOUR_GOOGLE_CALENDAR_EMAIL_2}}"
        },
        "limit": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Limit', ``, 'number') }}",
        "timeMin": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('After', ``, 'string') }}",
        "timeMax": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Before', ``, 'string') }}",
        "options": {}
      },
      "type": "n8n-nodes-base.googleCalendarTool",
      "typeVersion": 1.3,
      "position": [
        560,
        432
      ],
      "id": "e5c5880f-88e8-413e-b76d-6c1c90720e50",
      "name": "Get many events in Google Calendar1",
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "{{YOUR_GOOGLE_CALENDAR_CREDENTIAL_ID_2}}",
          "name": "{{YOUR_GOOGLE_CALENDAR_ACCOUNT_NAME_2}}"
        }
      }
    }
  ],
  "connections": {
    "Slack Trigger": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If": {
      "main": [
        [
          {
            "node": "メッセージ情報抽出",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Operation, do nothing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "メッセージ情報抽出": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Get many events in Google Calendar": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Get many events in Google Calendar1": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "masked-instance-id"
  }
}

動作イメージ

実際の動作イメージを紹介します。
例として「1 on 1 の調整をお願いされた」というSlackメッセージを想定しました。

画像のように、Google Calendar の予定も踏まえて返信案を提示してくれます。
場合によっては、そのまま必要な部分をコピペしてすぐに返事できそうですね。

想定メッセージ(私宛てに届いたもの)

AI秘書が考えた返信案

まとめ

まだ動作は不安定なところもありますが、「えっ、ここまでノーコードでできちゃうの?」と正直びっくりしました。
これまでは数千〜数万行のコードを書かないと実現できなかったような連携や自動化も、n8nならGUIでサクッと構築できるんです。
しかも、n8nには公式の ワークフローテンプレート集 が用意されていて、そこから選ぶだけで便利な仕組みがすぐに試せます。今回紹介したようなワークフローも、テンプレートを使えばあっという間に完成。
私自身は今後、RAGやカスタムプロンプトを組み合わせて、さらに精度の高い「自分専用AI秘書」に育てていきたいと思っています。
業務効率化の第一歩として、ぜひ一度 n8n を触ってみてください。

クロステックマネジメント(京都芸術大学)

Discussion