- 動作確認日:2026-10-01
- n8nのバージョン:2.41.3(Docker、セルフホスト)
① 結論:このワークフローでできること
GitHubにあるn8nの公式リポジトリ(n8n-io/n8n)のリリースを確かめ、まだ知らせていない新しいバージョンだけを、1通にまとめてDiscordに送ります。
- 安定版とベータ版を見分けて、「✅ 安定版」「🧪 ベータ版」と表示します。
stableやbetaのような、目印のためのリリースは知らせません。- 知らせたバージョン(タグ名)をn8nに覚えさせているので、何回実行しても、同じバージョンは2回知らせません。
- GitHubのトークンは、公開リポジトリを読むだけの権限で作ります。
- 動作確認のため、起動にはWebhookを使っています。実際に使うときは、Schedule Triggerで1時間ごとなどに実行する想定です。
② 完成イメージ

WebhookのURLにGETでアクセスすると、Discordにメッセージが届きます。

届いたメッセージは、次のとおりです(実際の内容です)。
🚀 n8n の新しいリリース 3件
**n8n@1.123.83**(✅ 安定版)09/30 15:13
<https://github.com/n8n-io/n8n/releases/tag/n8n%401.123.83>
**n8n@2.41.4**(✅ 安定版)09/30 15:04
<https://github.com/n8n-io/n8n/releases/tag/n8n%402.41.4>
**n8n@2.42.1**(🧪 ベータ版)09/30 14:56
<https://github.com/n8n-io/n8n/releases/tag/n8n%402.42.1>
続けてもう一度実行すると、Discordには何も届かず、呼び出し元には次の文章が返ってきました。
| 実行 | 返ってきた文章 | Discord |
|---|---|---|
| 1回目 | 3件のリリースをDiscordに送りました | 1通届いた |
| 2回目 | 新しいリリースはありませんでした | 何も届かない |
実行したときの件数の流れは、次のとおりです。
| ノード | 1回目 | 2回目 |
|---|---|---|
| Webhook | 1 | 1 |
| Get Releases(HTTP Request) | 20 | 20 |
| Pick New(Code) | 1(3件をまとめた1通分) | 1(0件) |
| Has New | 1(true側) | 1(false側) |
| Discord | 1 | 動かない |
| Respond Sent / Respond None | Respond Sent:1 | Respond None:1 |
動かしてみて気づいた点が3つあります。
- 取ってきた20件のリリースの中に、
stableとbetaという名前のリリースが入っていました。これは、そのとき一番新しい安定版・ベータ版を指すための目印で、バージョン番号ではありません。n8n@で始まるものだけを対象にして、外しています。 - 同じ日に、
n8n@2.41.4(2.x系の安定版)、n8n@2.42.1(2.x系のベータ版)、n8n@1.123.83(1.x系)が出ていました。n8nは、いくつかの系列を並行して出しているようです。 n8n@1.123.83は、1.x系を使い続けている人向けのリリースです。GitHubで「Pre-release(ベータ版)」の印が付いていないので、「✅ 安定版」と表示されています。2.x系だけを知らせたいときは、⑥のアイデアのように絞り込めます。
③ 使うノードと必要なもの
必要なもの
- n8n(今回はバージョン2.41.3をDockerで動かしました)
- GitHubのアカウントと、読み取り専用のトークン
- Discordのサーバーと、通知を送るチャンネルの「ウェブフックURL」
- 認証情報:GitHub API(トークンを登録します)と、Discord Webhook
GitHubのトークンの作り方
- https://github.com/settings/personal-access-tokens/new を開く(Fine-grained personal access token)
- Repository accessを「Public repositories」にする
- Permissionsには何も追加しない
- 「Generate token」を押し、表示された
github_pat_で始まるトークンを控える
このトークンは、公開されている情報を読むことしかできません。トークンなしでも公開リポジトリは読めますが、APIを呼べるのは1時間に60回までです。トークンがあると、1時間に5,000回まで呼べます。
なぜGitHub Triggerを使わないのか
n8nの「GitHub Trigger」は、GitHubからn8nを呼んでもらうしくみなので、インターネットから見えるURL(https)でn8nを動かしている必要があります。また、Webhookを設定できるのは、自分が管理しているリポジトリだけです。n8nの公式リポジトリのように、他人のリポジトリを見張ることはできません。そこで、GitHubのAPIでリリースの一覧を取りに行く作りにしました。
使うノード
| ノード | 役割 | 設定したこと |
|---|---|---|
| Webhook | 動作確認用の起動ボタン | Path:test-github-release-discord、Respond:Using 'Respond to Webhook' Node |
| HTTP Request(Get Releases) | リリースの一覧を取る | URL:https://api.github.com/repos/n8n-io/n8n/releases?per_page=20、Authentication:Predefined Credential Type → GitHub API |
| Code(Pick New) | まだ知らせていないリリースを選び、メッセージの文章を作る | 下のコード |
| If(Has New) | 知らせるリリースがあるか確かめる | count が 0 より大きい |
| Discord | メッセージを送る | Connection Type:Webhook、Operation:Send a Message、Message:{{ $json.content }} |
| Respond to Webhook(Respond Sent / Respond None) | 結果を返す | 送ったとき:○件のリリースをDiscordに送りました、送らなかったとき:新しいリリースはありませんでした |

