This workflow corresponds to n8n.io template #15753 — we link there as the canonical source.
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 →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "a6cc1a6e-81d7-44d8-875a-7a76c049416b",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-864,
-384
],
"parameters": {
"color": 4,
"width": 572,
"height": 1076,
"content": "## Daily Personalized News Digest Email \u2014 Google News RSS + GPT-4o-mini + Gmail + Sheets\n\nFor anyone who wants a personalized daily news briefing delivered to their inbox every morning \u2014 without manual curation. Every day at 7AM this workflow reads all active topics from your Google Sheet, fetches the latest articles from Google News RSS for each topic, parses the XML with regex, filters to active topics only, and splits each article into an individual item. GPT-4o-mini writes a 3-sentence plain-English summary per article. A Code node groups all summarized articles by topic, builds a clean HTML email with topic sections and inline styles, and sends it via Gmail. Google Sheets logs the run with article count, topic count, and sent time.\n\n## How it works\n- **1. Schedule \u2014 Every Day 7AM** triggers the pipeline automatically each morning\n- **2. Google Sheets \u2014 Read Topics** reads all rows from the Topics tab\n- **3. IF \u2014 Active Topics Only** filters rows where Active column equals TRUE \u2014 skips paused topics\n- **4. HTTP \u2014 Fetch Google News RSS** fetches the RSS feed for each topic using the Search Keywords field\n- **5. Code \u2014 Parse RSS + Extract Articles** parses the RSS XML with regex, extracts title, link, description, source, and timeAgo \u2014 respects the Max Articles limit per topic\n- **6. IF \u2014 Has Articles?** skips topics where no articles were found today\n- **7. Code \u2014 Split Into Individual Articles** splits the articles array into one item per article for individual AI processing\n- **8. AI Agent \u2014 Summarize Article** uses GPT-4o-mini to write a 3-line factual plain-English summary per article (max 60 words)\n- **9. OpenAI \u2014 GPT-4o-mini Model** language model attached to the AI Agent\n- **10. Code \u2014 Build HTML Email** collects all summarized items, groups by topic, builds a full HTML email with topic sections and styled article cards\n- **11. Gmail \u2014 Send Digest Email** sends the HTML email to your address\n- **12. Google Sheets \u2014 Log Digest** appends one row to the Digest Log tab with topic count, article count, and sent time\n\n## Set up steps\n1. In **2. Google Sheets \u2014 Read Topics** \u2014 connect your Google Sheets OAuth2 credential and replace `YOUR_GOOGLE_SHEET_ID`\n2. In **9. OpenAI \u2014 GPT-4o-mini Model** \u2014 connect your OpenAI credential\n3. In **11. Gmail \u2014 Send Digest Email** \u2014 connect your Gmail OAuth2 credential and replace `YOUR_EMAIL_ADDRESS`\n4. In **12. Google Sheets \u2014 Log Digest** \u2014 connect your Google Sheets OAuth2 credential and replace `YOUR_LOG_SHEET_ID`\n5. Create a Google Sheet with two tabs \u2014 Topics (columns: Topic, Search Keywords, Max Articles, Active) and Digest Log (columns: Date, Topics Count, Articles Found, Email Sent, Sent At)\n6. Add your topics to the Topics tab with Active set to TRUE. Set Active to FALSE to pause a topic without deleting it"
},
"typeVersion": 1
},
{
"id": "e563e930-518f-42b4-bab5-f79e7f9ade07",
"name": "Section \u2014 Schedule Trigger",
"type": "n8n-nodes-base.stickyNote",
"position": [
-240,
-96
],
"parameters": {
"color": 5,
"width": 292,
"height": 388,
"content": "## Schedule Trigger\nFires every day at 7AM automatically. Change the cron expression to adjust time \u2014 0 7 * * * means 7:00 AM daily. Can also be triggered manually."
},
"typeVersion": 1
},
{
"id": "47b0ef1e-6c64-46f1-b41d-23727c488159",
"name": "Section \u2014 Topics Sheet Read and Active Filter",
"type": "n8n-nodes-base.stickyNote",
"position": [
80,
-64
],
"parameters": {
"color": 5,
"width": 468,
"height": 292,
"content": "## Topics Sheet Read and Active Filter\nReads all rows from the Topics tab. IF filters only rows where the Active column equals TRUE \u2014 rows set to FALSE are skipped without being deleted."
},
"typeVersion": 1
},
{
"id": "a9aca568-85f5-4314-ba1f-205ce3ca029d",
"name": "Section \u2014 Google News RSS Fetch and Article Parse",
"type": "n8n-nodes-base.stickyNote",
"position": [
592,
-128
],
"parameters": {
"color": 6,
"width": 500,
"height": 388,
"content": "## Google News RSS Fetch and Article Parse\nFetches RSS feed for each topic using the Search Keywords field. Code node parses XML with regex, extracts title, link, description, source, and time ago. Respects the Max Articles limit per topic."
},
"typeVersion": 1
},
{
"id": "1e75970a-7445-4330-83c7-f8204ae438fd",
"name": "Section \u2014 Article Filter, Split, and AI Summary",
"type": "n8n-nodes-base.stickyNote",
"position": [
1152,
-208
],
"parameters": {
"color": 6,
"width": 804,
"height": 596,
"content": "## Article Filter, Split, and AI Summary\nIF skips topics with no articles found today. Code splits the articles array into individual items. GPT-4o-mini writes a 3-line factual plain-English summary per article (max 60 words, no bullets)."
},
"typeVersion": 1
},
{
"id": "d738ab7a-c004-447e-9860-e50b61912e7e",
"name": "Section \u2014 HTML Email Build, Gmail Send, and Digest Log",
"type": "n8n-nodes-base.stickyNote",
"position": [
2048,
-144
],
"parameters": {
"color": 4,
"width": 708,
"height": 372,
"content": "## HTML Email Build, Gmail Send, and Digest Log\nGroups all summarized articles by topic and builds a full HTML email with styled topic sections. Gmail sends the digest. Google Sheets logs the run with topic count, article count, and sent time."
},
"typeVersion": 1
},
{
"id": "80a8de16-8234-49bf-897a-16d6bd4c5bda",
"name": "1. Schedule \u2014 Every Day 7AM",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-144,
48
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 7 * * *"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "a8b94852-741c-431a-9f91-8bc44a3981b4",
"name": "2. Google Sheets \u2014 Read Topics",
"type": "n8n-nodes-base.googleSheets",
"position": [
128,
48
],
"parameters": {
"operation": "getAll",
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_GOOGLE_SHEET_ID"
}
},
"typeVersion": 4.5
},
{
"id": "ee923df4-a962-4dde-8160-e38defd4be61",
"name": "3. IF \u2014 Active Topics Only",
"type": "n8n-nodes-base.if",
"position": [
352,
48
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "active-check",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json['Active'] }}",
"rightValue": "TRUE"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "6a75ba6c-b667-4553-9cb6-2a1add4ec711",
"name": "4. HTTP \u2014 Fetch Google News RSS",
"type": "n8n-nodes-base.httpRequest",
"position": [
656,
32
],
"parameters": {
"url": "=https://news.google.com/rss/search?q={{ encodeURIComponent($json['Search Keywords']) }}&hl=en-US&gl=US&ceid=US:en",
"options": {
"response": {
"response": {
"responseFormat": "text"
}
}
}
},
"typeVersion": 4.2
},
{
"id": "7f420497-3bbc-4ccf-833a-07664b36bfea",
"name": "5. Code \u2014 Parse RSS + Extract Articles",
"type": "n8n-nodes-base.code",
"position": [
896,
32
],
"parameters": {
"jsCode": "// Parse RSS XML and extract articles for this topic\nconst rssText = $json.data || $json.body || '';\nconst topicName = $('3. IF \u2014 Active Topics Only').item.json['Topic'];\nconst maxArticles = parseInt($('3. IF \u2014 Active Topics Only').item.json['Max Articles']) || 3;\n\nif (!rssText || rssText.trim().length === 0) {\n return [{ json: { topic: topicName, articles: [], hasArticles: false } }];\n}\n\n// Extract items from RSS XML using regex\nconst itemMatches = rssText.match(/<item>([\\s\\S]*?)<\\/item>/g) || [];\n\nconst articles = [];\n\nfor (const item of itemMatches) {\n if (articles.length >= maxArticles) break;\n\n const titleMatch = item.match(/<title><!\\[CDATA\\[([\\s\\S]*?)\\]\\]><\\/title>/) ||\n item.match(/<title>([^<]*)<\\/title>/);\n const title = titleMatch ? titleMatch[1].trim() : 'No Title';\n\n const linkMatch = item.match(/<link>([^<]*)<\\/link>/) ||\n item.match(/<link\\/>([^<]+)/);\n const link = linkMatch ? linkMatch[1].trim() : '';\n\n const descMatch = item.match(/<description><!\\[CDATA\\[([\\s\\S]*?)\\]\\]><\\/description>/) ||\n item.match(/<description>([^<]*)<\\/description>/);\n const rawDesc = descMatch ? descMatch[1].trim() : '';\n const description = rawDesc.replace(/<[^>]+>/g, ' ').replace(/\\s+/g, ' ').trim();\n\n const sourceMatch = item.match(/<source[^>]*>([^<]*)<\\/source>/);\n const source = sourceMatch ? sourceMatch[1].trim() : 'Google News';\n\n const dateMatch = item.match(/<pubDate>([^<]*)<\\/pubDate>/);\n const pubDateStr = dateMatch ? dateMatch[1].trim() : '';\n const pubDate = pubDateStr ? new Date(pubDateStr) : new Date();\n\n const timeAgo = getTimeAgo(pubDate);\n\n if (title && title !== 'No Title') {\n articles.push({\n title,\n link,\n description: description.substring(0, 300),\n source,\n pubDate: pubDate.toISOString(),\n timeAgo\n });\n }\n}\n\nfunction getTimeAgo(date) {\n const now = new Date();\n const diffMs = now - date;\n const diffHours = Math.floor(diffMs / (1000 * 60 * 60));\n const diffMins = Math.floor(diffMs / (1000 * 60));\n if (diffMins < 60) return `${diffMins} minutes ago`;\n if (diffHours < 24) return `${diffHours} hours ago`;\n return `${Math.floor(diffHours / 24)} days ago`;\n}\n\nreturn [{\n json: {\n topic: topicName,\n articles,\n hasArticles: articles.length > 0,\n articleCount: articles.length\n }\n}];"
},
"typeVersion": 2
},
{
"id": "bfe6f2a8-b339-4199-8e75-151693a6fc28",
"name": "6. IF \u2014 Has Articles?",
"type": "n8n-nodes-base.if",
"position": [
1200,
32
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "has-articles",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.hasArticles }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.3
},
{
"id": "f32d472b-777e-4d0b-9a14-2ab775fc1809",
"name": "7. Code \u2014 Split Into Individual Articles",
"type": "n8n-nodes-base.code",
"position": [
1440,
16
],
"parameters": {
"jsCode": "// Split articles array into individual items for AI processing\nconst topicData = $json;\nconst articles = topicData.articles || [];\n\nreturn articles.map(article => ({\n json: {\n topic: topicData.topic,\n title: article.title,\n link: article.link,\n description: article.description,\n source: article.source,\n timeAgo: article.timeAgo,\n pubDate: article.pubDate\n }\n}));"
},
"typeVersion": 2
},
{
"id": "8c7c2d35-33e9-40b3-819e-59bc638c615f",
"name": "8. AI Agent \u2014 Summarize Article",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
1680,
16
],
"parameters": {
"text": "=Article Title: {{ $json.title }}\n\nArticle Description: {{ $json.description }}\n\nSource: {{ $json.source }}\n\nWrite a 3-line plain English summary of this news article. Be specific and factual. No fluff. Each line should be one clear sentence. Do not use bullet points or numbers \u2014 just 3 plain sentences separated by line breaks.",
"options": {
"systemMessage": "You are a news summarizer. Read the article title and description provided and write a clear 3-line summary in plain simple English. Each line is one sentence. Be factual and specific. Maximum 60 words total. Never use bullet points or numbers."
},
"promptType": "define"
},
"typeVersion": 3.1
},
{
"id": "db32b991-e526-40cb-8ed3-6e3cc29f2dd1",
"name": "9. OpenAI \u2014 GPT-4o-mini Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1680,
240
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {},
"builtInTools": {}
},
"typeVersion": 1.3
},
{
"id": "fa703e5b-aa40-42c6-a2d3-2ceae152cb66",
"name": "10. Code \u2014 Build HTML Email",
"type": "n8n-nodes-base.code",
"position": [
2096,
16
],
"parameters": {
"jsCode": "// Collect all summarized articles and build HTML email\nconst allItems = $input.all();\n\nif (allItems.length === 0) {\n return [{ json: { htmlEmail: '<p>No news found today.</p>', articleCount: 0, topicCount: 0 } }];\n}\n\n// Group articles by topic\nconst groupedByTopic = {};\nfor (const item of allItems) {\n const topic = item.json.topic || 'General';\n if (!groupedByTopic[topic]) {\n groupedByTopic[topic] = [];\n }\n groupedByTopic[topic].push({\n title: item.json.title || '',\n link: item.json.link || '#',\n summary: item.json.output || item.json.summary || 'Summary not available.',\n source: item.json.source || 'News',\n timeAgo: item.json.timeAgo || 'Recently'\n });\n}\n\nconst today = new Date().toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n});\n\nconst totalTopics = Object.keys(groupedByTopic).length;\nconst totalArticles = allItems.length;\n\n// Topic emoji map\nconst topicEmojis = {\n 'AI News': '\ud83e\udd16',\n 'Cricket': '\ud83c\udfcf',\n 'Bitcoin': '\u20bf',\n 'n8n': '\u2699\ufe0f',\n 'Startup India': '\ud83d\ude80',\n 'default': '\ud83d\udcf0'\n};\n\n// Build HTML sections per topic\nlet topicSectionsHtml = '';\nfor (const [topic, articles] of Object.entries(groupedByTopic)) {\n const emoji = topicEmojis[topic] || topicEmojis['default'];\n\n let articlesHtml = '';\n for (const article of articles) {\n const summaryLines = article.summary\n .split('\\n')\n .map(l => l.trim())\n .filter(l => l.length > 0)\n .slice(0, 3);\n const summaryHtml = summaryLines.map(line => `<p style=\"margin:4px 0;font-size:14px;color:#444;line-height:1.6;\">${line}</p>`).join('');\n\n articlesHtml += `\n <div style=\"padding:16px;margin-bottom:12px;background:#ffffff;border-radius:8px;border-left:3px solid #4A90E2;\">\n <h3 style=\"margin:0 0 6px;font-size:15px;font-weight:600;color:#1a1a1a;line-height:1.4;\">\n <a href=\"${article.link}\" style=\"color:#1a1a1a;text-decoration:none;\">${article.title}</a>\n </h3>\n <p style=\"margin:0 0 8px;font-size:12px;color:#888;\">\ud83d\udccc ${article.source} \u2022 ${article.timeAgo}</p>\n ${summaryHtml}\n <a href=\"${article.link}\" style=\"display:inline-block;margin-top:10px;font-size:12px;color:#4A90E2;text-decoration:none;font-weight:500;\">Read full article \u2192</a>\n </div>`;\n }\n\n topicSectionsHtml += `\n <div style=\"margin-bottom:28px;\">\n <div style=\"background:#1a1a1a;padding:12px 20px;border-radius:6px;margin-bottom:12px;\">\n <h2 style=\"margin:0;font-size:16px;color:#ffffff;font-weight:700;letter-spacing:0.5px;\">\n ${emoji} ${topic.toUpperCase()}\n </h2>\n </div>\n ${articlesHtml}\n </div>`;\n}\n\nconst htmlEmail = `\n<!DOCTYPE html>\n<html>\n<head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"></head>\n<body style=\"margin:0;padding:0;background:#f4f4f4;font-family:Arial,sans-serif;\">\n <div style=\"max-width:620px;margin:0 auto;background:#f4f4f4;padding:20px 0;\">\n <div style=\"background:#1a1a1a;padding:28px 32px;text-align:center;border-radius:8px 8px 0 0;\">\n <h1 style=\"margin:0;color:#ffffff;font-size:22px;font-weight:700;\">\u2600\ufe0f Your Daily News Digest</h1>\n <p style=\"margin:8px 0 0;color:#aaaaaa;font-size:13px;\">${today}</p>\n </div>\n <div style=\"background:#4A90E2;padding:10px 32px;\">\n <p style=\"margin:0;color:#ffffff;font-size:13px;text-align:center;\">\n \ud83d\udcca ${totalTopics} topics \u2022 ${totalArticles} articles \u2022 All summaries powered by AI\n </p>\n </div>\n <div style=\"background:#f4f4f4;padding:24px 20px;\">\n ${topicSectionsHtml}\n </div>\n <div style=\"background:#1a1a1a;padding:16px 32px;text-align:center;border-radius:0 0 8px 8px;\">\n <p style=\"margin:0;color:#888888;font-size:11px;\">Your personalized news digest \u2022 Powered by n8n + GPT-4o-mini</p>\n <p style=\"margin:6px 0 0;color:#888888;font-size:11px;\">To add or remove topics, update your Google Sheet</p>\n </div>\n </div>\n</body>\n</html>`;\n\nreturn [{\n json: {\n htmlEmail,\n articleCount: totalArticles,\n topicCount: totalTopics,\n digestDate: today\n }\n}];"
},
"typeVersion": 2
},
{
"id": "0ce8a8d8-b41a-4d12-86b3-df9dfd084eef",
"name": "11. Gmail \u2014 Send Digest Email",
"type": "n8n-nodes-base.gmail",
"position": [
2336,
16
],
"parameters": {
"sendTo": "YOUR_EMAIL_ADDRESS",
"message": "={{ $json.htmlEmail }}",
"options": {
"senderName": "Daily News Digest"
},
"subject": "=\u2600\ufe0f Your Daily News Digest \u2014 {{ $now.toFormat('MMMM d, yyyy') }}"
},
"typeVersion": 2.1
},
{
"id": "336ddd95-4d38-4e05-afd4-009566d85c9e",
"name": "12. Google Sheets \u2014 Log Digest",
"type": "n8n-nodes-base.googleSheets",
"position": [
2576,
16
],
"parameters": {
"columns": {
"value": {
"Date": "={{ $now.toFormat('dd MMMM yyyy') }}",
"Sent At": "={{ $now.toFormat('HH:mm') }}",
"Email Sent": "Yes",
"Topics Count": "={{ $('10. Code \u2014 Build HTML Email').item.json.topicCount }}",
"Articles Found": "={{ $('10. Code \u2014 Build HTML Email').item.json.articleCount }}"
},
"schema": [],
"mappingMode": "defineBelow",
"matchingColumns": []
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Digest Log"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_LOG_SHEET_ID"
}
},
"typeVersion": 4.5
}
],
"connections": {
"6. IF \u2014 Has Articles?": {
"main": [
[
{
"node": "7. Code \u2014 Split Into Individual Articles",
"type": "main",
"index": 0
}
]
]
},
"3. IF \u2014 Active Topics Only": {
"main": [
[
{
"node": "4. HTTP \u2014 Fetch Google News RSS",
"type": "main",
"index": 0
}
]
]
},
"1. Schedule \u2014 Every Day 7AM": {
"main": [
[
{
"node": "2. Google Sheets \u2014 Read Topics",
"type": "main",
"index": 0
}
]
]
},
"10. Code \u2014 Build HTML Email": {
"main": [
[
{
"node": "11. Gmail \u2014 Send Digest Email",
"type": "main",
"index": 0
}
]
]
},
"11. Gmail \u2014 Send Digest Email": {
"main": [
[
{
"node": "12. Google Sheets \u2014 Log Digest",
"type": "main",
"index": 0
}
]
]
},
"9. OpenAI \u2014 GPT-4o-mini Model": {
"ai_languageModel": [
[
{
"node": "8. AI Agent \u2014 Summarize Article",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"2. Google Sheets \u2014 Read Topics": {
"main": [
[
{
"node": "3. IF \u2014 Active Topics Only",
"type": "main",
"index": 0
}
]
]
},
"4. HTTP \u2014 Fetch Google News RSS": {
"main": [
[
{
"node": "5. Code \u2014 Parse RSS + Extract Articles",
"type": "main",
"index": 0
}
]
]
},
"8. AI Agent \u2014 Summarize Article": {
"main": [
[
{
"node": "10. Code \u2014 Build HTML Email",
"type": "main",
"index": 0
}
]
]
},
"5. Code \u2014 Parse RSS + Extract Articles": {
"main": [
[
{
"node": "6. IF \u2014 Has Articles?",
"type": "main",
"index": 0
}
]
]
},
"7. Code \u2014 Split Into Individual Articles": {
"main": [
[
{
"node": "8. AI Agent \u2014 Summarize Article",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Add your news topics to a Google Sheet once — with keywords, article limits, and an Active toggle — and every morning at 7AM a personalized news digest lands in your inbox automatically. The workflow fetches fresh articles from Google News for each active topic, parses the RSS…
Source: https://n8n.io/workflows/15753/ — 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.
This n8n automation workflow automates the creation, scripting, production, and posting of YouTube videos. It leverages AI (OpenAI), image generation (PIAPI), video rendering (Shotstack), and platform
Created by: Peyton Leveillee Last updated: October 2025
The Multi-Model Agency Content Engine is a high-performance editorial system designed for agencies. It solves the "blank page" problem by alternating between real-world social proof and strategic expe
This workflow automates the creation, rendering, approval, and posting of TikTok-style POV (Point of View) videos to Instagram, with cross-posting to Facebook and YouTube. It eliminates manual video p
SEO Blog Article Generation Workflow. Uses outputParserStructured, httpRequest, agent, lmChatOpenAi. Scheduled trigger; 56 nodes.