- 動作確認日:2026-10-01
- n8nのバージョン:2.41.3(Docker、セルフホスト)
① 結論:このワークフローでできること
Googleカレンダーから今日の予定を時刻順に取り出し、1通のメッセージにまとめてLINEに送ります。
- 読むカレンダーは、IDで1つに決めています。今回は、テスト用に作った
n8nテストカレンダーだけを読みました。 - 終日の予定は「終日」と表示します。予定がない日は、「今日の予定はありません」と送ります。
- LINEへの送信には、LINE公式アカウントのMessaging APIを使います(LINE Notifyは2025年3月に終わりました)。
- 使う認証情報(Credentials)は、Google CalendarとHeader Auth(LINEのトークン)の2つです。
- 動作確認のため、起動にはWebhookを使っています。実際に使うときは、Schedule Triggerで毎朝実行する想定です。
② 完成イメージ

テスト用のカレンダーに今日の予定を3件入れてから、WebhookのURLにGETでアクセスしました。LINEにメッセージが届きます。

届いたメッセージは、次のとおりです(実際の内容です)。
📅【2026-10-01 の予定(3件)】
・08:00〜09:00 travel
・10:00〜11:00 shopping
・12:00〜13:00 lunch
実行したときの件数の流れは、次のとおりです。
| ノード | 出てきた件数 |
|---|---|
| Webhook | 1 |
| Google Calendar(Today’s Events) | 3 |
| Make Message(Code) | 1(3件をまとめた1通分) |
| LINE Broadcast(HTTP Request) | 1 |
| Respond to Webhook | 1 |
動かしてみて気づいた点として、LINEでは、見出しの 2026-10-01 が自動で青いリンクになりました。LINEが日付だと判断して、押すと予定を作れるようにしているようです。
③ 使うノードと必要なもの
必要なもの
- n8n(今回はバージョン2.41.3をDockerで動かしました)
- Googleカレンダーと、読みたいカレンダーのID
- LINE公式アカウント(無料で作れます)と、その「チャネルアクセストークン(長期)」
- 認証情報:Google Calendar OAuth2 API(Google Cloudで作ったOAuthクライアントを使います)と、Header Auth(
Authorization: Bearer チャネルアクセストークン)
LINE公式アカウントの作り方と、チャネルアクセストークンの発行のしかたは、「n8nでブログの新着記事をLINEに通知するワークフロー」の記事で説明しています。今回も、自分だけが友だちになっている通知専用の公式アカウントに、一斉送信(broadcast)で送りました。
カレンダーのIDは、Googleカレンダーの「設定と共有」→「カレンダーの統合」にある「カレンダーID」で確かめられます。自分で作ったカレンダーは、…@group.calendar.google.com のような形です。
使うノード
| ノード | 役割 | 設定したこと |
|---|---|---|
| Webhook | 動作確認用の起動ボタン | Path:test-calendar-line、Respond:Using 'Respond to Webhook' Node |
| Google Calendar(Today’s Events) | 今日の予定を取る | Resource:Event、Operation:Get Many、Calendar:By ID、Return All:オン、After / Before:今日の0時〜23時59分 |
| Code(Make Message) | 予定を1通のメッセージにまとめる | 下のコード |
| HTTP Request(LINE Broadcast) | LINEに一斉送信する | URL:https://api.line.me/v2/bot/message/broadcast、Header Authの認証情報 |
| Respond to Webhook | 送った文章を返す | Respond With:Text |

Today’s Eventsでは、「After」と「Before」で今日の予定だけに絞り、「Order By」を Start Time にして開始時刻の順に並べています。予定が0件の日でも次のノードが動くように、ノードの「Settings」で「Always Output Data」をオンにしています。