Get Releasesでは、Authenticationを「Predefined Credential Type」にして、n8nに最初からある「GitHub API」の認証情報を選んでいます。こうすると、トークンを自分でヘッダーに書かなくても、n8nが付けて送ってくれます。
GitHubのAPIはリリースを配列で返すので、HTTP Requestノードの出力は、リリース1件ごとに1つのデータ(今回は20件)になります。

Pick Newでは、次の処理をしています。
// まだ知らせていないリリースだけを選ぶ(知らせたタグ名を覚えておく)
const state = $getWorkflowStaticData('global');
// stable / beta のような目印用のリリースは除き、n8n@x.y.z だけを対象にする
const releases = $input.all()
.map(i => i.json)
.filter(r => r.tag_name.startsWith('n8n@') && !r.draft)
.sort((a, b) => b.published_at.localeCompare(a.published_at));
const seen = state.seenTags || [];
// 初回は最新3件だけ知らせ、残りは知らせたことにする
const fresh = state.seenTags ? releases.filter(r => !seen.includes(r.tag_name)) : releases.slice(0, 3);
state.seenTags = [...new Set([...releases.map(r => r.tag_name), ...seen])].slice(0, 200);
const lines = fresh.map(r => {
const date = DateTime.fromISO(r.published_at).setZone('Asia/Tokyo').toFormat('MM/dd HH:mm');
const kind = r.prerelease ? '🧪 ベータ版' : '✅ 安定版';
return `**${r.tag_name}**(${kind})${date}\n<${r.html_url}>`;
});
const content = `🚀 n8n の新しいリリース ${fresh.length}件\n\n` + lines.join('\n\n');
return [{ json: { count: fresh.length, content } }];
prereleaseがtrueのものを「ベータ版」、それ以外を「安定版」と表示しています。- 知らせたかどうかは、タグ名(
n8n@2.42.1など)で覚えています。$getWorkflowStaticData('global')は、実行が終わっても消えない、ワークフロー専用の保存場所です。 - URLを
<と>で囲んで、Discordでリンクのプレビュー(大きなカード)が出ないようにしています。
④ JSON
n8nの公開API(POST /api/v1/workflows)でこのJSONからワークフローを作成し、有効化してから、上の結果を確認しました。
使うときは、次の箇所を自分の環境に合わせて書き換えてください。
YOUR_GITHUB_CREDENTIAL_ID:n8nに登録したGitHub APIの認証情報のIDYOUR_DISCORD_CREDENTIAL_ID:n8nに登録したDiscord Webhookの認証情報のID- Get ReleasesのURLの
n8n-io/n8n:ほかのリポジトリを見張りたいときは、持ち主/リポジトリ名に変えます(その場合は、Pick Newの'n8n@'の部分も、そのリポジトリのタグ名に合わせて変えます)
読み込んだあとに、ノードの画面で認証情報を選び直しても大丈夫です。
{
"name": "test-github-release-discord",
"nodes": [
{ "parameters": { "path": "test-github-release-discord", "responseMode": "responseNode", "options": {} },
"id": "b7100000-0001-4000-8000-000000000001", "name": "Webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [0, 0], "webhookId": "b7100000-aaaa-4000-8000-000000000001" },
{ "parameters": {
"url": "https://api.github.com/repos/n8n-io/n8n/releases?per_page=20",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "githubApi",
"options": {} },
"id": "b7100000-0002-4000-8000-000000000002", "name": "Get Releases", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [220, 0],
"credentials": { "githubApi": { "id": "YOUR_GITHUB_CREDENTIAL_ID", "name": "GitHub account (read-only)" } } },
{ "parameters": { "jsCode": "// まだ知らせていないリリースだけを選ぶ(知らせたタグ名を覚えておく)\nconst state = $getWorkflowStaticData('global');\n// stable / beta のような目印用のリリースは除き、n8n@x.y.z だけを対象にする\nconst releases = $input.all()\n .map(i => i.json)\n .filter(r => r.tag_name.startsWith('n8n@') && !r.draft)\n .sort((a, b) => b.published_at.localeCompare(a.published_at));\nconst seen = state.seenTags || [];\n// 初回は最新3件だけ知らせ、残りは知らせたことにする\nconst fresh = state.seenTags ? releases.filter(r => !seen.includes(r.tag_name)) : releases.slice(0, 3);\nstate.seenTags = [...new Set([...releases.map(r => r.tag_name), ...seen])].slice(0, 200);\n\nconst lines = fresh.map(r => {\n const date = DateTime.fromISO(r.published_at).setZone('Asia/Tokyo').toFormat('MM/dd HH:mm');\n const kind = r.prerelease ? '🧪 ベータ版' : '✅ 安定版';\n return `**${r.tag_name}**(${kind})${date}\\n<${r.html_url}>`;\n});\nconst content = `🚀 n8n の新しいリリース ${fresh.length}件\\n\\n` + lines.join('\\n\\n');\nreturn [{ json: { count: fresh.length, content } }];" },
"id": "b7100000-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": "b7100000-0004-4000-8000-000000000004", "name": "Has New", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [660, 0] },
{ "parameters": { "authentication": "webhook", "content": "={{ $json.content }}", "options": {} },
"id": "b7100000-0005-4000-8000-000000000005", "name": "Discord", "type": "n8n-nodes-base.discord", "typeVersion": 2, "position": [880, -100],
"webhookId": "b7100000-bbbb-4000-8000-000000000005",
"credentials": { "discordWebhookApi": { "id": "YOUR_DISCORD_CREDENTIAL_ID", "name": "Discord Webhook account" } } },
{ "parameters": { "respondWith": "text", "responseBody": "={{ $('Pick New').item.json.count }}件のリリースをDiscordに送りました", "options": {} },
"id": "b7100000-0006-4000-8000-000000000006", "name": "Respond Sent", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "position": [1100, -100] },
{ "parameters": { "respondWith": "text", "responseBody": "新しいリリースはありませんでした", "options": {} },
"id": "b7100000-0007-4000-8000-000000000007", "name": "Respond None", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "position": [880, 120] }
],
"connections": {
"Webhook": { "main": [[{ "node": "Get Releases", "type": "main", "index": 0 }]] },
"Get Releases": { "main": [[{ "node": "Pick New", "type": "main", "index": 0 }]] },
"Pick New": { "main": [[{ "node": "Has New", "type": "main", "index": 0 }]] },
"Has New": { "main": [[{ "node": "Discord", "type": "main", "index": 0 }], [{ "node": "Respond None", "type": "main", "index": 0 }]] },
"Discord": { "main": [[{ "node": "Respond Sent", "type": "main", "index": 0 }]] }
},
"settings": { "executionOrder": "v1" }
}
⑤ つまずきポイントと対処
このワークフロー自体は、1回目の実行でエラーなく動きました。
気をつける点として、per_page=20 で取っているのは、新しい順の20件だけです。n8nは、多い日には1日に何件もリリースを出します。実行の間隔を長くしすぎると、20件より前のリリースは取りこぼします。1時間〜数時間ごとに確かめる使い方なら、20件で足りると考えています。
⑥ 応用アイデア
どれも、まだ試していないアイデアです。
- Webhookを「Schedule Trigger」に替えて、1時間ごとに確かめる
r.tag_name.startsWith('n8n@2.')にして、2.x系だけを知らせる- 安定版だけを知らせる(
!r.prereleaseで絞り込む) - 通知先を、LINEやTelegramに替える
- リリースノート(
body)の最初の数行も、一緒に送る
