AutomationFlowsWeb Scraping › Automated Tech News Digest via RSS

Automated Tech News Digest via RSS

Original n8n title: Tech News Digest

Tech News Digest. Uses rssFeedRead, httpRequest. Scheduled trigger; 11 nodes.

Cron / scheduled trigger★★★★☆ complexity11 nodesRSS Feed ReadHTTP Request
Web Scraping Trigger: Cron / scheduled Nodes: 11 Complexity: ★★★★☆ Added:

This workflow follows the HTTP Request → RSS Feed Read 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": "tech-digest-workflow",
  "name": "Tech News Digest",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * *"
            }
          ]
        }
      },
      "id": "schedule-trigger",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        250,
        300
      ],
      "notes": "Runs daily at 8:00 AM"
    },
    {
      "parameters": {
        "jsCode": "// Load TECH-DIGEST job configuration\nconst JOB_NAME = 'tech-digest';\n\nconst fs = require('fs');\nconst path = `/data/jobs/${JOB_NAME}.json`;\n\ntry {\n  const configData = fs.readFileSync(path, 'utf8');\n  const config = JSON.parse(configData);\n  \n  // Return one item per source\n  const items = config.sources.map(source => ({\n    json: {\n      config: config,\n      source: source,\n      jobName: JOB_NAME\n    }\n  }));\n  \n  return items;\n} catch (error) {\n  throw new Error(`Could not read job config at ${path}: ${error.message}`);\n}"
      },
      "id": "load-config",
      "name": "Load Config",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        450,
        300
      ]
    },
    {
      "parameters": {
        "url": "={{ $json.source.url }}"
      },
      "id": "fetch-rss",
      "name": "Fetch RSS",
      "type": "n8n-nodes-base.rssFeedRead",
      "typeVersion": 1,
      "position": [
        650,
        300
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "jsCode": "// rssFeedRead splits the feed into individual items - each item IS one article.\n// config was on the pre-RSS items; pull it back from the Load Config node.\nconst config = $('Load Config').first().json.config;\nconst now = new Date();\nconst lookbackHours = config?.lookback_hours || 24;\nconst maxAgeMs = lookbackHours * 60 * 60 * 1000;\n\nconst allArticles = [];\nfor (const item of $input.all()) {\n  const pubDate = new Date(item.json.pubDate || item.json.isoDate || Date.now());\n  if ((now - pubDate) <= maxAgeMs) {\n    allArticles.push({\n      json: {\n        title: item.json.title || 'Untitled',\n        description: item.json.contentSnippet || item.json.content || item.json.summary || '',\n        link: item.json.link || item.json.guid || '',\n        pubDate: pubDate.toISOString(),\n        source: item.json.feedTitle || 'News'\n      }\n    });\n  }\n}\n\nconst maxArticles = config?.max_articles || 60;\nreturn allArticles.slice(0, maxArticles);"
      },
      "id": "filter-articles",
      "name": "Filter Articles",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        850,
        300
      ]
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "articles",
        "options": {}
      },
      "id": "aggregate-articles",
      "name": "Aggregate Articles",
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        1050,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const config = $('Load Config').first().json.config;\nconst items = $input.all();\nif (items.length === 0 || !items[0].json.articles || items[0].json.articles.length === 0) {\n  throw new Error('No articles reached Build Prompt - check Filter Articles output');\n}\n\n// Aggregate stores items as plain objects, NOT wrapped in .json\nconst articles = items[0].json.articles;\nlet articleList = '';\nfor (const article of articles) {\n  articleList += `\\n- **${article.title}** (${article.source}): ${(article.description || '').substring(0, 200)}`;\n}\n\nconst systemPrompt = config.system_prompt || 'You are a news summarizer.';\nconst userPrompt = (config.user_prompt_format || 'Summarize these news articles:') + articleList;\n\nreturn [{\n  json: {\n    config,\n    articleCount: articles.length,\n    // Pre-build the Ollama request body to avoid n8n expression serialisation issues\n    ollamaBody: JSON.stringify({\n      model: config.model,\n      messages: [\n        { role: 'system', content: systemPrompt },\n        { role: 'user',   content: userPrompt }\n      ],\n      stream: false,\n      options: config.ollama_options || {}\n    })\n  }\n}];"
      },
      "id": "build-prompt",
      "name": "Build Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1250,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "2a5b6cc6-23a5-4f1c-a2e3-4be9f5ce3b2f",
              "leftValue": "={{ $json.articleCount }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "check-articles",
      "name": "Have Articles?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1450,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $json.config.ollama_url || 'http://host.docker.internal:11434/api/chat' }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.ollamaBody }}",
        "options": {}
      },
      "id": "ollama-summarize",
      "name": "Ollama API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        1650,
        200
      ]
    },
    {
      "parameters": {
        "jsCode": "const config = $('Load Config').first().json.config;\nconst httpResponse = $input.first().json;\n\nconst summary = httpResponse?.message?.content\n  || (typeof httpResponse?.message === 'string' ? httpResponse.message : null)\n  || httpResponse?.response\n  || 'No summary returned.';\n\nconst today = new Date().toISOString().split('T')[0];\n\nlet webhookUrl = config?.discord?.webhook_url || '';\nconst envVar = config?.discord?.webhook_url_env;\nif (!webhookUrl && envVar && $env[envVar]) webhookUrl = $env[envVar];\nif (!webhookUrl) throw new Error(`No Discord webhook URL. Set ${envVar || 'DISCORD_WEBHOOK_URL'} in .env or discord.webhook_url in the job config.`);\n\n// Split into <=4000-char chunks (Discord embed limit)\nconst MAX = 4000;\nconst chunks = [];\nif (summary.length <= MAX) {\n  chunks.push(summary);\n} else {\n  const parts = summary.split('\\n\\n');\n  let current = '';\n  for (const part of parts) {\n    if ((current + part).length > MAX) {\n      if (current) chunks.push(current.trim());\n      current = part;\n    } else {\n      current += (current ? '\\n\\n' : '') + part;\n    }\n  }\n  if (current) chunks.push(current.trim());\n}\n\nconst title = config?.name || 'News Digest';\nconst embeds = chunks.map((chunk, i) => ({\n  title: i === 0 ? `${title} - ${today}` : `${title} (cont.)`,\n  description: chunk,\n  color: config?.discord?.embed_color || 3447003,\n  footer: { text: i === chunks.length - 1\n    ? `Generated by ${config?.model || 'Ollama'}`\n    : `Part ${i + 1}/${chunks.length}` }\n}));\n\nconst discordBody = JSON.stringify({ username: config?.discord?.username || 'News Bot', embeds });\nreturn [{ json: { webhookUrl, discordBody } }];"
      },
      "id": "format-discord",
      "name": "Format Discord",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1850,
        200
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $json.webhookUrl }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.discordBody }}",
        "options": {}
      },
      "id": "send-discord",
      "name": "Send to Discord",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        2250,
        200
      ]
    },
    {
      "parameters": {},
      "id": "no-articles",
      "name": "No Articles (Skip)",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1650,
        400
      ]
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [
    {
      "name": "tech-digest",
      "id": "tag-tech-digest"
    },
    {
      "name": "cron",
      "id": "tag-cron"
    },
    {
      "name": "ollama",
      "id": "tag-ollama"
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Load Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Config": {
      "main": [
        [
          {
            "node": "Fetch RSS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch RSS": {
      "main": [
        [
          {
            "node": "Filter Articles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Articles": {
      "main": [
        [
          {
            "node": "Aggregate Articles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Articles": {
      "main": [
        [
          {
            "node": "Build Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Prompt": {
      "main": [
        [
          {
            "node": "Have Articles?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Have Articles?": {
      "main": [
        [
          {
            "node": "Ollama API",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Articles (Skip)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ollama API": {
      "main": [
        [
          {
            "node": "Format Discord",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Discord": {
      "main": [
        [
          {
            "node": "Send to Discord",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "description": "Tech News Digest - Runs daily at 8:00 AM. Loads tech-digest.json job config and sends summaries to Discord."
}
Pro

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

About this workflow

Tech News Digest. Uses rssFeedRead, httpRequest. Scheduled trigger; 11 nodes.

Source: https://github.com/JimBimBomBom/AI-setup/blob/b26fd81e7611043b749ef6bc464ae553d7a4d38b/n8n/workflows/tech-digest.json — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

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

Web Scraping

Blog Post → Social Media. Uses rssFeedRead, httpRequest. Scheduled trigger; 24 nodes.

RSS Feed Read, HTTP Request
Web Scraping

Kairos - RSS Processor v3. Uses httpRequest, rssFeedRead. Scheduled trigger; 23 nodes.

HTTP Request, RSS Feed Read
Web Scraping

NJOOBA RSS Feed Aggregator V3. Uses rssFeedRead, httpRequest. Scheduled trigger; 13 nodes.

RSS Feed Read, HTTP Request
Web Scraping

NJOOBA RSS Feed Aggregator V2. Uses rssFeedRead, httpRequest. Scheduled trigger; 13 nodes.

RSS Feed Read, HTTP Request
Web Scraping

同步豆瓣想看列表到 Sonarr. Uses httpRequest, rssFeedRead. Scheduled trigger; 11 nodes.

HTTP Request, RSS Feed Read