Make Messageでは、次の処理をしています。
// 今日の予定を1通のメッセージにまとめる(予定がないときも1通作る)
const events = $input.all().map(i => i.json).filter(e => e.summary);
const today = $now.setZone('Asia/Tokyo').toFormat('yyyy-MM-dd');
const fmt = iso => DateTime.fromISO(iso).setZone('Asia/Tokyo').toFormat('HH:mm');
const lines = events.map(e => {
const when = e.start.dateTime ? `${fmt(e.start.dateTime)}〜${fmt(e.end.dateTime)}` : '終日';
return `・${when} ${e.summary}`;
});
// LINEは太字が使えないので、【】で見出しにする
const text = events.length
? `📅【${today} の予定(${events.length}件)】\n` + lines.join('\n')
: `📅【${today}】今日の予定はありません`;
return [{ json: { count: events.length, text } }];
- 時刻のある予定は
start.dateTimeに、終日の予定はstart.dateに日付が入ります。start.dateTimeがないものを「終日」としています。 - 予定が0件の日は、Google Calendarノードから中身が空のデータが1つ届くので、
filter(e => e.summary)で件名のあるものだけを数えています。
LINE Broadcastの設定は、「天気予報をLINEに通知する」ワークフローと同じです。JSONには {{ JSON.stringify({ messages: [{ type: 'text', text: $json.text }] }) }} を入れています。
④ JSON
n8nの公開API(POST /api/v1/workflows)でこのJSONからワークフローを作成し、有効化してから、上の結果を確認しました。
使うときは、次の箇所を自分の環境に合わせて書き換えてください。
YOUR_CALENDAR_ID:読みたいカレンダーのIDYOUR_GOOGLE_CALENDAR_CREDENTIAL_ID:n8nに登録したGoogle Calendarの認証情報のIDYOUR_LINE_CREDENTIAL_ID:n8nに登録したHeader Authの認証情報のID
読み込んだあとに、ノードの画面で認証情報を選び直しても大丈夫です。
{
"name": "test-calendar-line",
"nodes": [
{ "parameters": { "path": "test-calendar-line", "responseMode": "responseNode", "options": {} },
"id": "b8100000-0001-4000-8000-000000000001", "name": "Webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [0, 0], "webhookId": "b8100000-aaaa-4000-8000-000000000001" },
{ "parameters": {
"operation": "getAll",
"calendar": { "__rl": true, "value": "YOUR_CALENDAR_ID", "mode": "id" },
"returnAll": true,
"timeMin": "={{ $now.setZone('Asia/Tokyo').startOf('day').toISO() }}",
"timeMax": "={{ $now.setZone('Asia/Tokyo').endOf('day').toISO() }}",
"options": { "orderBy": "startTime", "recurringEventHandling": "expand" } },
"id": "b8100000-0002-4000-8000-000000000002", "name": "Today's Events", "type": "n8n-nodes-base.googleCalendar", "typeVersion": 1.3, "position": [220, 0],
"alwaysOutputData": true,
"credentials": { "googleCalendarOAuth2Api": { "id": "YOUR_GOOGLE_CALENDAR_CREDENTIAL_ID", "name": "Google Calendar account" } } },
{ "parameters": { "jsCode": "// 今日の予定を1通のメッセージにまとめる(予定がないときも1通作る)\nconst events = $input.all().map(i => i.json).filter(e => e.summary);\nconst today = $now.setZone('Asia/Tokyo').toFormat('yyyy-MM-dd');\nconst fmt = iso => DateTime.fromISO(iso).setZone('Asia/Tokyo').toFormat('HH:mm');\nconst lines = events.map(e => {\n const when = e.start.dateTime ? `${fmt(e.start.dateTime)}〜${fmt(e.end.dateTime)}` : '終日';\n return `・${when} ${e.summary}`;\n});\n// LINEは太字が使えないので、【】で見出しにする\nconst text = events.length\n ? `📅【${today} の予定(${events.length}件)】\\n` + lines.join('\\n')\n : `📅【${today}】今日の予定はありません`;\nreturn [{ json: { count: events.length, text } }];" },
"id": "b8100000-0003-4000-8000-000000000003", "name": "Make Message", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [440, 0] },
{ "parameters": {
"method": "POST",
"url": "https://api.line.me/v2/bot/message/broadcast",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ messages: [{ type: 'text', text: $json.text }] }) }}",
"options": {} },
"id": "b8100000-0004-4000-8000-000000000004", "name": "LINE Broadcast", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [660, 0],
"credentials": { "httpHeaderAuth": { "id": "YOUR_LINE_CREDENTIAL_ID", "name": "LINE Messaging API token" } } },
{ "parameters": { "respondWith": "text", "responseBody": "={{ $('Make Message').item.json.text }}", "options": {} },
"id": "b8100000-0005-4000-8000-000000000005", "name": "Respond to Webhook", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "position": [880, 0] }
],
"connections": {
"Webhook": { "main": [[{ "node": "Today's Events", "type": "main", "index": 0 }]] },
"Today's Events": { "main": [[{ "node": "Make Message", "type": "main", "index": 0 }]] },
"Make Message": { "main": [[{ "node": "LINE Broadcast", "type": "main", "index": 0 }]] },
"LINE Broadcast": { "main": [[{ "node": "Respond to Webhook", "type": "main", "index": 0 }]] }
},
"settings": { "executionOrder": "v1" }
}
⑤ つまずきポイントと対処
このワークフロー自体は、1回目の実行でエラーなく動きました。
いつものカレンダーの予定が、そのままLINEに届いてしまう
- 気をつけること:メインのカレンダーを読むと、仕事や私用の予定の名前がそのまま送られます。一斉送信(broadcast)なので、公式アカウントの友だち全員に届きます。
- 対処:今回は、テスト用に
n8nテストカレンダーを新しく作り、そのIDだけを指定して読みました。また、自分だけが友だちになっている通知専用の公式アカウントに送っています。
まだ確かめていないこと
今回のテストでは、時刻のある予定3件だけを使いました。終日の予定の見え方と、予定が0件の日の「今日の予定はありません」は、まだ実際には確かめていません。
⑥ 応用アイデア
どれも、まだ試していないアイデアです。
- Webhookを「Schedule Trigger」に替えて、毎朝7時に通知する
- 天気予報と今日の予定を、1通にまとめて送る
- 一斉送信を個別送信(push)に替えて、家族それぞれに、その人のカレンダーの予定を送る
- 予定の場所(
location)も一緒に送る
