{
  "nodes": [
    {
      "id": "c8dae6f8-d64b-46c5-a28f-d8bd01575441",
      "name": "Main Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -304,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 604,
        "height": 800,
        "content": "## \ud83d\udcf0 AI Newsletter Generator (RSS \u2192 Email)\n\nAutomatically fetches the latest AI and tech news, summarises it with AI, and delivers a clean newsletter to your inbox \u2014 on demand or on a schedule.\n\n**Perfect for:** developers, marketers, and teams who want a curated AI news digest without manually browsing multiple sources.\n\n---\n\n## How it works\n\n1. **Manual trigger** starts the workflow (replace with a Schedule Trigger for automatic runs).\n2. **RSS feeds** \u2014 The Verge and TechCrunch AI feeds are fetched in parallel.\n3. **Merge & sort** \u2014 All articles are combined and sorted newest-first.\n4. **24-hour filter** \u2014 Only articles published in the last 24 hours pass through. If none are found, the run exits cleanly.\n5. **Limit** \u2014 Caps at 6 articles to keep the newsletter concise and within token limits.\n6. **AI Summarisation loop (Groq)** \u2014 Articles are processed 2 at a time. Each batch is summarised by AI Agent 1 and the result is stored in workflow static memory.\n7. **Formatting** \u2014 Raw AI output is cleaned: escape characters normalised, dashes removed, paragraph spacing fixed.\n8. **Markdown \u2192 HTML** \u2014 Summaries are converted to email-ready HTML.\n9. **Memory collection** \u2014 All loop summaries are extracted, split, aggregated, and passed to the second AI agent.\n10. **AI Assembly (Groq)** \u2014 AI Agent 2 merges all summaries into a single, polished newsletter body.\n11. **Gmail** \u2014 Final newsletter is delivered to the configured recipient.\n\n---\n\n## Setup (~5 minutes)\n\n1. **Groq API** \u2014 Add your key in both *Groq Chat Model* nodes.\n2. **Gmail** \u2014 Connect via OAuth2 in the *Send a message1* node. Set `sendTo` to your recipient email. Update the subject line if desired.\n3. **RSS sources** \u2014 The Verge and TechCrunch AI feeds are pre-configured. Swap or add RSS Feed nodes to change sources.\n4. **Scheduling** \u2014 Replace the Manual Trigger with a Schedule Trigger (e.g. daily at 8 AM) for automatic delivery.\n5. **Run** \u2014 Click *Execute Workflow* to generate and send your first newsletter."
      },
      "typeVersion": 1
    },
    {
      "id": "37f29e22-9993-49b5-ad9c-cef9ac13b502",
      "name": "Section: Fetch & Filter",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        64
      ],
      "parameters": {
        "color": 5,
        "width": 1296,
        "height": 560,
        "content": "## 1\ufe0f\u20e3 Fetch, Filter & Prepare\n\nRSS feeds from The Verge and TechCrunch are fetched in parallel, merged, and sorted newest-first. A JavaScript filter discards any articles older than 24 hours. The remaining articles are capped at 6 items and trimmed to their content snippet before entering the summarisation loop."
      },
      "typeVersion": 1
    },
    {
      "id": "6d34f59f-934c-4d43-a49a-4d32678ae4ce",
      "name": "Section: AI Summarisation Loop",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1728,
        64
      ],
      "parameters": {
        "color": 3,
        "width": 1480,
        "height": 464,
        "content": "## 2\ufe0f\u20e3 AI Summarisation Loop\n\nArticles are processed in batches of 2. For each batch, AI Agent 1 (Groq Llama 3.3 70B) generates a structured professional summary. After formatting and Markdown-to-HTML conversion, the result is saved to workflow static memory. The loop continues until all articles are processed, then hands off to the assembly stage."
      },
      "typeVersion": 1
    },
    {
      "id": "3b5a082e-38e6-4752-bce6-85980afe865c",
      "name": "Section: Assembly & Send",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1936,
        560
      ],
      "parameters": {
        "color": 6,
        "width": 1680,
        "height": 404,
        "content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## 3\ufe0f\u20e3 Assembly & Delivery\n\nOnce the loop finishes, all stored summaries are extracted from memory, split into individual items, and re-aggregated into a single payload. AI Agent 2 (Groq) cleans up formatting and merges everything into a polished newsletter. The output is converted from Markdown to HTML and sent via Gmail."
      },
      "typeVersion": 1
    },
    {
      "id": "3dbff022-af42-43f0-9e01-d8e95f5bf597",
      "name": "Merge All RSS Items1",
      "type": "n8n-nodes-base.merge",
      "position": [
        656,
        368
      ],
      "parameters": {},
      "typeVersion": 3
    },
    {
      "id": "baf3a3d9-751f-41b3-8e64-dcdd62b07f0e",
      "name": "Sort by Date (Newest First)1",
      "type": "n8n-nodes-base.sort",
      "position": [
        880,
        368
      ],
      "parameters": {
        "options": {},
        "sortFieldsUi": {
          "sortField": [
            {
              "order": "descending",
              "fieldName": "pubDate"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2a7b65d9-384b-4b27-a121-d1cd271a14c8",
      "name": "Extract Key Fields1",
      "type": "n8n-nodes-base.set",
      "position": [
        1312,
        368
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "0e5bf66d-413f-4eca-a338-658e84f23d7f",
              "name": "contentSnippet",
              "type": "string",
              "value": "={{ $json.contentSnippet }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "af6ffb32-2f3b-4db8-bdce-51ffa8b54fd8",
      "name": "The verge1",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        416,
        224
      ],
      "parameters": {
        "url": "https://www.theverge.com/rss/ai-artificial-intelligence/index.xml",
        "options": {
          "ignoreSSL": true
        }
      },
      "typeVersion": 1
    },
    {
      "id": "7362b986-8f65-4e0b-9145-3bbcb09afd09",
      "name": "TechCrunch1",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        416,
        464
      ],
      "parameters": {
        "url": "https://techcrunch.com/category/artificial-intelligence/feed/",
        "options": {
          "ignoreSSL": true
        }
      },
      "typeVersion": 1
    },
    {
      "id": "93349678-b158-437f-8676-fe24e6c54333",
      "name": "AI Agent2",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2064,
        240
      ],
      "parameters": {
        "text": "={{ $json.contentSnippet }}",
        "options": {
          "systemMessage": "=You are a professional content assistant.\n\nYour task is to analyze the provided data and create a clear, professional summary formatted as the body of an email.\n\nInstructions:\n\n1. Read all provided items carefully.\n2. Summarize each item in a concise and professional manner.\n3. Maintain a clean structure so the email is easy to read.\n4. Separate each summarized item with a clear paragraph gap.\n5. Use simple, professional language suitable for a business email.\n6. Do NOT include a subject line.\n7. Start the email body with a short introduction such as:\n   \"Here is the summary of the latest updates:\"\n8. Present each summary as a numbered section.\n\nOutput Format:\n\nStart with a short introduction, followed by numbered summaries.\n\nExample structure:\n\nHere is the summary of the latest updates:\n\n1. First Update\n   Provide a short and clear summary of the first item.\n\n2. Second Update\n   Provide a short and clear summary of the second item.\n\n3. Third Update\n   Provide a short and clear summary of the third item.\n\nEnsure the final output looks like a clean professional email body with proper spacing between sections.\n"
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "88493800-dccf-4821-a9b4-557598f616b7",
      "name": "Code in JavaScript1",
      "type": "n8n-nodes-base.code",
      "position": [
        1088,
        368
      ],
      "parameters": {
        "jsCode": "const now = Date.now();\nconst cutoff = now - 24 * 60 * 60 * 1000;\n\nconst filtered = items.filter(item => {\n  const dateStr = item.json.isoDate || item.json.pubDate || item.json.date || null;\n  if (!dateStr) return false;\n  const pubTime = new Date(dateStr).getTime();\n  if (isNaN(pubTime)) return false;\n  return pubTime >= cutoff;\n});\n\nif (filtered.length === 0) {\n  return [{\n    json: {\n      _noArticles: true,\n      message: \"No articles found in the last 24 hours.\",\n      checkedAt: new Date().toISOString()\n    }\n  }];\n}\n\nconsole.log(`\u2705 ${filtered.length} articles passed the 24h filter (out of ${items.length} total)`);\n\nreturn filtered;"
      },
      "typeVersion": 2
    },
    {
      "id": "d5aba26e-3d0c-486c-96b6-36af1a0f150f",
      "name": "Loop Over Items1",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1728,
        304
      ],
      "parameters": {
        "options": {},
        "batchSize": 2
      },
      "typeVersion": 3
    },
    {
      "id": "58427726-0684-43e2-a08b-a1fb24300af5",
      "name": "Markdown2",
      "type": "n8n-nodes-base.markdown",
      "position": [
        2608,
        224
      ],
      "parameters": {
        "mode": "markdownToHtml",
        "options": {},
        "markdown": "={{ $json.professional_summary }}"
      },
      "typeVersion": 1
    },
    {
      "id": "b0b04dcc-3b1d-44a2-a677-fdfea6df9a38",
      "name": "Send a message",
      "type": "n8n-nodes-base.gmail",
      "position": [
        3472,
        592
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "={{ $json.output }}",
        "options": {
          "appendAttribution": false
        },
        "subject": "Your Weekly AI & Tech Newsletter"
      },
      "typeVersion": 2.2
    },
    {
      "id": "41931e4c-e450-475c-b79c-38d80caf4eea",
      "name": "Limit1",
      "type": "n8n-nodes-base.limit",
      "position": [
        1520,
        368
      ],
      "parameters": {
        "maxItems": 6
      },
      "typeVersion": 1
    },
    {
      "id": "01c1abf8-edb4-414c-897a-47df8405ef70",
      "name": "memory1",
      "type": "n8n-nodes-base.code",
      "position": [
        2848,
        240
      ],
      "parameters": {
        "jsCode": "// Access workflow static memory\nconst store = $getWorkflowStaticData('global');\n\n// Initialize storage array if it doesn't exist\nif (!store.collectedSummaries) {\n  store.collectedSummaries = [];\n}\n\n// Push current loop item\nstore.collectedSummaries.push({\n  professional_summary: $json.professional_summary,\n  data: $json.data\n});\n\n// Return current item so the loop continues\nreturn $input.item;"
      },
      "typeVersion": 2
    },
    {
      "id": "59c5fa83-3583-401f-b23a-ef433cae3fde",
      "name": "Groq Chat Model2",
      "type": "@n8n/n8n-nodes-langchain.lmChatGroq",
      "position": [
        2096,
        448
      ],
      "parameters": {
        "model": "llama-3.3-70b-versatile",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "d4e0d226-e627-47f9-a002-9570847bdf5b",
      "name": "Next 3 Items1",
      "type": "n8n-nodes-base.noOp",
      "position": [
        3104,
        288
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "1d043251-5e0d-4848-a7db-71aafc0e334e",
      "name": "Extract Saved data1",
      "type": "n8n-nodes-base.code",
      "position": [
        2016,
        592
      ],
      "parameters": {
        "jsCode": "const store = $getWorkflowStaticData('global');\n\nconst results = store.collectedSummaries || [];\n\n// Optional: clear memory for next run\nstore.collectedSummaries = [];\n\nreturn [\n  {\n    json: {\n      all_summaries: results\n    }\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "9cd43306-a227-470b-bc7d-8fe18ac87d8d",
      "name": "Edit Fields1",
      "type": "n8n-nodes-base.set",
      "position": [
        2656,
        608
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "ddcbc967-72d3-4cc4-9982-d95d481727cc",
              "name": "Data",
              "type": "string",
              "value": "={{ $json.data }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "baeb1c64-2bd8-426e-a6e6-a8469a36fb48",
      "name": "Split Out1",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        2224,
        592
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "all_summaries"
      },
      "typeVersion": 1
    },
    {
      "id": "3162e8b4-d11b-4296-b779-91c337a6ee4d",
      "name": "Aggregate1",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        2432,
        592
      ],
      "parameters": {
        "options": {},
        "fieldsToAggregate": {
          "fieldToAggregate": [
            {
              "fieldToAggregate": "data"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a25f482b-edd9-476d-af0d-6715e57e4a19",
      "name": "Formatting1",
      "type": "n8n-nodes-base.code",
      "position": [
        2384,
        240
      ],
      "parameters": {
        "jsCode": "// Ensure we are working with the input items\nreturn $input.all().map(item => {\n  // Capture the output from your AI Agent\n  let text = item.json.output || \"\";\n\n  // 1. Convert literal '\\n' strings into actual line breaks\n  let formatted = text.replace(/\\\\n/g, '\\n');\n\n  // 2. Remove the ugly dashed separators (-------)\n  formatted = formatted.replace(/-{3,}/g, '');\n\n  // 3. Fix paragraph spacing (ensuring a clean gap)\n  formatted = formatted.replace(/\\n{3,}/g, '\\n\\n').trim();\n\n  return {\n    json: {\n      professional_summary: formatted\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "e7520ad2-9cd4-478d-b4e6-b888ab8b2a1c",
      "name": "AI Agent3",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2880,
        576
      ],
      "parameters": {
        "text": "=you are a helpful assitant who helps to format scattered or unformatted data\n{{ $json.Data }}",
        "options": {
          "systemMessage": "you need to format the data in a ready made way to present to the user.\n\nInstructions:\nremove repetitive headlines, remove unnecessary gaps etc.\noutput:\njust give me the presentable data nothing more."
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "d5bc6659-ac78-4866-b4c9-a7203ff30047",
      "name": "Groq Chat Model3",
      "type": "@n8n/n8n-nodes-langchain.lmChatGroq",
      "position": [
        2880,
        784
      ],
      "parameters": {
        "model": "llama-3.3-70b-versatile",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "808c0a36-9724-4055-a363-b523e1de568c",
      "name": "Markdown3",
      "type": "n8n-nodes-base.markdown",
      "position": [
        3232,
        576
      ],
      "parameters": {
        "mode": "markdownToHtml",
        "options": {},
        "markdown": "={{ $json.output }}"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Limit1": {
      "main": [
        [
          {
            "node": "Loop Over Items1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "memory1": {
      "main": [
        [
          {
            "node": "Next 3 Items1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent2": {
      "main": [
        [
          {
            "node": "Formatting1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent3": {
      "main": [
        [
          {
            "node": "Markdown3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Markdown2": {
      "main": [
        [
          {
            "node": "memory1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Markdown3": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate1": {
      "main": [
        [
          {
            "node": "Edit Fields1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out1": {
      "main": [
        [
          {
            "node": "Aggregate1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "The verge1": {
      "main": [
        [
          {
            "node": "Merge All RSS Items1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Formatting1": {
      "main": [
        [
          {
            "node": "Markdown2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "TechCrunch1": {
      "main": [
        [
          {
            "node": "Merge All RSS Items1",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Edit Fields1": {
      "main": [
        [
          {
            "node": "AI Agent3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Next 3 Items1": {
      "main": [
        [
          {
            "node": "Loop Over Items1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Groq Chat Model2": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent2",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Groq Chat Model3": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent3",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items1": {
      "main": [
        [
          {
            "node": "Extract Saved data1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "AI Agent2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript1": {
      "main": [
        [
          {
            "node": "Extract Key Fields1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Key Fields1": {
      "main": [
        [
          {
            "node": "Limit1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Saved data1": {
      "main": [
        [
          {
            "node": "Split Out1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge All RSS Items1": {
      "main": [
        [
          {
            "node": "Sort by Date (Newest First)1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort by Date (Newest First)1": {
      "main": [
        [
          {
            "node": "Code in JavaScript1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}