- 動作確認日:2026-10-01
- n8nのバージョン:2.41.3(Docker、セルフホスト)
① 結論:このワークフローでできること
ブログのRSSフィードを読み、まだ通知していない記事だけを、1通のメッセージにまとめてSlackのチャンネルに送ります。
- 送った記事のURLをn8nに覚えさせているので、何回実行しても、同じ記事は2回送りません。
- 新しい記事がないときは、Slackには何も送りません。
- 記事のタイトルを、そのまま記事へのリンクにして送ります。
- n8nのSlackノードが最後に付ける「このワークフローで自動送信しました」のリンクは、オフにしています。
- 動作確認のため、起動にはWebhookを使っています。実際に使うときは、Schedule Triggerで定期的に実行する想定です。
② 完成イメージ

WebhookのURLにGETでアクセスすると、Slackのチャンネルにメッセージが届きます。

送ったメッセージは、次のとおりです(実際の内容です。Slackでは *<URL|タイトル>* の部分が、太字のリンクとして表示されます)。
:newspaper: 新着記事 3件
• *<https://automatelabo.com/2026/10/01/n8n-github-release-discord/|n8nの新しいバージョンが出たらDiscordに通知するワークフロー(GitHub API・重複なし)>* 10/01 12:45
• *<https://automatelabo.com/2026/10/01/n8n-telegram-google-sheets/|n8nでTelegramのボットに送ったメモをGoogleスプレッドシートに記録するワークフロー(公開URLなし)>* 10/01 11:43
• *<https://automatelabo.com/2026/10/01/n8n-rss-telegram/|n8nでブログの新着記事をTelegramに通知するワークフロー(重複なし)>* 10/01 11:43
続けてもう一度実行すると、Slackには何も届かず、呼び出し元には次の文章が返ってきました。
| 実行 | 返ってきた文章 | Slack |
|---|---|---|
| 1回目 | 3件をSlackに送りました | 1通届いた |
| 2回目 | 新着記事はありませんでした | 何も届かない |
実行したときの件数の流れは、次のとおりです。
| ノード | 1回目 | 2回目 |
|---|---|---|
| Webhook | 1 | 1 |
| RSS Read | 10 | 10 |
| Pick New | 1(3件をまとめた1通分) | 1(0件) |
| Has New | 1(true側) | 1(false側) |
| Slack | 1 | 動かない |
| Respond Sent / Respond None | Respond Sent:1 | Respond None:1 |
③ 使うノードと必要なもの
必要なもの
- n8n(今回はバージョン2.41.3をDockerで動かしました)
- 読み込みたいRSSフィードのURL(今回は
https://automatelabo.com/feed) - Slackのワークスペースと、通知を送るチャンネル
- Slackアプリの「Bot User OAuth Token」(
xoxb-で始まるもの) - 送り先のチャンネルID(
Cで始まる英数字) - 認証情報:Slack API(トークンを登録します)
Slack側の準備
今回は、テスト用に新しいワークスペースを作り、次の順に準備しました。
- https://api.slack.com/apps で「Create New App」を押し、「Blank app」を選ぶ(以前の画面では「From scratch」)
- App Nameを入れ、ワークスペースを選んで作る
- 「OAuth & Permissions」の「Bot Token Scopes」に、
chat:writeを追加する - ページ上部の「Install to ワークスペース名」を押して許可し、「Bot User OAuth Token」を控える
- Slackで通知用のチャンネルを開き、
/invite @アプリ名でアプリを招待する - チャンネル名を押して、詳細の一番下にある「チャンネルID」を控える
アプリは、招待されたチャンネルにしか書き込めません。手順5を忘れると、送信のときに「チャンネルに参加していない」というエラーになります。
トークンを知っている人は、このアプリとしてメッセージを送れてしまいます。n8nの認証情報(Credentials)に登録しておけば、ワークフローのJSONにトークンが入らないので、JSONを人に渡しても安心です。
使うノード
| ノード | 役割 | 設定したこと |
|---|---|---|
| Webhook | 動作確認用の起動ボタン | Path:test-rss-slack、Respond:Using 'Respond to Webhook' Node |
| RSS Read | フィードを読み込む | URL:https://automatelabo.com/feed |
| Code(Pick New) | まだ送っていない記事を選び、メッセージの文章を作る | 下のコード |
| If(Has New) | 送る記事があるか確かめる | count が 0 より大きい |
| Slack | メッセージを送る | 下の表のとおり |
| Respond to Webhook(Respond Sent / Respond None) | 結果を返す | 送ったとき:○件をSlackに送りました、送らなかったとき:新着記事はありませんでした |

Pick Newでは、次の処理をしています。
// まだ送っていない記事だけを選ぶ(送った記事のURLを覚えておく)
const state = $getWorkflowStaticData('global');
const items = $input.all()
.map(i => ({ ...i.json, url: i.json.link.split('?')[0] }))
.sort((a, b) => b.isoDate.localeCompare(a.isoDate));
const sent = state.sentUrls || [];
// 初回は最新3件だけ送り、残りは送ったことにする
const fresh = state.sentUrls ? items.filter(a => !sent.includes(a.url)) : items.slice(0, 3);
state.sentUrls = [...new Set([...items.map(a => a.url), ...sent])].slice(0, 100);
// Slackの書き方(mrkdwn)で、タイトルをリンクにする: <URL|表示する文字>
// タイトルに < > & があると崩れるので、先に置き換える
const esc = s => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
const lines = fresh.map(a => {
const date = DateTime.fromISO(a.isoDate).setZone('Asia/Tokyo').toFormat('MM/dd HH:mm');
return `• *<${a.url}|${esc(a.title)}>* ${date}`;
});
const text = `:newspaper: 新着記事 ${fresh.length}件\n` + lines.join('\n');
return [{ json: { count: fresh.length, text } }];
- Slackでは、
<URL|表示する文字>と書くと、その文字がリンクになります。*で囲むと太字です。 :newspaper:のように:で囲んだ名前は、Slackでは絵文字として表示されます。- 送った記事のURLは、
$getWorkflowStaticData('global')(実行が終わっても消えない、ワークフロー専用の保存場所)に覚えさせています。

Slackノードの設定は、次のとおりです(Channelの値は、画像ではぼかしています)。
| 項目 | 値 |
|---|---|
| Resource / Operation | Message / Send |
| Send Message To | Channel |
| Channel | By ID:チャンネルID |
| Message Type | Simple Text Message |
| Message Text | {{ $json.text }} |
| Include Link to Workflow | オフ(オンのままだと、最後に「このn8nのワークフローで自動送信しました」というリンクが付きます) |
| Unfurl Links | オフ(3件分のリンクのプレビューを出さない) |
④ JSON
n8nの公開API(POST /api/v1/workflows)でこのJSONからワークフローを作成し、有効化してから、上の結果を確認しました。
使うときは、次の箇所を自分の環境に合わせて書き換えてください。
YOUR_CHANNEL_ID:送り先のチャンネルIDYOUR_SLACK_CREDENTIAL_ID:n8nに登録したSlack APIの認証情報のID。読み込んだあとに、ノードの画面で認証情報を選び直しても大丈夫です。- RSS ReadのURL:通知したいブログのフィードに変えてください。
{
"name": "test-rss-slack",
"nodes": [
{ "parameters": { "path": "test-rss-slack", "responseMode": "responseNode", "options": {} },
"id": "b5000000-0001-4000-8000-000000000001", "name": "Webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [0, 0], "webhookId": "b5000000-aaaa-4000-8000-000000000001" },
{ "parameters": { "url": "https://automatelabo.com/feed", "options": {} },
"id": "b5000000-0002-4000-8000-000000000002", "name": "RSS Read", "type": "n8n-nodes-base.rssFeedRead", "typeVersion": 1.2, "position": [220, 0] },
{ "parameters": { "jsCode": "// まだ送っていない記事だけを選ぶ(送った記事のURLを覚えておく)\nconst state = $getWorkflowStaticData('global');\nconst items = $input.all()\n .map(i => ({ ...i.json, url: i.json.link.split('?')[0] }))\n .sort((a, b) => b.isoDate.localeCompare(a.isoDate));\nconst sent = state.sentUrls || [];\n// 初回は最新3件だけ送り、残りは送ったことにする\nconst fresh = state.sentUrls ? items.filter(a => !sent.includes(a.url)) : items.slice(0, 3);\nstate.sentUrls = [...new Set([...items.map(a => a.url), ...sent])].slice(0, 100);\n\n// Slackの書き方(mrkdwn)で、タイトルをリンクにする: <URL|表示する文字>\n// タイトルに < > & があると崩れるので、先に置き換える\nconst esc = s => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\nconst lines = fresh.map(a => {\n const date = DateTime.fromISO(a.isoDate).setZone('Asia/Tokyo').toFormat('MM/dd HH:mm');\n return `• *<${a.url}|${esc(a.title)}>* ${date}`;\n});\nconst text = `:newspaper: 新着記事 ${fresh.length}件\\n` + lines.join('\\n');\nreturn [{ json: { count: fresh.length, text } }];" },
"id": "b5000000-0003-4000-8000-000000000003", "name": "Pick New", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [440, 0] },
{ "parameters": {
"conditions": {
"options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose", "version": 2 },
"conditions": [ { "id": "n1", "leftValue": "={{ $json.count }}", "rightValue": 0, "operator": { "type": "number", "operation": "gt" } } ],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {} },
"id": "b5000000-0004-4000-8000-000000000004", "name": "Has New", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [660, 0] },
{ "parameters": {
"select": "channel",
"channelId": { "__rl": true, "value": "YOUR_CHANNEL_ID", "mode": "id" },
"text": "={{ $json.text }}",
"otherOptions": { "includeLinkToWorkflow": false, "unfurl_links": false } },
"id": "b5000000-0005-4000-8000-000000000005", "name": "Slack", "type": "n8n-nodes-base.slack", "typeVersion": 2.3, "position": [880, -100],
"webhookId": "b5000000-bbbb-4000-8000-000000000005",
"credentials": { "slackApi": { "id": "YOUR_SLACK_CREDENTIAL_ID", "name": "Slack account" } } },
{ "parameters": { "respondWith": "text", "responseBody": "={{ $('Pick New').item.json.count }}件をSlackに送りました", "options": {} },
"id": "b5000000-0006-4000-8000-000000000006", "name": "Respond Sent", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "position": [1100, -100] },
{ "parameters": { "respondWith": "text", "responseBody": "新着記事はありませんでした", "options": {} },
"id": "b5000000-0007-4000-8000-000000000007", "name": "Respond None", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "position": [880, 120] }
],
"connections": {
"Webhook": { "main": [[{ "node": "RSS Read", "type": "main", "index": 0 }]] },
"RSS Read": { "main": [[{ "node": "Pick New", "type": "main", "index": 0 }]] },
"Pick New": { "main": [[{ "node": "Has New", "type": "main", "index": 0 }]] },
"Has New": { "main": [[{ "node": "Slack", "type": "main", "index": 0 }], [{ "node": "Respond None", "type": "main", "index": 0 }]] },
"Slack": { "main": [[{ "node": "Respond Sent", "type": "main", "index": 0 }]] }
},
"settings": { "executionOrder": "v1" }
}
⑤ つまずきポイントと対処
このワークフロー自体は、1回目の実行でエラーなく動きました。ここでは、準備の途中で気づいたことを書きます。
アプリの作り方の画面が変わっていた
- 起きたこと:Slackアプリを作る画面で、よく説明されている「From scratch」が見当たりませんでした。代わりに「AI agent」「Starter app」「From a manifest」「Blank app」の4つが並んでいました。
- 対処:何も入っていない状態から作る「Blank app」を選びました。これが、以前の「From scratch」にあたります。
トークンの確認で「missing_scope」が出た
- 起きたこと:送る前に、チャンネルにアプリが入っているかをAPI(
conversations.info)で確かめようとしたところ、missing_scope(権限が足りない)と返ってきました。 - 原因:チャンネルの情報を読むには
channels:readという別の権限が必要で、今回はchat:write(書き込み)だけを付けていたためです。 - 対処:メッセージを送るだけなら
chat:writeで足りるので、そのまま進めました。実際に送れたので、招待もできていたことがわかりました。
⑥ 応用アイデア
どれも、まだ試していないアイデアです。
- Webhookを「Schedule Trigger」に替えて、1時間ごとに新着を確かめる
- Message Typeを「Blocks」にして、記事ごとに区切りや画像の付いた見た目にする
- 複数のブログのフィードを読み込み、チャンネルを分けて通知する
- 通知先を、DiscordやLINE、Telegramにする(Pick Newのコードは、書き方を少し変えるだけで使えます)
