AutomationFlowsAI & RAG › Daily RSS Feed Summarizer with AI

Daily RSS Feed Summarizer with AI

Original n8n title: RSS Daily

Rss-Daily. Uses @mendable/n8n-nodes-firecrawl, agent, gmail, lmChatOllama. Scheduled trigger; 38 nodes.

Cron / scheduled trigger★★★★★ complexityAI-powered38 nodes@Mendable/N8N Nodes FirecrawlAgentGmailOllama ChatPostgresHTTP RequestRSS Feed Read
AI & RAG Trigger: Cron / scheduled Nodes: 38 Complexity: ★★★★★ AI nodes: yes Added:

This workflow follows the Agent → 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
{
  "nodes": [
    {
      "parameters": {
        "numberInputs": 3
      },
      "id": "98cdcf1b-a95e-4804-a414-89dcfb3c64e6",
      "name": "Merge All Sources",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        -5536,
        9664
      ]
    },
    {
      "parameters": {
        "jsCode": "// Fetch and parse all RSS feeds with categories\nconst feeds = [\n  { url: 'https://arstechnica.com/feed/', category: 'Tech News' },\n  { url: 'https://noted.lol/rss', category: 'Homelab' },\n  { url: 'https://omgubuntu.co.uk/feed', category: 'Linux' },\n  { url: 'https://9to5linux.com/feed', category: 'Linux' },\n  { url: 'https://www.cyberciti.com/atom/atom.xml', category: 'Linux' }\n];\n\n// Date filter: previous calendar day only\nconst now = new Date();\nconst yesterdayStart = new Date(now);\nyesterdayStart.setDate(yesterdayStart.getDate() - 1);\nyesterdayStart.setHours(0, 0, 0, 0);\n\nconst yesterdayEnd = new Date(now);\nyesterdayEnd.setDate(yesterdayEnd.getDate() - 1);\nyesterdayEnd.setHours(23, 59, 59, 999);\n\nconst allArticles = [];\n\nfor (const feed of feeds) {\n  try {\n    const response = await this.helpers.httpRequest({\n      method: 'GET',\n      url: feed.url,\n      returnFullResponse: false\n    });\n    \n    const xmlData = typeof response === 'string' ? response : (response.data || response.body || '');\n    \n    if (!xmlData) continue;\n    \n    // Detect Atom vs RSS\n    const isAtom = xmlData.includes('<feed') && xmlData.includes('<entry');\n    \n    if (isAtom) {\n      // Parse Atom\n      const entryRegex = /<entry[^>]*>([\\s\\S]*?)<\\/entry>/gi;\n      let match;\n      let count = 0;\n      \n      while ((match = entryRegex.exec(xmlData)) !== null && count < 3) {\n        const entryXml = match[1];\n        \n        const linkMatch = entryXml.match(/<link[^>]*href=[\"']([^\"']+)[\"']/i);\n        const link = linkMatch ? linkMatch[1] : '';\n        \n        const titleMatch = entryXml.match(/<title[^>]*><!\\[CDATA\\[([\\s\\S]*?)\\]\\]><\\/title>/i) ||\n                          entryXml.match(/<title[^>]*>([\\s\\S]*?)<\\/title>/i);\n        const title = titleMatch ? titleMatch[1].replace(/<[^>]+>/g, '').trim() : 'No title';\n        \n        const dateMatch = entryXml.match(/<published>([^<]+)<\\/published>/i) ||\n                         entryXml.match(/<updated>([^<]+)<\\/updated>/i);\n        const pubDate = dateMatch ? new Date(dateMatch[1]) : null;\n        \n        if (link && pubDate && pubDate >= yesterdayStart && pubDate <= yesterdayEnd) {\n          allArticles.push({ title, url: link, category: feed.category, pubDate: pubDate.toISOString() });\n          count++;\n        }\n      }\n    } else {\n      // Parse RSS\n      const itemRegex = /<item[^>]*>([\\s\\S]*?)<\\/item>/gi;\n      let match;\n      let count = 0;\n      \n      while ((match = itemRegex.exec(xmlData)) !== null && count < 3) {\n        const itemXml = match[1];\n        \n        const getTag = (tag) => {\n          const regex = new RegExp(`<${tag}[^>]*><!\\\\[CDATA\\\\[([\\\\s\\\\S]*?)\\\\]\\\\]><\\/${tag}>|<${tag}[^>]*>([\\\\s\\\\S]*?)<\\/${tag}>`, 'i');\n          const m = itemXml.match(regex);\n          return m ? (m[1] || m[2] || '').trim() : '';\n        };\n        \n        const title = getTag('title') || 'No title';\n        const link = getTag('link');\n        const pubDateStr = getTag('pubDate');\n        const pubDate = pubDateStr ? new Date(pubDateStr) : null;\n        \n        if (link && pubDate && pubDate >= yesterdayStart && pubDate <= yesterdayEnd) {\n          allArticles.push({ title, url: link, category: feed.category, pubDate: pubDate.toISOString() });\n          count++;\n        }\n      }\n    }\n  } catch (error) {\n    continue;\n  }\n}\n\nif (allArticles.length === 0) {\n  return [{ json: { empty: true, message: 'No recent articles found' } }];\n}\n\nreturn allArticles.map(a => ({ json: a }));"
      },
      "id": "4a3b0190-c8ba-45b9-a7ae-e0c71bdf47a8",
      "name": "Fetch & Parse All Feeds",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -7392,
        9696
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "17dcef4d-b39e-49c6-8672-c3d3c3e2c1c1",
      "name": "Loop Over Articles",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        -7216,
        9696
      ]
    },
    {
      "parameters": {
        "operation": "scrape",
        "url": "={{ $json.url }}",
        "requestOptions": {}
      },
      "type": "@mendable/n8n-nodes-firecrawl.firecrawl",
      "typeVersion": 1,
      "position": [
        -7008,
        9776
      ],
      "id": "b398aca6-1f9a-4aad-bd83-bd1f49081f37",
      "name": "Scrape URL",
      "credentials": {
        "firecrawlApi": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "// Combine scraped content with original metadata\nconst scraped = $('Scrape URL').first().json;\nconst original = $('Loop Over Articles').first().json;\n\nif (!scraped || scraped.error) {\n  return [{\n    json: {\n      title: original.title || 'No title',\n      url: original.url,\n      category: original.category,\n      content: 'Content could not be scraped.'\n    }\n  }];\n}\n\nconst data = scraped.data || scraped;\n\nreturn [{\n  json: {\n    title: data.metadata?.title || original.title || 'No title',\n    url: original.url,\n    category: original.category,\n    content: data.markdown || data.content || 'No content available'\n  }\n}];"
      },
      "id": "64ecf827-8072-412a-8f54-6ea7114450c9",
      "name": "Format Content",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -6832,
        9776
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=Summarize this news article:\n\n**Title:** {{ $json.title }}\n**Category:** {{ $json.category }}\n**URL:** {{ $json.url }}\n\n**Content:**\n{{ $json.content }}\n\n---\n\nProvide your summary in EXACTLY this format:\n\n**TLDR:** [One to two sentences summarizing the article]\n\n**Key Points:**\n- [First key point]\n- [Second key point]\n- [Third key point]\n\nIMPORTANT: List exactly 3 key points. No more, no less. Stop after the third point. These should be with - and not numbers.",
        "options": {
          "systemMessage": "You are a concise news summarization assistant. Output ONLY the formatted summary, nothing else."
        }
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 2.2,
      "position": [
        -6640,
        9776
      ],
      "id": "7733d1c1-80c8-4e2d-8658-3cc079972ba6",
      "name": "AI Summary Agent"
    },
    {
      "parameters": {
        "jsCode": "const summary = $json.output || '';\nconst original = $('Format Content').first().json;\n\nreturn [{\n  json: {\n    category: original.category,\n    url: original.url,\n    title: original.title,\n    summary: summary\n  }\n}];"
      },
      "id": "3e3c020c-c060-4a30-b029-fa3d8c9d6529",
      "name": "Store Summary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -6352,
        9776
      ]
    },
    {
      "parameters": {
        "amount": 3
      },
      "id": "0546a3f8-934d-4869-bd2b-5a6809a90c06",
      "name": "Wait 3 Seconds",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        -6160,
        9872
      ]
    },
    {
      "parameters": {
        "jsCode": "// Aggregate all summaries into final report\nconst items = $input.all();\n\nconst categories = [\n  { key: 'Tech News', emoji: '\ud83d\udcbb' },\n  { key: 'Homelab', emoji: '\ud83d\udda5\ufe0f' },\n  { key: 'Linux', emoji: '\ud83d\udc27' },\n  { key: 'GitHub Trending', emoji: '\u2b50' },\n  { key: 'Hacker News', emoji: '\ud83d\udd25' }\n];\n\nconst today = new Date();\nconst dateFormatted = today.toLocaleDateString('en-US', { \n  weekday: 'long', \n  year: 'numeric', \n  month: 'long', \n  day: 'numeric' \n});\n\nlet report = `# Daily News Summary\\n`;\nreport += `*Generated on ${dateFormatted}*\\n\\n`;\n\nlet hasContent = false;\nfor (const cat of categories) {\n  const catItems = items.filter(i => i.json.category === cat.key);\n  if (catItems.length > 0) {\n    hasContent = true;\n    report += `## ${cat.emoji} ${cat.key}\\n\\n`;\n    \n    if (cat.key === 'GitHub Trending') {\n      for (const item of catItems) {\n        const title = item.json.title || 'Repository';\n        const lang = item.json.language ? ` (${item.json.language})` : '';\n        report += `### [${title}](${item.json.url})${lang}\\n\\n`;\n        report += item.json.summary + '\\n\\n---\\n';\n      }\n    } else if (cat.key === 'Hacker News') {\n      for (const item of catItems) {\n        const title = item.json.title || 'Story';\n        const points = item.json.points ? ` (${item.json.points} points)` : '';\n        const priority = item.json.priority ? ' \ud83c\udfaf' : '';\n        report += `### [${title}](${item.json.url})${points}${priority}\\n\\n`;\n        report += item.json.summary + '\\n';\n        if (item.json.hnUrl) {\n          report += `\\n[Discuss on HN](${item.json.hnUrl})\\n`;\n        }\n        report += '\\n---\\n';\n      }\n    } else {\n      for (const item of catItems) {\n        const title = item.json.title || 'Article';\n        report += `### [${title}](${item.json.url})\\n\\n`;\n        report += item.json.summary + '\\n\\n---\\n';\n      }\n    }\n    report += '\\n';\n  }\n}\n\nif (!hasContent) {\n  report += 'No articles were found.\\n';\n}\n\nreturn [{ json: { output: report } }];"
      },
      "id": "639e72ea-2e06-4f60-8ca0-2528056db797",
      "name": "Aggregate Summaries",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -5360,
        9680
      ]
    },
    {
      "parameters": {
        "jsCode": "// Convert markdown to clean HTML for email\nconst markdown = $json.output || '';\n\nlet html = markdown\n  .replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href=\"$2\" style=\"color: #1a73e8; text-decoration: none;\">$1</a>')\n  .replace(/^### (.+)$/gm, '<h3 style=\"font-weight: 600; font-size: 15px; margin: 12px 0 4px 0;\">\ud83d\udcc4 $1</h3>')\n  .replace(/^# Daily News Summary$/gm, '<h1 style=\"font-weight: 600; font-size: 24px; margin: 0 0 2px 0;\">\ud83d\udcf0 Daily News Summary</h1>')\n  .replace(/^## (\ud83d\udcbb|\ud83d\udda5\ufe0f|\ud83d\udc27|\u2b50|\ud83d\udd25) (.+)$/gm, '<h2 style=\"font-weight: 600; font-size: 17px; margin: 20px 0 8px 0;\">$1 $2</h2>')\n  .replace(/^# (.+)$/gm, '<h1 style=\"font-weight: 600; font-size: 24px; margin: 0 0 2px 0;\">$1</h1>')\n  .replace(/^## (.+)$/gm, '<h2 style=\"font-weight: 600; font-size: 17px; margin: 20px 0 8px 0;\">$2</h2>')\n  .replace(/\\*\\*TLDR:\\*\\*/g, '<p><strong>\ud83d\udca1 TLDR:</strong>')\n  .replace(/\\*\\*Key Points:\\*\\*/g, '</p><p><strong>\ud83d\udd11 Key Points:</strong></p>')\n  .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')\n  .replace(/\ud83c\udfaf/g, '<span style=\"background: #fef3c7; padding: 2px 6px; border-radius: 4px; font-size: 12px;\">\ud83c\udfaf Priority</span>')\n  .replace(/^- (.+)$/gm, '<li style=\"margin: 1px 0;\">$1</li>')\n  .replace(/^\\* (.+)$/gm, '<li style=\"margin: 1px 0;\">$1</li>')\n  .replace(/^\\d+\\.\\s+(.+)$/gm, '<li style=\"margin: 1px 0;\">$1</li>')\n  .replace(/---/g, '<hr style=\"border: none; border-top: 1px solid #eee; margin: 10px 0;\">')\n  .replace(/\\n/g, '');\n\nhtml = html.replace(/(<li[^>]*>.*?<\\/li>)+/g, '<ul style=\"padding-left: 18px; margin: 2px 0;\">$&</ul>');\n\nconst emailHtml = `<!DOCTYPE html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"></head><body style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 14px; line-height: 1.5; color: #333; max-width: 640px; margin: 0 auto; padding: 24px 16px;\">${html}<p style=\"color: #999; font-size: 11px; margin-top: 20px; padding-top: 10px; border-top: 1px solid #eee;\">\u2728 Generated automatically</p></body></html>`;\n\nreturn [{ json: { output: $json.output, htmlOutput: emailHtml } }];"
      },
      "id": "360d70ec-1172-49ee-aa12-88e32699ebab",
      "name": "Convert to HTML",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -5136,
        9680
      ]
    },
    {
      "parameters": {
        "sendTo": "brandon@hopkins.sh",
        "subject": "=\ud83d\udcf0 Daily News Summary - {{ $now.format('EEEE, MMMM d, yyyy') }}",
        "message": "={{ $json.htmlOutput }}",
        "options": {}
      },
      "id": "4cbeab91-b891-408e-9141-e7cdfae69b8b",
      "name": "Send Email Report",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        -4912,
        9680
      ],
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "model": "mistral:latest",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOllama",
      "typeVersion": 1,
      "position": [
        -6640,
        9936
      ],
      "id": "bf5bdf7e-e142-4ea2-9cb2-f4db4807bb50",
      "name": "Ollama Chat Model",
      "credentials": {
        "ollamaApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Extract and normalize metadata from RSS items\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const json = item.json;\n  \n  const hnLink = json.comments || json.link || '';\n  const storyIdMatch = hnLink.match(/id=(\\d+)/);\n  const storyId = storyIdMatch ? storyIdMatch[1] : '';\n  \n  const description = json.description || json.content || '';\n  \n  let points = 0;\n  let commentCount = 0;\n  \n  const pointsMatch = description.match(/Points:\\s*(\\d+)/i);\n  if (pointsMatch) {\n    points = parseInt(pointsMatch[1], 10);\n  }\n  \n  const commentsMatch = description.match(/Comments:\\s*(\\d+)/i);\n  if (commentsMatch) {\n    commentCount = parseInt(commentsMatch[1], 10);\n  }\n  \n  results.push({\n    json: {\n      title: json.title || '',\n      url: json.link || '',\n      hnLink: json.comments || hnLink,\n      storyId: storyId,\n      points: points,\n      commentCount: commentCount,\n      pubDate: json.pubDate || json.isoDate || new Date().toISOString()\n    }\n  });\n}\n\nreturn results;"
      },
      "id": "f23a679d-c8a2-4f03-8254-cb182db5b4f3",
      "name": "Extract HN Metadata",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -7216,
        10128
      ]
    },
    {
      "parameters": {
        "jsCode": "// Filter out stories we've already processed\nconst incomingStories = $('Extract HN Metadata').all();\nconst processedData = $('Get Processed IDs1').all();\n\nconst processedIds = new Set();\nfor (const row of processedData) {\n  if (row.json.story_id) {\n    processedIds.add(String(row.json.story_id));\n  }\n}\n\nconst newStories = [];\nfor (const story of incomingStories) {\n  const storyId = String(story.json.storyId || '');\n  if (storyId && !processedIds.has(storyId)) {\n    newStories.push(story);\n  }\n}\n\nif (newStories.length === 0) {\n  return [{ json: { _empty: true, message: 'No new stories to process' } }];\n}\n\nreturn newStories;"
      },
      "id": "ae918942-d5c8-4a8d-956e-14a708f0d5e5",
      "name": "Remove HN Duplicates",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -6816,
        10128
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "condition-not-empty",
              "leftValue": "={{ $json._empty }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "notEquals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "78a917e6-7778-4c12-9049-f4c54d36d69f",
      "name": "Has New HN Stories?",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        -6608,
        10128
      ]
    },
    {
      "parameters": {
        "maxItems": 10
      },
      "id": "958305ed-b9d4-40a9-b7c3-b6fe34b2b7c7",
      "name": "Limit HN to Top 10",
      "type": "n8n-nodes-base.limit",
      "typeVersion": 1,
      "position": [
        -6400,
        10128
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "3d5894dd-6582-4508-a410-c1f3c02ac134",
      "name": "Loop Over HN Stories",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        -6192,
        10128
      ]
    },
    {
      "parameters": {
        "operation": "scrape",
        "url": "={{ $json.url }}",
        "requestOptions": {}
      },
      "type": "@mendable/n8n-nodes-firecrawl.firecrawl",
      "typeVersion": 1,
      "position": [
        -5760,
        10128
      ],
      "id": "34e1ad7b-b2bb-4f9c-932b-ad600dfbf514",
      "name": "Scrape HN URL",
      "credentials": {
        "firecrawlApi": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "// Combine scraped content with original HN metadata\nconst scraped = $('Scrape HN URL').first().json;\nconst original = $('Loop Over HN Stories').first().json;\n\nlet content = 'Content could not be scraped.';\n\nif (scraped && !scraped.error) {\n  const data = scraped.data || scraped;\n  content = data.markdown || data.content || 'No content available';\n}\n\nreturn [{\n  json: {\n    title: original.title || 'No title',\n    url: original.url,\n    hnLink: original.hnLink,\n    storyId: original.storyId,\n    points: original.points,\n    commentCount: original.commentCount,\n    content: content\n  }\n}];"
      },
      "id": "ae66b063-9fcd-402e-b040-a66c565a718b",
      "name": "Format HN Content",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -5536,
        10128
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=Summarize this Hacker News article:\n\n**Title:** {{ $json.title }}\n**Points:** {{ $json.points }} | **Comments:** {{ $json.commentCount }}\n**URL:** {{ $json.url }}\n**HN Discussion:** {{ $json.hnLink }}\n\n**Content:**\n{{ $json.content }}\n\n---\n\nProvide your summary in EXACTLY this format:\n\n**TLDR:** [One to two sentences summarizing the article]\n\n**Why It Matters:** [One sentence on relevance to developers, sysadmins, or the tech community]",
        "options": {
          "systemMessage": "You are a concise tech news summarization assistant focused on developers, system administrators, and tech enthusiasts interested in Linux, self-hosting, homelabs, and open-source software. Flag content as priority if it relates to these topics. Output ONLY the formatted summary."
        }
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 2.2,
      "position": [
        -5344,
        10128
      ],
      "id": "611abb67-552f-4698-affb-f59d6bb00818",
      "name": "AI Agent"
    },
    {
      "parameters": {
        "model": "mistral:latest",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOllama",
      "typeVersion": 1,
      "position": [
        -5440,
        10288
      ],
      "id": "0afafe0d-edaf-4c72-9b11-f0e5566c9d9f",
      "name": "Ollama HN Model",
      "credentials": {
        "ollamaApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Store HN summary with metadata\nconst summary = $json.output || '';\nconst original = $('Format HN Content').first().json;\n\n// Check if it's priority content based on keywords\nconst priorityKeywords = ['linux', 'open source', 'self-host', 'homelab', 'privacy', 'docker', 'kubernetes', 'proxmox', 'truenas', 'raspberry pi', 'ansible', 'terraform'];\nconst lowerTitle = (original.title || '').toLowerCase();\nconst lowerContent = (original.content || '').toLowerCase();\nconst isPriority = priorityKeywords.some(kw => lowerTitle.includes(kw) || lowerContent.includes(kw));\n\nreturn [{\n  json: {\n    category: 'Hacker News',\n    url: original.url,\n    hnUrl: original.hnLink,\n    title: original.title,\n    storyId: original.storyId,\n    points: original.points,\n    commentCount: original.commentCount,\n    summary: summary,\n    priority: isPriority\n  }\n}];"
      },
      "id": "5ea65959-7de5-4b47-b790-d84b95ba65f2",
      "name": "Store HN Summary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -5088,
        10128
      ]
    },
    {
      "parameters": {
        "amount": 3
      },
      "id": "dd8f7825-bf38-4564-841f-e0ecbe3d4e4e",
      "name": "Wait 3 Seconds HN",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        -4864,
        10224
      ]
    },
    {
      "parameters": {
        "jsCode": "// Prepare INSERT query for processed stories\nconst items = $input.all();\n\nconst validStories = items.filter(i => i.json.storyId && i.json.category === 'Hacker News');\n\nif (validStories.length === 0) {\n  return [{ json: { _skip: true, message: 'No HN stories to insert' } }];\n}\n\nconst today = new Date().toISOString().split('T')[0];\n\nconst values = validStories.map(item => {\n  const story = item.json;\n  const storyId = String(story.storyId).replace(/'/g, \"''\");\n  const title = String(story.title).replace(/'/g, \"''\");\n  const url = String(story.url).replace(/'/g, \"''\");\n  const category = 'Hacker News';\n  const points = parseInt(story.points) || 0;\n  const priority = story.priority ? 'TRUE' : 'FALSE';\n  \n  return `('${storyId}', '${title}', '${url}', ${points}, '${category}', '${today}', ${priority})`;\n}).join(',\\n');\n\nconst query = `INSERT INTO processed_stories (story_id, title, url, points, category, processed_date, is_priority)\nVALUES\n${values}\nON CONFLICT (story_id) DO UPDATE SET\n  points = EXCLUDED.points,\n  category = EXCLUDED.category,\n  is_priority = EXCLUDED.is_priority`;\n\nreturn [{ json: { query: query, storyCount: validStories.length } }];"
      },
      "id": "e4711b70-29de-4a76-8418-cfc98ba2babf",
      "name": "Build HN Insert",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -5136,
        9856
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "={{ $json.query }}",
        "options": {}
      },
      "id": "171babfa-363e-40f9-9989-6da2339a2dea",
      "name": "Insert HN Stories",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        -4912,
        9856
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "filter-hn-valid",
              "leftValue": "={{ $json.category }}",
              "rightValue": "Hacker News",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f0022c65-70e3-4744-908c-38e53142aba5",
      "name": "Filter HN for Merge",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        -5824,
        9856
      ]
    },
    {
      "parameters": {
        "operation": "extractHtmlContent",
        "extractionValues": {
          "values": [
            {
              "key": "box",
              "cssSelector": "div.Box",
              "returnValue": "html"
            }
          ]
        },
        "options": {}
      },
      "id": "94558806-acda-4124-9261-d2d1ada4efe6",
      "name": "Extract Box1",
      "type": "n8n-nodes-base.html",
      "position": [
        -7216,
        9424
      ],
      "typeVersion": 1.2
    },
    {
      "parameters": {
        "url": "https://github.com/trending?since=daily",
        "options": {}
      },
      "id": "9c9e5adf-1f7b-4406-a5a8-56aad1653f70",
      "name": "Request to Github Trend1",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -7392,
        9424
      ],
      "typeVersion": 4.2
    },
    {
      "parameters": {
        "fieldToSplitOut": "repositories",
        "options": {}
      },
      "id": "c4d5a63b-8233-4a36-9968-f0e01c522aa6",
      "name": "Turn to a list1",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -6832,
        9424
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a0e76646-60d7-44a6-af77-33f27fb465cb",
              "name": "author",
              "type": "string",
              "value": "={{ $json.repository.split('/')[0].trim() }}"
            },
            {
              "id": "a2bd790a-784e-4d72-9a4e-92be22edea8f",
              "name": "title",
              "type": "string",
              "value": "={{ $json.repository.split('/')[1].trim() }}"
            },
            {
              "id": "22f1518a-7081-4417-ab9d-88f26a7b5cfe",
              "name": "repository",
              "type": "string",
              "value": "={{ $json.repository }}"
            },
            {
              "id": "baff9a9f-020a-4968-bb80-a4a91a94144a",
              "name": "url",
              "type": "string",
              "value": "=https://github.com/{{ $json.repository.replaceAll(' ','') }}"
            },
            {
              "id": "f5c48a02-b55d-4167-a823-53ac1d851ee5",
              "name": "created_at",
              "type": "string",
              "value": "={{$now}}"
            },
            {
              "id": "27a44ce9-4b5b-44b2-94d9-eb5b2ae81dcd",
              "name": "description",
              "type": "string",
              "value": "={{ $json.description }}"
            },
            {
              "id": "b1c2d3e4-f5g6-7h8i-9j0k-l1m2n3o4p5q6",
              "name": "language",
              "type": "string",
              "value": "={{ $json.language }}"
            }
          ]
        },
        "options": {}
      },
      "id": "7b1ecfa2-a30e-41f6-b554-d8a0df159112",
      "name": "Set Result Variables1",
      "type": "n8n-nodes-base.set",
      "position": [
        -6384,
        9424
      ],
      "typeVersion": 3.4
    },
    {
      "parameters": {
        "operation": "extractHtmlContent",
        "dataPropertyName": "repositories",
        "extractionValues": {
          "values": [
            {
              "key": "repository",
              "cssSelector": "a.Link"
            },
            {
              "key": "language",
              "cssSelector": "span[itemprop='programmingLanguage']"
            },
            {
              "key": "description",
              "cssSelector": "p"
            }
          ]
        },
        "options": {}
      },
      "id": "012d9ad8-5c52-425e-9c54-3b399c2e4d18",
      "name": "Extract repository data1",
      "type": "n8n-nodes-base.html",
      "position": [
        -6608,
        9424
      ],
      "typeVersion": 1.2
    },
    {
      "parameters": {
        "operation": "extractHtmlContent",
        "dataPropertyName": "box",
        "extractionValues": {
          "values": [
            {
              "key": "repositories",
              "cssSelector": "article.Box-row",
              "returnValue": "html",
              "returnArray": true
            }
          ]
        },
        "options": {
          "trimValues": true,
          "cleanUpText": true
        }
      },
      "id": "c967ddbc-003a-4185-a1d4-e1f8ebb59515",
      "name": "Extract all repositories1",
      "type": "n8n-nodes-base.html",
      "position": [
        -7040,
        9424
      ],
      "typeVersion": 1.2
    },
    {
      "parameters": {
        "maxItems": 10
      },
      "id": "b0a0ee57-026e-43e8-ab62-3b6850d15e9d",
      "name": "Limit to Top ",
      "type": "n8n-nodes-base.limit",
      "position": [
        -6160,
        9424
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "jsCode": "// Format GitHub repos to match news article structure\nreturn $input.all().map(item => ({\n  json: {\n    category: 'GitHub Trending',\n    url: item.json.url,\n    title: `${item.json.author}/${item.json.title}`,\n    summary: item.json.description || 'No description available.',\n    language: item.json.language || null\n  }\n}));"
      },
      "id": "24443d7b-067b-463f-8f2a-1490d760ef51",
      "name": "Format GitHub Items1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -5936,
        9424
      ]
    },
    {
      "parameters": {
        "url": "https://hnrss.org/frontpage?points=75&count=25",
        "options": {}
      },
      "id": "c2b1a7c2-00b1-4f59-8b01-ba50afe4aeef",
      "name": "Fetch HN Trending1",
      "type": "n8n-nodes-base.rssFeedRead",
      "typeVersion": 1.1,
      "position": [
        -7392,
        10128
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT story_id FROM processed_stories WHERE processed_date > CURRENT_DATE - INTERVAL '7 days'",
        "options": {}
      },
      "id": "1a996401-56e5-4e02-8878-ca1cfdb4c595",
      "name": "Get Processed IDs1",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        -7024,
        10128
      ],
      "alwaysOutputData": true,
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 7,
              "triggerAtMinute": 30
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -7776,
        9696
      ],
      "id": "4c1536ac-a3ab-4345-93c2-937366d3b6f2",
      "name": "Run every day at 7AM"
    },
    {
      "parameters": {
        "sendTo": "nima@netbird.io",
        "subject": "=\ud83d\udcf0 Daily News Summary - {{ $now.format('EEEE, MMMM d, yyyy') }}",
        "message": "={{ $json.htmlOutput }}",
        "options": {}
      },
      "id": "c69aefad-3122-4c5e-90f1-a08eb24bdc0e",
      "name": "Send Email Report1",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        -4912,
        9536
      ],
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Merge All Sources": {
      "main": [
        [
          {
            "node": "Aggregate Summaries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch & Parse All Feeds": {
      "main": [
        [
          {
            "node": "Loop Over Articles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Articles": {
      "main": [
        [
          {
            "node": "Merge All Sources",
            "type": "main",
            "index": 1
          }
        ],
        [
          {
            "node": "Scrape URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape URL": {
      "main": [
        [
          {
            "node": "Format Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Content": {
      "main": [
        [
          {
            "node": "AI Summary Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Summary Agent": {
      "main": [
        [
          {
            "node": "Store Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Summary": {
      "main": [
        [
          {
            "node": "Wait 3 Seconds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 3 Seconds": {
      "main": [
        [
          {
            "node": "Loop Over Articles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Summaries": {
      "main": [
        [
          {
            "node": "Convert to HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert to HTML": {
      "main": [
        [
          {
            "node": "Send Email Report",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Email Report1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ollama Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Summary Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Extract HN Metadata": {
      "main": [
        [
          {
            "node": "Get Processed IDs1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove HN Duplicates": {
      "main": [
        [
          {
            "node": "Has New HN Stories?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has New HN Stories?": {
      "main": [
        [
          {
            "node": "Limit HN to Top 10",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Limit HN to Top 10": {
      "main": [
        [
          {
            "node": "Loop Over HN Stories",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over HN Stories": {
      "main": [
        [
          {
            "node": "Build HN Insert",
            "type": "main",
            "index": 0
          },
          {
            "node": "Filter HN for Merge",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Scrape HN URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape HN URL": {
      "main": [
        [
          {
            "node": "Format HN Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format HN Content": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Store HN Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ollama HN Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Store HN Summary": {
      "main": [
        [
          {
            "node": "Wait 3 Seconds HN",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 3 Seconds HN": {
      "main": [
        [
          {
            "node": "Loop Over HN Stories",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build HN Insert": {
      "main": [
        [
          {
            "node": "Insert HN Stories",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter HN for Merge": {
      "main": [
        [
          {
            "node": "Merge All Sources",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Extract Box1": {
      "main": [
        [
          {
            "node": "Extract all repositories1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request to Github Trend1": {
      "main": [
        [
          {
            "node": "Extract Box1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Turn to a list1": {
      "main": [
        [
          {
            "node": "Extract repository data1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Result Variables1": {
      "main": [
        [
          {
            "node": "Limit to Top ",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract repository data1": {
      "main": [
        [
          {
            "node": "Set Result Variables1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract all repositories1": {
      "main": [
        [
          {
            "node": "Turn to a list1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Limit to Top ": {
      "main": [
        [
          {
            "node": "Format GitHub Items1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format GitHub Items1": {
      "main": [
        [
          {
            "node": "Merge All Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch HN Trending1": {
      "main": [
        [
          {
            "node": "Extract HN Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Processed IDs1": {
      "main": [
        [
          {
            "node": "Remove HN Duplicates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run every day at 7AM": {
      "main": [
        [
          {
            "node": "Fetch HN Trending1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Request to Github Trend1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch & Parse All Feeds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "meta": {
    "templateCredsSetupCompleted": true
  }
}

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

Rss-Daily. Uses @mendable/n8n-nodes-firecrawl, agent, gmail, lmChatOllama. Scheduled trigger; 38 nodes.

Source: https://github.com/TechHutTV/homelab/blob/7240cc3f15a58c083276d7c7e65ec77f5cb124bc/automations/rss-daily.json — 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

Main. Uses httpRequest, rssFeedRead, agent, lmChatAzureOpenAi. Scheduled trigger; 59 nodes.

HTTP Request, RSS Feed Read, Agent +2
AI & RAG

This workflow automates the process of generating, reviewing, and publishing blog posts across multiple platforms, now enhanced with support for RSS Feeds as a content source. It streamlines the manag

HTTP Request, Html Extract, RSS Feed Read +9
AI & RAG

Automates sales data analysis and strategic insight generation for sales managers and strategists needing actionable intelligence. Fetches multi-source data from sales, marketing, and financial system

HTTP Request, Agent, OpenAI Chat +6
AI & RAG

Scheduled runs collect data from oil markets, global shipping movements, news sources, and official reports. The system performs statistical checks to detect anomalies and volatility shifts. An AI-dri

HTTP Request, Agent, Gmail +3
AI & RAG

Automatically compare AI-generated email drafts against what your support team actually sent, learn from the differences, and improve future drafts over time — without any model fine-tuning.

Postgres, Gmail, Agent +3