AutomationFlowsAI & RAG › Send Multi-city Weather Forecasts with Ai-enhanced Formatting From…

Send Multi-city Weather Forecasts with Ai-enhanced Formatting From…

Original n8n title: Send Multi-city Weather Forecasts with Ai-enhanced Formatting From Openweathermap to Gmail

BySridevi Edupuganti @edupuganti on n8n.io

This workflow automates weather forecast delivery by collecting city names, fetching 5-day forecasts from OpenWeatherMap, and generating professionally formatted HTML emails using GPT-4. The AI creates condition-based color-coded reports with safety precautions and sends them…

Event trigger★★★★☆ complexityAI-powered20 nodesGmailOpenWeatherMapHTTP RequestForm TriggerOpenAI
AI & RAG Trigger: Event Nodes: 20 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #9909 — we link there as the canonical source.

This workflow follows the Form Trigger → Gmail 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 →

Download .json
{
  "id": "MQmMLg34LuvMsRan",
  "meta": {
    "templateId": "806",
    "templateCredsSetupCompleted": true
  },
  "name": "Weather prediction in 3 cities",
  "tags": [],
  "nodes": [
    {
      "id": "45a9c8c7-b596-4f48-baf8-09a46e4004df",
      "name": "JS - Validate JSON from LLM",
      "type": "n8n-nodes-base.code",
      "position": [
        1824,
        16
      ],
      "parameters": {
        "jsCode": "function extractRaw(o) {\n  if (typeof o === 'string') return o;\n  const m = o.message || o.messages || o;\n  if (typeof m?.content === 'string') return m.content;\n  if (Array.isArray(m?.content)) {\n    const first = m.content[0];\n    if (typeof first?.text?.value === 'string') return first.text.value;\n    if (typeof first?.text === 'string') return first.text;\n  }\n  if (Array.isArray(m)) {\n    const c = m[0]?.content;\n    if (typeof c === 'string') return c;\n    if (Array.isArray(c)) {\n      const first = c[0];\n      if (typeof first?.text?.value === 'string') return first.text.value;\n      if (typeof first?.text === 'string') return first.text;\n    }\n  }\n  return JSON.stringify(o);\n}\nfunction tryParseJSON(s) {\n  if (!s || typeof s !== 'string') return null;\n  try { return JSON.parse(s); } catch {}\n  const start = s.indexOf('{');\n  const end = s.lastIndexOf('}');\n  if (start >= 0 && end > start) {\n    const candidate = s.slice(start, end + 1);\n    try { return JSON.parse(candidate); } catch {}\n  }\n  return null;\n}\n\nconst raw = extractRaw($json);\nconst parsed = tryParseJSON(raw);\n\nif (parsed && parsed.subject && parsed.html) {\n  return [{ subject: parsed.subject, html: parsed.html, ok: true }];\n} else {\n  return [{ ok: false, reason: 'bad_json', raw }];\n}\n"
      },
      "typeVersion": 2
    },
    {
      "id": "f837e628-3bac-4d22-afef-32ba3df0c016",
      "name": "GMAIL - Send Weather Briefing",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1712,
        352
      ],
      "parameters": {
        "sendTo": "<<user@example.com>>",
        "message": "={{ $('LLM - AI Weather Briefing Composer').item.json.message.content.html }}",
        "options": {},
        "subject": "={{ $('LLM - AI Weather Briefing Composer').item.json.message.content.subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "b6c7d8b2-0a69-472d-b508-3453eadeea4b",
      "name": "JS - Bundle Cities for LLM",
      "type": "n8n-nodes-base.code",
      "position": [
        1328,
        96
      ],
      "parameters": {
        "jsCode": "// Pack all incoming items into one payload for the LLM.\nconst cities = items.map(i => ({\n  city: i.json.city,\n  country: i.json.country,\n  summaries: i.json.summaries\n}));\nreturn [{ cities }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "e4a3a918-3b48-44ba-a463-3d77786fdc9a",
      "name": "JS - Build Daily Summaries (5 days)",
      "type": "n8n-nodes-base.code",
      "position": [
        1216,
        288
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// We need to get the asked city from Pick Lat/Lon node\n// Since we can't use $items() reliably, we'll store it differently\n\n// The OpenWeatherMap response has city.name which is the API's name\n// But we need the original asked city name\n// Check if we have it in the input, otherwise fall back to API name\nconst apiCityName = $json.city?.name || 'City';\nconst apiCountry = $json.city?.country || '';\nconst tzOffset = $json.city?.timezone || 0; // seconds\nconst data = $json.list || [];\nconst units = 'metric';\n\n// Use the API city name for now - we'll fix this in the next step\nconst city = apiCityName;\nconst country = apiCountry;\n\nfunction toLocalDateStr(dt) {\n  // dt is unix seconds; shift by city timezone\n  const d = new Date((dt + tzOffset) * 1000);\n  return d.toISOString().slice(0,10); // YYYY-MM-DD\n}\n\n// Group by day\nconst days = {};\nfor (const p of data) {\n  const day = toLocalDateStr(p.dt);\n  if (!days[day]) days[day] = { points: [] };\n  days[day].points.push(p);\n}\n\n// Aggregate per day\nconst daySummaries = Object.entries(days).map(([date, obj]) => {\n  const pts = obj.points;\n\n  const temps = pts.map(p => p.main.temp);\n  const min = Math.min(...temps);\n  const max = Math.max(...temps);\n\n  // wind\n  const windMax = Math.max(...pts.map(p => (p.wind?.gust ?? p.wind?.speed ?? 0)));\n\n  // rain total (mm)\n  const rain = pts.reduce((acc, p) => acc + (p.rain?.['3h'] || 0), 0);\n\n  // pop average (precip probability 0..1)\n  const popAvg = pts.reduce((a,p)=>a+(p.pop||0),0) / (pts.length || 1);\n\n  // humidity average\n  const humAvg = pts.reduce((a,p)=>a+(p.main?.humidity||0),0) / (pts.length || 1);\n\n  // dominant condition by frequency\n  const counts = {};\n  for (const p of pts) {\n    const desc = (p.weather?.[0]?.main || 'Other');\n    counts[desc] = (counts[desc] || 0) + 1;\n  }\n  const dominant = Object.entries(counts).sort((a,b)=>b[1]-a[1])[0]?.[0] || 'Other';\n\n  return {\n    date,\n    min_temp: Number(min.toFixed(1)),\n    max_temp: Number(max.toFixed(1)),\n    dominant_condition: dominant,\n    rain_mm: Number(rain.toFixed(1)),\n    precip_prob_avg: Number((popAvg*100).toFixed(0)),\n    wind_max: Number(windMax.toFixed(1)),\n    humidity_avg: Number(humAvg.toFixed(0)),\n    unit_temp: units === 'metric' ? '\u00b0C' : '\u00b0F',\n    unit_wind: units === 'metric' ? 'm/s' : 'mph'\n  };\n}).sort((a,b)=>a.date.localeCompare(b.date));\n\n// keep only next 5 days\nconst next5 = daySummaries.slice(0,5);\n\nreturn {\n  city,\n  country,\n  timezone_offset_seconds: tzOffset,\n  summaries: next5\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "0a824d7f-d3d3-4c8e-b782-36d5c94191ae",
      "name": "OpenWeatherMap (OWM) - 5 Day Forecast (by City)",
      "type": "n8n-nodes-base.openWeatherMap",
      "position": [
        1024,
        80
      ],
      "parameters": {
        "cityName": "={{ $json.asked_city }}",
        "operation": "5DayForecast"
      },
      "credentials": {
        "openWeatherMapApi": {
          "name": "<your credential>"
        }
      },
      "executeOnce": false,
      "typeVersion": 1
    },
    {
      "id": "2ad3a2e8-3605-4203-a113-44fe99c34e66",
      "name": "JS - Pick Lat/Lon",
      "type": "n8n-nodes-base.code",
      "position": [
        640,
        288
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const asked = $json.city || null;\nconst location = $json.body?.[0] || $json.body;\n\nif (!location || !location.lat || !location.lon) {\n  throw new Error('No valid geocoding results for: ' + asked);\n}\n\nreturn {\n  asked_city: asked,\n  city_from_api: location.name || asked,\n  country: location.country || null,\n  lat: location.lat,\n  lon: location.lon\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "e6202cc9-8516-4c12-beea-1c3dca68dc8a",
      "name": "SET - Carry Asked City to Forecast",
      "type": "n8n-nodes-base.set",
      "position": [
        832,
        288
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "2c7d6017-02ca-4e76-ab6e-000707eea2ab",
              "name": "asked_city",
              "type": "string",
              "value": "={{ $json.asked_city }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "d2f43ed1-2f4a-476b-8e38-4277ce72e786",
      "name": "MERGE - Pair Cities",
      "type": "n8n-nodes-base.merge",
      "position": [
        464,
        288
      ],
      "parameters": {
        "mode": "combine",
        "options": {
          "includeUnpaired": false
        },
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "5dd95724-b5fe-4230-87b4-4554f1dc74ea",
      "name": "HTTP - OWM Geocoding",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        224,
        32
      ],
      "parameters": {
        "url": "https://api.openweathermap.org/geo/1.0/direct\n",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "responseFormat": "json"
            }
          }
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "q",
              "value": "=={{ encodeURIComponent($json.city) }}"
            },
            {
              "name": "limit",
              "value": "1"
            },
            {
              "name": "appid",
              "value": "<<YOUR_API_KEY>>"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "d574f191-aed1-472c-80e0-96f22ef14480",
      "name": "JS - Normalize City Inputs",
      "type": "n8n-nodes-base.code",
      "position": [
        16,
        304
      ],
      "parameters": {
        "jsCode": "// Collect only city names from the form payload\n// Filter out metadata fields like submittedAt and formMode\nconst cities = [];\n\nfor (const [key, value] of Object.entries($json)) {\n  // Exclude known metadata fields\n  if (key === 'submittedAt' || key === 'formMode') {\n    continue;\n  }\n  \n  // Include all other string values that are not empty\n  if (typeof value === 'string' && value.trim()) {\n    cities.push(value.trim());\n  }\n}\n\n// Limit to 3 cities and create one item per city\nreturn cities.slice(0, 3).map(c => ({ city: c }));"
      },
      "typeVersion": 2
    },
    {
      "id": "11ec0415-f62f-4e61-8416-7468c235e2b7",
      "name": "TRIGGER - Input City Names",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -160,
        304
      ],
      "parameters": {
        "options": {},
        "formTitle": "Weather predictor demo task",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Enter City Name 1",
              "placeholder": "eg: Hyderabad",
              "requiredField": true
            },
            {
              "fieldLabel": "Enter City Name 2",
              "placeholder": "eg: Bangalore",
              "requiredField": true
            },
            {
              "fieldLabel": "Enter City Name 3",
              "placeholder": "Chennai",
              "requiredField": true
            }
          ]
        },
        "formDescription": "It provides the weather prediction of 3 cities and provide general tips and precuations"
      },
      "typeVersion": 2.3
    },
    {
      "id": "7b1dfae5-be7d-4d76-9f6c-3bf3bc0eb271",
      "name": "LLM - AI Weather Briefing Composer",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1520,
        16
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "GPT-4O-MINI"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "=You are an expert meteorologist creating professional weather briefings. Return STRICT JSON with keys: subject, html. Use clear, actionable language. No conversational text outside the JSON.\n\n"
            },
            {
              "content": "=You are an expert meteorologist creating professional weather briefings. Return STRICT JSON with keys: subject, html. Use clear, actionable language. No conversational text outside the JSON.\n```\n\n## **Revised User Prompt:**\n```\nCreate a professional 5-day weather forecast email for EACH city in this JSON:\n{{ JSON.stringify($json.cities) }}\n\nREQUIREMENTS:\n\n\ud83d\udccb Structure:\n- Start with a friendly greeting and brief intro\n- For each city, use H2 with \"\ud83c\udf24\ufe0f City \u2014 Country\"\n- HTML table with these columns: Date | Temp Range | Conditions | Rain(mm) | Precip(%) | Wind(m/s) | General Tips | Precautions\n- Add a concluding paragraph with travel/planning tips across all cities\n\n\ud83c\udfa8 Styling (use inline CSS):\n- Header row: background #4A90E2 (blue), white text, bold, font-size: 14px\n- Borders: 1px solid #DEE2E6 on all cells\n- Padding: 10px in cells\n- Font: Arial, sans-serif, font-size: 13px\n- Center-align all table content\n- Table width: 100%, border-collapse: collapse\n\n\ud83c\udf08 CONDITION-BASED ROW COLORS (apply to entire row based on dominant_condition):\n- \"Clear\": background-color: #FFFACD (light yellow)\n- \"Clouds\": background-color: #ADD8E6 (light blue)\n- \"Rain\": background-color: #FFB6C6 (light red/pink)\n- Extremely sunny (max_temp \u2265 35\u00b0C AND condition is \"Clear\"): background-color: #FFBF00 (amber)\n- Default (other conditions): background-color: #FFFFFF (white)\n\n\ud83d\udcca Content Guidelines:\n- Date: Format as \"YYYY-MM-DD (Day)\" - e.g., \"2025-10-19 (Sun)\"\n- Temp Range: Show as \"Min\u00b0C / Max\u00b0C\" (e.g., \"24\u00b0 / 32\u00b0\")\n- Conditions: Add weather emoji + condition name:\n  - Clear: \u2600\ufe0f Clear\n  - Clouds: \u2601\ufe0f Clouds\n  - Rain: \ud83c\udf27\ufe0f Rain\n  - Thunderstorm: \u26c8\ufe0f Thunderstorm\n- Rain: Show mm value\n- Precip%: Show percentage value\n- Wind: Show m/s value\n- General Tips: Brief everyday advice (e.g., \"Stay hydrated\", \"Dress lightly\")\n- Precautions: Specific safety warnings (detailed below)\n\n\u26a0\ufe0f PRECAUTIONS Column Content (show relevant warnings only, or \"None\" if weather is safe):\n- Heavy rain (rain_mm \u2265 10 OR precip_prob_avg \u2265 70): \"\u26a0\ufe0f Heavy rain expected. Carry umbrella, avoid travel if possible. Expect waterlogging.\"\n- Moderate rain (rain_mm \u2265 5 AND < 10 OR precip_prob_avg \u2265 50 AND < 70): \"\u2614 Rain likely. Carry rain gear, drive carefully.\"\n- Extreme heat (max_temp \u2265 35\u00b0C): \"\ud83c\udf21\ufe0f HEAT ALERT: Stay indoors during peak hours (12-4 PM). Drink 3+ liters water. Risk of heat stroke.\"\n- High heat (max_temp \u2265 32\u00b0C AND < 35\u00b0C): \"\u2600\ufe0f Very hot. Limit outdoor activities. Use sunscreen SPF 50+.\"\n- High wind (wind_max \u2265 10 m/s): \"\ud83d\udca8 Strong winds expected. Secure loose objects. Avoid outdoor hoardings.\"\n- Thunderstorms (if \"Thunderstorm\" in conditions): \"\u26c8\ufe0f STORM WARNING: Stay indoors. Unplug electronics. Avoid water bodies.\"\n- Multiple conditions: Combine warnings separated by \" | \"\n- Safe weather: \"None\" or \"\u2705 Safe weather conditions\"\n\n\ud83d\udca1 GENERAL TIPS Column Content (positive, practical daily advice):\n- Clear & pleasant (max < 32\u00b0C): \"Great for outdoor activities\"\n- Clear & hot (max \u2265 32\u00b0C): \"Stay hydrated, use sun protection\"\n- Clouds: \"Comfortable weather, good for sightseeing\"\n- Light rain: \"Keep umbrella handy\"\n- Heavy rain: \"Plan indoor activities\"\n- Windy: \"Secure loose items\"\n\n\ud83d\udcdd Email Subject:\nCreate an engaging subject that highlights the most significant weather event:\n- If any day has rain_mm \u2265 10: \"\u26a0\ufe0f Heavy Rain Alert: [City Names] | 5-Day Forecast\"\n- If any day has max_temp \u2265 35\u00b0C: \"\ud83c\udf21\ufe0f Heat Wave Alert: [City Names] | 5-Day Forecast\"\n- If all days are clear: \"\u2600\ufe0f Sunny Week Ahead: [City Names] | 5-Day Forecast\"\n- Otherwise: \"5-Day Weather Forecast: [City Names]\"\n\n\ud83c\udfaf Conclusion Paragraph:\nInclude:\n- Overall weather pattern across all cities\n- Best days for outdoor activities/travel\n- Most important safety advisory if any extreme weather\n- Friendly closing line\n\nCRITICAL: Apply the condition-based background color to the ENTIRE ROW (all cells in that row), not just the Conditions column.\n\nReturn ONLY valid JSON:\n{\"subject\":\"...\",\"html\":\"...\"}"
            }
          ]
        },
        "jsonOutput": true
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "07e5c472-3d02-4579-a1ef-77bd8ea44bf5",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -816,
        -512
      ],
      "parameters": {
        "width": 608,
        "height": 1056,
        "content": "# Try Out\n\n## Overview\n### This workflow automates weather forecast delivery by collecting city names, fetching 5-day forecasts from OpenWeatherMap, and generating professionally formatted HTML emails using GPT-4. The AI creates condition-based color-coded reports with safety precautions and sends them via Gmail.\n\n## How It Works\nA form trigger collects up to three city names, which are geocoded via OpenWeatherMap API to retrieve coordinates and 5-day forecasts.\n\nJavaScript nodes process the raw weather data into daily summaries, calculating temperature ranges, precipitation levels, wind speeds, and dominant weather conditions.\n\nGPT-4 then generates professionally formatted HTML emails with condition-based color coding: The AI intelligently adds contextual safety warnings for heavy rain, extreme heat, high winds, and thunderstorms.\n\nA validation node ensures proper JSON formatting before Gmail sends the final briefing.\n\n## Use Cases\n\n\u2022 Field ops & construction crew briefings\n\u2022 Travel planning and itinerary preparation\n\u2022 Outdoor event planning & coordination\n\u2022 Logistics and transportation route planning\n\u2022 Real estate property viewing scheduling\n\u2022 Sports and recreational activity planning\n\n## Setup Requirements\n\nOpenWeatherMap API credentials\nOpenAI API key\nGmail OAuth2 authentication\n\n## Need Help?\nJoin the Discord or ask in the Forum!\nREADME file available at https://tinyurl.com/MulticityWeatherForecast"
      },
      "typeVersion": 1
    },
    {
      "id": "39f8510b-c1a3-44b7-97bd-fe491bd72d15",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        176,
        -160
      ],
      "parameters": {
        "color": 4,
        "width": 208,
        "height": 656,
        "content": "## Geocoding (OWM)\n### Resolve each city to lat/lon using OpenWeather Geocoding API."
      },
      "typeVersion": 1
    },
    {
      "id": "746f1466-187a-421b-8160-b4a982dd02a8",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        400,
        96
      ],
      "parameters": {
        "color": 6,
        "width": 560,
        "height": 400,
        "content": "## Pairing Cities \u2194 Coordinates\n### Align normalized city with its geocode (by position) and select asked_city, country, lat, lon."
      },
      "typeVersion": 1
    },
    {
      "id": "fd98f570-9beb-4cb7-9de3-eb1fb2599a33",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        976,
        -144
      ],
      "parameters": {
        "color": 3,
        "width": 208,
        "height": 640,
        "content": "## Forecast Retrieval\n### Pull 5-day forecast per city by coordinates."
      },
      "typeVersion": 1
    },
    {
      "id": "7864bbf7-eeab-43a6-ab00-7d1786a5f414",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1456,
        -144
      ],
      "parameters": {
        "color": 2,
        "width": 576,
        "height": 352,
        "content": "## LLM AI Weather Briefing\n### Ask LLM to produce subject + HTML for an email; validate/extract the result."
      },
      "typeVersion": 1
    },
    {
      "id": "8ac37160-d037-42a3-8900-c70f326c5901",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1200,
        -144
      ],
      "parameters": {
        "color": 4,
        "height": 640,
        "content": "## Summarization & Packaging\n### Convert raw OWM output into per-day tables & guidance; bundle all cities into one payload."
      },
      "typeVersion": 1
    },
    {
      "id": "fe07b361-deae-4f13-b3a7-d85804d5d7a7",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1456,
        224
      ],
      "parameters": {
        "color": 6,
        "width": 576,
        "height": 288,
        "content": "## Delivery \n### Send the formatted briefing to the recipient(s)."
      },
      "typeVersion": 1
    },
    {
      "id": "0d440118-543a-44ca-bdc1-03187c110850",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -192,
        112
      ],
      "parameters": {
        "color": 5,
        "width": 352,
        "height": 384,
        "content": "## Input & Normalization\n### Collect 3 city names from the form and normalize (trim, title-case, dedupe)."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "dd0565ff-6b0c-4003-a4a6-fd5579f32295",
  "connections": {
    "JS - Pick Lat/Lon": {
      "main": [
        [
          {
            "node": "SET - Carry Asked City to Forecast",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "MERGE - Pair Cities": {
      "main": [
        [
          {
            "node": "JS - Pick Lat/Lon",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP - OWM Geocoding": {
      "main": [
        [
          {
            "node": "MERGE - Pair Cities",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JS - Bundle Cities for LLM": {
      "main": [
        [
          {
            "node": "LLM - AI Weather Briefing Composer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JS - Normalize City Inputs": {
      "main": [
        [
          {
            "node": "HTTP - OWM Geocoding",
            "type": "main",
            "index": 0
          },
          {
            "node": "MERGE - Pair Cities",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "TRIGGER - Input City Names": {
      "main": [
        [
          {
            "node": "JS - Normalize City Inputs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JS - Validate JSON from LLM": {
      "main": [
        [
          {
            "node": "GMAIL - Send Weather Briefing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LLM - AI Weather Briefing Composer": {
      "main": [
        [
          {
            "node": "JS - Validate JSON from LLM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SET - Carry Asked City to Forecast": {
      "main": [
        [
          {
            "node": "OpenWeatherMap (OWM) - 5 Day Forecast (by City)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JS - Build Daily Summaries (5 days)": {
      "main": [
        [
          {
            "node": "JS - Bundle Cities for LLM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenWeatherMap (OWM) - 5 Day Forecast (by City)": {
      "main": [
        [
          {
            "node": "JS - Build Daily Summaries (5 days)",
            "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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow automates weather forecast delivery by collecting city names, fetching 5-day forecasts from OpenWeatherMap, and generating professionally formatted HTML emails using GPT-4. The AI creates condition-based color-coded reports with safety precautions and sends them…

Source: https://n8n.io/workflows/9909/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

What it is An automated LinkedIn content system that takes a simple form (idea + optional file), generates LinkedIn posts with OpenAI, stores them in Notion, builds Google Slides carousels, and auto-p

Form Trigger, OpenAI, Notion +6
AI & RAG

This template is designed for content creators, podcasters, businesses, and researchers who need to transcribe long audio recordings that exceed OpenAI Whisper's 25 MB file size limit (~20 minutes of

Form Trigger, HTTP Request, OpenAI +1
AI & RAG

Automatically gather hundreds of real customer reviews from five major platforms in one run using Thordata API and Proxy — Trustpilot, Capterra, Chrome Web Store, TrustRadius, and Product Hunt — then

HTTP Request, OpenAI, Form Trigger +1
AI & RAG

This workflow enables seamless speech-to-text transcription, AI-powered summarization, sentiment analysis, and automated email delivery. It supports two different input modes: Form Upload (Local File)

HTTP Request, OpenAI, Gmail +2
AI & RAG

This workflow automates the "speed-to-lead" process for insurance agencies. It instantly triggers an AI voice call when a new lead comes in, qualifies their needs via conversation, and automatically g

Form Trigger, Airtable, HTTP Request +3