This workflow corresponds to n8n.io template #13731 — we link there as the canonical source.
This workflow follows the Google Calendar → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "ce57e613-e2d9-42f0-841b-36eeb1520048",
"name": "\ud83d\udccb WORKFLOW OVERVIEW",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1488,
-336
],
"parameters": {
"width": 760,
"height": 516,
"content": "\u2600\ufe0f WhatsApp Daily Briefing Bot\nWorkflow Purpose\nDelivers a personalized morning summary via WATI combining live weather, news, Google Calendar events, and Google Tasks, featuring a motivational OpenAI greeting.\n\n\ud83d\ude80 How it Works\nDual Triggers: Fires automatically at 7 AM or manually when a user texts a command.\n\nData Retrieval: Sequentially fetches weather data, top headlines, and your daily agenda from Google services.\n\nAI Personalization: OpenAI crafts a short, context-aware opener based on your specific daily schedule and local weather.\n\nAssembly & Dispatch: Merges all data into a formatted WhatsApp card for delivery."
},
"typeVersion": 1
},
{
"id": "74dd5c85-9d01-406c-a8f0-3e0ffbd91c4e",
"name": "\u2460 ENTRY POINTS \u00b7 Schedule Trigger \u00b7 Wati Trigger \u00b7 Intent Router",
"type": "n8n-nodes-base.stickyNote",
"position": [
-640,
192
],
"parameters": {
"color": 7,
"width": 560,
"height": 820,
"content": "### Two ways the briefing fires\n\n**A) Automated \u2014 Schedule Trigger (7AM Daily)**\nCron: `0 7 * * *`\nFires every morning \u2192 straight to `Load User Config` \u2192 full briefing pipeline runs for every subscriber.\n\n**B) On-Demand \u2014 Wati Trigger \u2192 Intent Router**\nUser sends a WhatsApp message \u2192 Switch node routes:\n\n**Both A and B converge at `Load User Config`.**\nSchedule path passes subscriber list items; on-demand path passes the sender's phone + default preferences.\n"
},
"typeVersion": 1
},
{
"id": "692cc4bc-33d8-4f8e-ad00-eb4dbfcedc44",
"name": "\u2462 FETCH WEATHER \u00b7 FETCH NEWS \u00b7 Two HTTP GET nodes",
"type": "n8n-nodes-base.stickyNote",
"position": [
528,
-48
],
"parameters": {
"color": 7,
"width": 452,
"height": 532,
"content": "### Weather & News \u2014 two live data fetches\n**Node 1 \u00b7 Fetch Weather** (HTTP GET)\nURL: `https://api.openweathermap.org/data/2.5/weather?q=YOUR_CITY&appid=YOUR_KEY&units=metric`\nReturns: `main.temp`, `main.feels_like`, `main.humidity`, `wind.speed`, `weather[0].description`, `name`\n**Node 2 \u00b7 Fetch News** (HTTP GET)\nURL: `https://newsapi.org/v2/top-headlines?q=YOUR_TOPICS&apiKey=YOUR_KEY&pageSize=3`\nReturns: `articles[]` with `title`, `source.name`, `url`\n\n"
},
"typeVersion": 1
},
{
"id": "8d19c4d7-5876-4c22-bcc3-40efd4d2ebbd",
"name": "\u2463 GET CALENDAR EVENTS \u00b7 GET DUE TASKS \u00b7 Google Calendar + Google Tasks",
"type": "n8n-nodes-base.stickyNote",
"position": [
528,
512
],
"parameters": {
"color": 7,
"width": 468,
"height": 560,
"content": "### Calendar & Tasks \u2014 two Google OAuth2 nodes\n**Node 1 \u00b7 Get Today's Calendar Events** (Google Calendar).Operation: `getAll`.Calendar: `primary`\nReturns: array of events with `summary`, `start.dateTime`, `start.date`, `location`\n**Node 2 \u00b7 Get Due Tasks** (Google Tasks).Resource: `task` \u00b7 Operation: `getAll`.Tasklist: `primary`\nReturns: array of tasks with `title`, `due`, `notes`, `status`\n\n"
},
"typeVersion": 1
},
{
"id": "a65003e7-d073-487a-a25c-6822d23218f6",
"name": "\u2464 OPENAI \u2013 GENERATE DAILY OPENER \u00b7 HTTP POST",
"type": "n8n-nodes-base.stickyNote",
"position": [
1024,
352
],
"parameters": {
"color": 7,
"width": 740,
"height": 424,
"content": "Nodes: OpenAI \u2013 Opener \u2192 Assemble Briefing \u2192 WATI \u2013 Send\nAI Opener: OpenAI (gpt-4o) crafts a unique, 15-word motivational greeting using the subscriber's name, weather, and daily event/task count.\nAggregation: Assemble Briefing merges weather, news, calendar, and task data into a formatted WhatsApp card, with a fallback greeting for reliability.\nDelivery: Delivers the complete morning briefing via WATI."
},
"typeVersion": 1
},
{
"id": "7b013a1b-d87c-41db-a096-02b38c0fd05e",
"name": "\u2466 SUBSCRIBE / STOP / HELP \u00b7 Code + WATI send nodes",
"type": "n8n-nodes-base.stickyNote",
"position": [
-48,
560
],
"parameters": {
"color": 7,
"width": 560,
"height": 632,
"content": "**Subscribe path** (keyword: `subscribe`).`Add Subscriber` code node logs the sender's phone + name..In this template the list lives in `Load User Config` \u2014 edit that node to add them permanently.\n`WATI \u2013 Confirm Subscribe` sends: *\"You're subscribed! Daily briefing at 7AM.\"*\n\n**Stop path** (keyword: `stop`)\n`Remove Subscriber` code node prepares opt-out confirmation..Edit `Load User Config` to remove their entry from the subscribers array.\n`WATI \u2013 Confirm Unsubscribe` sends: *\"You've been unsubscribed. Send subscribe to rejoin.\"*\n\n"
},
"typeVersion": 1
},
{
"id": "b1dba8ba-2685-4ea8-a29f-bfc92eb97b6b",
"name": "Schedule Trigger \u2013 7AM Daily",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-608,
496
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 7 * * *"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "f54dc78a-97be-48b0-b084-641c3b85cb09",
"name": "Intent Router",
"type": "n8n-nodes-base.switch",
"position": [
-368,
784
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Instant Brief",
"conditions": {
"options": {
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.text.toLowerCase().trim() }}",
"rightValue": "brief"
}
]
},
"renameOutput": true
},
{
"outputKey": "Subscribe",
"conditions": {
"options": {
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.text.toLowerCase().trim() }}",
"rightValue": "subscribe"
}
]
},
"renameOutput": true
},
{
"outputKey": "Stop",
"conditions": {
"options": {
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.text.toLowerCase().trim() }}",
"rightValue": "stop"
}
]
},
"renameOutput": true
}
]
},
"options": {
"fallbackOutput": "extra"
}
},
"typeVersion": 3
},
{
"id": "24f956da-c182-41bb-b6db-86ca7bab8d6c",
"name": "Add Subscriber",
"type": "n8n-nodes-base.code",
"position": [
48,
800
],
"parameters": {
"jsCode": "const phone = $json.waId || $json.from;\nconst name = $json.senderName || 'Friend';\nreturn [{ json: {\n phone,\n confirmMsg: `\u2705 *Subscribed, ${name}!*\n\nYou'll get your daily briefing every morning at 7AM.\n\nSend *brief* anytime for an instant update.\nSend *stop* to unsubscribe.`\n}}];"
},
"typeVersion": 2
},
{
"id": "ca36adf1-8db3-4cf4-910f-89a0cf28da00",
"name": "WATI \u2013 Confirm Subscribe",
"type": "n8n-nodes-wati.wati",
"position": [
304,
800
],
"parameters": {
"target": "={{ $json.phone }}",
"messageText": "={{ $json.confirmMsg }}"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "c5bb0f3d-d816-45fa-9c1e-7d62024085e0",
"name": "Remove Subscriber",
"type": "n8n-nodes-base.code",
"position": [
64,
992
],
"parameters": {
"jsCode": "const phone = $json.waId || $json.from;\nconst name = $json.senderName || 'Friend';\nreturn [{ json: {\n phone,\n confirmMsg: `\ud83d\udc4b *Unsubscribed, ${name}.*\n\nNo more daily briefings from us.\nSend *subscribe* anytime to rejoin!`\n}}];"
},
"typeVersion": 2
},
{
"id": "2cbc18da-4776-417b-b4f9-0b2d14d3a3e8",
"name": "WATI \u2013 Confirm Unsubscribe",
"type": "n8n-nodes-wati.wati",
"position": [
304,
992
],
"parameters": {
"target": "={{ $json.phone }}",
"messageText": "={{ $json.confirmMsg }}"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "832b26c7-6737-4401-9753-2f836d3c13c7",
"name": "Load User Config",
"type": "n8n-nodes-base.code",
"position": [
-240,
496
],
"parameters": {
"jsCode": "// \u2500\u2500 Called from TWO entry points \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// 1) Schedule path \u2192 $json has no waId; use the subscriber list below\n// 2) On-demand path \u2192 $json.waId is set; build a single-user object\n\nconst isOnDemand = '917024935915'|| $json.from;\n\nif (isOnDemand) {\n return [{ json: {\n phone: '917024935915' || $json.from,\n name: $json.senderName || 'Friend',\n city: 'Mumbai', // default city for on-demand\n interests: 'technology,business',\n source: 'on-demand'\n }}];\n}\n\n// \u2500\u2500 Edit this list to add/remove subscribers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst subscribers = [\n { phone: 'SUBSCRIBER_PHONE_1', name: 'Priya', city: 'Mumbai', interests: 'technology,startups' },\n { phone: 'SUBSCRIBER_PHONE_2', name: 'Arjun', city: 'Bengaluru', interests: 'finance,business' },\n { phone: 'SUBSCRIBER_PHONE_3', name: 'Sneha', city: 'Delhi', interests: 'health,science' }\n];\n\nreturn subscribers.map(s => ({ json: { ...s, source: 'schedule' } }));"
},
"typeVersion": 2
},
{
"id": "f4066a7d-8dde-4743-8846-f436329b335b",
"name": "Fetch Weather",
"type": "n8n-nodes-base.httpRequest",
"position": [
688,
192
],
"parameters": {
"url": "=https://api.openweathermap.org/data/2.5/weather?q=London&appid=983fb93df498d7d6da5593c98692d94c&units=metric",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "55fb6a6c-674c-4557-aa87-aa6ed3280329",
"name": "Fetch News",
"type": "n8n-nodes-base.httpRequest",
"position": [
688,
352
],
"parameters": {
"url": "=https://newsapi.org/v2/top-headlines?q={{ $('Load User Config').item.json.interests }}&apiKey=YOUR_NEWSAPI_KEY&pageSize=3",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "78f9e775-4c50-4180-918d-7cd26866d621",
"name": "Get Today\u2019s Calendar Events",
"type": "n8n-nodes-base.googleCalendar",
"position": [
688,
736
],
"parameters": {
"options": {},
"calendar": {
"__rl": true,
"mode": "list",
"value": "en.indian#user@example.com",
"cachedResultName": "Holidays in India"
},
"operation": "getAll"
},
"credentials": {
"googleCalendarOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "28c8ec60-2da1-4ab6-b331-50ceea5aa2c1",
"name": "Get Due Tasks",
"type": "n8n-nodes-base.googleTasks",
"position": [
688,
928
],
"parameters": {
"task": "MTUwODEyMDczNTQ2NDkwNjQ5MjQ6MDow",
"operation": "getAll",
"additionalFields": {}
},
"credentials": {
"googleTasksOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "87a08db3-9307-4c2e-8698-c08c45038b9b",
"name": "OpenAI \u2013 Generate Daily Opener",
"type": "n8n-nodes-base.httpRequest",
"position": [
1104,
544
],
"parameters": {
"url": "https://api.openai.com/v1/chat/completions",
"method": "POST",
"options": {},
"sendHeaders": true,
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"nodeCredentialType": "openAiApi"
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
},
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "1fb16907-e723-42f6-abf8-cc0067bc9d43",
"name": "Assemble Briefing",
"type": "n8n-nodes-base.code",
"position": [
1296,
544
],
"parameters": {
"jsCode": "// \u2500\u2500 Collect from all upstream nodes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst sub = $('Load User Config').item.json;\n\n// Weather\nlet weatherLine = '\ud83c\udf24\ufe0f Weather unavailable';\nlet weatherEmoji = '\u2600\ufe0f';\ntry {\n const w = $('Fetch Weather').item.json;\n const temp = Math.round(w.main?.temp || 0);\n const feel = Math.round(w.main?.feels_like || 0);\n const desc = (w.weather?.[0]?.description || 'clear').replace(/\\b\\w/g, c => c.toUpperCase());\n const hum = w.main?.humidity || 0;\n const wind = Math.round((w.wind?.speed || 0) * 3.6);\n const city = w.name || sub.city;\n const iconMap = { clear:'\u2600\ufe0f', cloud:'\u26c5', rain:'\ud83c\udf27\ufe0f', storm:'\u26c8\ufe0f', snow:'\u2744\ufe0f', mist:'\ud83c\udf2b\ufe0f', fog:'\ud83c\udf2b\ufe0f' };\n const icon = iconMap[Object.keys(iconMap).find(k => desc.toLowerCase().includes(k)) || 'cloud'] || '\ud83c\udf24\ufe0f';\n weatherEmoji = icon;\n weatherLine = `${icon} *${city}* \u2014 ${temp}\u00b0C (feels ${feel}\u00b0C) \u00b7 ${desc}\\n \ud83d\udca7 Humidity ${hum}% \u00b7 \ud83d\udca8 Wind ${wind} km/h`;\n} catch(e) {}\n\n// News\nlet newsLines = [' No headlines available'];\ntry {\n const arts = $('Fetch News').item.json.articles || [];\n if (arts.length > 0) {\n newsLines = arts.slice(0, 3).map((a, i) =>\n ` ${i+1}. ${a.title}\\n _${a.source?.name || 'Source'}_`\n );\n }\n} catch(e) {}\n\n// Calendar\nlet calLines = [' \ud83c\udf89 No events today'];\nlet eventCount = 0;\ntry {\n const events = $('Get Today\u2019s Calendar Events').all().map(r => r.json);\n if (events.length > 0) {\n eventCount = events.length;\n calLines = events\n .filter(e => e.start)\n .sort((a, b) => new Date(a.start.dateTime || a.start.date) - new Date(b.start.dateTime || b.start.date))\n .slice(0, 5)\n .map(e => {\n let time = '';\n if (e.start?.dateTime) {\n time = new Date(e.start.dateTime).toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit', hour12: false }) + ' \u00b7 ';\n }\n return ` ${time}${e.summary || 'Untitled event'}`;\n });\n }\n} catch(e) {}\n\n// Tasks\nlet taskLines = [' \u2705 All clear for today'];\nlet taskCount = 0;\ntry {\n const tasks = $('Get Due Tasks').all().map(r => r.json).filter(t => t.title && t.status !== 'completed');\n if (tasks.length > 0) {\n taskCount = tasks.length;\n taskLines = tasks.slice(0, 5).map(t => ` \u2022 ${t.title}`);\n }\n} catch(e) {}\n\n// AI opener\nconst rawOpener = $json?.choices?.[0]?.message?.content || '';\nconst opener = rawOpener.trim() || `Have a productive ${new Date().toLocaleDateString('en-IN', { weekday: 'long' })}, ${sub.name}!`;\n\n// Build message\nconst now = new Date();\nconst dayStr = now.toLocaleDateString('en-IN', { weekday: 'long', day: 'numeric', month: 'short' });\nconst greet = now.getHours() < 12 ? 'Good morning' : now.getHours() < 17 ? 'Good afternoon' : 'Good evening';\nconst calHead = `\ud83d\udcc5 *Your Day*${eventCount > 0 ? ' (' + eventCount + ' event' + (eventCount > 1 ? 's' : '') + ')' : ''}`;\nconst taskHead = `\u2705 *Priority Tasks*${taskCount > 0 ? ' (' + taskCount + ' due today)' : ''}`;\n\nconst msg = [\n `${weatherEmoji} *${greet}, ${sub.name}!* ${dayStr}`,\n '',\n `\ud83d\udcac _${opener}_`,\n '',\n `\ud83c\udf24\ufe0f *Weather*`,\n weatherLine,\n '',\n `\ud83d\udcf0 *Top Headlines*`,\n ...newsLines,\n '',\n calHead,\n ...calLines,\n '',\n taskHead,\n ...taskLines,\n '',\n '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500',\n '_Reply *brief* anytime for a refresh_'\n].join('\\n');\n\nreturn [{ json: { phone: sub.phone, briefing: msg } }];"
},
"typeVersion": 2
},
{
"id": "a32471e4-2101-461f-8f2a-5abdf06107a1",
"name": "WATI \u2013 Send Briefing",
"type": "n8n-nodes-wati.wati",
"position": [
1504,
544
],
"parameters": {
"target": "={{ $json.phone }}",
"messageText": "={{ $json.briefing }}"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "5d04de70-f449-4894-807a-f457c2feaf75",
"name": "Wati Trigger1",
"type": "n8n-nodes-wati.watiTrigger",
"position": [
-608,
816
],
"parameters": {
"event": "messageReceived"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1,
"alwaysOutputData": true
}
],
"connections": {
"Fetch News": {
"main": [
[
{
"node": "Get Today\u2019s Calendar Events",
"type": "main",
"index": 0
}
]
]
},
"Fetch Weather": {
"main": [
[
{
"node": "Fetch News",
"type": "main",
"index": 0
}
]
]
},
"Get Due Tasks": {
"main": [
[
{
"node": "OpenAI \u2013 Generate Daily Opener",
"type": "main",
"index": 0
}
]
]
},
"Intent Router": {
"main": [
[
{
"node": "Load User Config",
"type": "main",
"index": 0
}
],
[
{
"node": "Add Subscriber",
"type": "main",
"index": 0
}
],
[
{
"node": "Remove Subscriber",
"type": "main",
"index": 0
}
],
[]
]
},
"Wati Trigger1": {
"main": [
[
{
"node": "Intent Router",
"type": "main",
"index": 0
}
]
]
},
"Add Subscriber": {
"main": [
[
{
"node": "WATI \u2013 Confirm Subscribe",
"type": "main",
"index": 0
}
]
]
},
"Load User Config": {
"main": [
[
{
"node": "Fetch Weather",
"type": "main",
"index": 0
}
]
]
},
"Assemble Briefing": {
"main": [
[
{
"node": "WATI \u2013 Send Briefing",
"type": "main",
"index": 0
}
]
]
},
"Remove Subscriber": {
"main": [
[
{
"node": "WATI \u2013 Confirm Unsubscribe",
"type": "main",
"index": 0
}
]
]
},
"Get Today\u2019s Calendar Events": {
"main": [
[
{
"node": "Get Due Tasks",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger \u2013 7AM Daily": {
"main": [
[
{
"node": "Load User Config",
"type": "main",
"index": 0
}
]
]
},
"OpenAI \u2013 Generate Daily Opener": {
"main": [
[
{
"node": "Assemble Briefing",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleCalendarOAuth2ApigoogleTasksOAuth2ApihttpHeaderAuthopenAiApiwatiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Transform your morning routine with an automated personal assistant that delivers everything you need to know directly to WhatsApp. This workflow aggregates live data from multiple sources and uses OpenAI to greet you with a context-aware, motivational message based on your…
Source: https://n8n.io/workflows/13731/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Elevate your shopping experience with an AI-driven personal assistant that lives right in your WhatsApp. This template automates the entire lifecycle of a shopping list—from intelligent intake and liv
Who’s it for
This n8n template demonstrates how to automatically fetch upcoming movie releases from TMDB and let users add selected movies to their Google Calendar directly from Telegram. On a daily schedule, the
Busy professionals who want a quick daily update combining their calendar, weather, and top news.
Sync Discord Scheduled Events To Google Calendar. Uses httpRequest, scheduleTrigger, googleCalendar, stickyNote. Scheduled trigger; 9 nodes.