{
  "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
          }
        ]
      ]
    }
  }
}