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 →
{
"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."
}
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 →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Blog Post → Social Media. Uses rssFeedRead, httpRequest. Scheduled trigger; 24 nodes.
Kairos - RSS Processor v3. Uses httpRequest, rssFeedRead. Scheduled trigger; 23 nodes.
NJOOBA RSS Feed Aggregator V3. Uses rssFeedRead, httpRequest. Scheduled trigger; 13 nodes.
NJOOBA RSS Feed Aggregator V2. Uses rssFeedRead, httpRequest. Scheduled trigger; 13 nodes.
同步豆瓣想看列表到 Sonarr. Uses httpRequest, rssFeedRead. Scheduled trigger; 11 nodes.