This workflow follows the Gmail → HTTP Request 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 →
{
"name": "AI News and weekly digest",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"triggerAtDay": [
1
],
"triggerAtHour": 9
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [
-2512,
-144
],
"id": "cb986641-aaee-4aff-82e8-d4b655f2152d",
"name": "Schedule Trigger"
},
{
"parameters": {
"url": "https://openai.com/news/rss.xml",
"options": {}
},
"type": "n8n-nodes-base.rssFeedRead",
"typeVersion": 1.2,
"position": [
-2112,
-288
],
"id": "1a17fcf3-235b-4ae0-8d16-ded090c45a73",
"name": "RSS Read open AI",
"onError": "continueErrorOutput"
},
{
"parameters": {
"url": "https://www.technologyreview.com/topic/artificial-intelligence/feed/",
"options": {
"response": {
"response": {
"responseFormat": "text"
}
}
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
-2112,
160
],
"id": "3a6f0266-53bc-4895-9a7e-bd70afe9986c",
"name": "HTTP Request",
"onError": "continueErrorOutput"
},
{
"parameters": {
"url": "https://blog.google/technology/ai/rss/",
"options": {}
},
"type": "n8n-nodes-base.rssFeedRead",
"typeVersion": 1.2,
"position": [
-2112,
-112
],
"id": "8942d21f-5f21-4936-9365-df3da3bf9082",
"name": "Google technology",
"onError": "continueErrorOutput"
},
{
"parameters": {
"modelId": {
"__rl": true,
"value": "gpt-4o-mini",
"mode": "list",
"cachedResultName": "GPT-4O-MINI"
},
"responses": {
"values": [
{
"role": "system",
"content": "You are a competitive intelligence analyst for an AI company. You read raw\narticle dumps from competitor blogs and news feeds and distill them into\nconcise, decision useful takeaways for a weekly digest. You always:\n1. Surface only genuinely new product, research, or strategy signals; skip\n fluff, event recaps with no real news, or near duplicate stories.\n2. Write in plain, specific language, no marketing fluff.\n3. Attribute every takeaway to its source article link(s).\n4. Return ONLY valid JSON matching the exact schema given; no commentary,\n no markdown code fences, no extra text before or after the JSON."
},
{
"content": "=Below are raw articles (title, source, link, snippet) collected this week\nfrom competitor RSS feeds. Identify the 3 to 5 most important takeaways\nacross ALL of them combined; compare and connect across sources where\nrelevant, don't just summarize each article one by one.\n\nReturn ONLY a JSON array where each element has this exact shape:\n{\n \"headline\": \"short punchy headline, max 12 words\",\n \"summary\": \"2 to 3 sentence explanation of why this matters\",\n \"sources\": [\"<url1>\", \"<url2>\"]\n}\n\nArticles:\n{{ $json.text }}"
}
]
},
"builtInTools": {},
"options": {
"maxTokens": 1000,
"temperature": 0.3
}
},
"type": "@n8n/n8n-nodes-langchain.openAi",
"typeVersion": 2.3,
"position": [
-640,
-224
],
"id": "08d32188-129f-4ac5-8e3a-561dc739bb27",
"name": "Message a model",
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"sendTo": "prasanthprg25@gmail.com",
"subject": "=Weekly Competitor Digest - {{ $now.format('yyyy-MM-dd') }}",
"message": "={{ $json.digestHtml }}",
"options": {}
},
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.2,
"position": [
560,
-128
],
"id": "54018a8f-4a4f-47fc-8951-d64d26522889",
"name": "Send a message",
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const raw = $input.first().json.data;\nconst items = [];\nconst itemBlocks = raw.match(/<item>([\\s\\S]*?)<\\/item>/g) || [];\n\nconst getTag = (block, tag) => {\n const match = block.match(new RegExp(`<${tag}[^>]*>([\\\\s\\\\S]*?)<\\\\/${tag}>`));\n if (!match) return '';\n return match[1].replace('<![CDATA[', '').replace(']]>', '').trim();\n};\n\nconst getAllTags = (block, tag) => {\n const matches = [...block.matchAll(new RegExp(`<${tag}[^>]*>([\\\\s\\\\S]*?)<\\\\/${tag}>`, 'g'))];\n return matches.map(m => m[1].replace('<![CDATA[', '').replace(']]>', '').trim());\n};\n\nfunction decodeEntities(str) {\n if (!str) return '';\n return str\n .replace(/&#(\\d+);/g, (_, n) => String.fromCharCode(n))\n .replace(/ /g, ' ').replace(/&/g, '&')\n .replace(/"/g, '\"').replace(/'|'/g, \"'\")\n .replace(/</g, '<').replace(/>/g, '>');\n}\n\nfunction stripHtml(html) {\n return decodeEntities(html).replace(/<[^>]*>/g, ' ').replace(/\\s+/g, ' ').trim();\n}\n\nfunction stripBoilerplate(text) {\n return text\n .replace(/This content was produced by[^.]*\\./gi, '')\n .replace(/DOWNLOAD THE REPORT/gi, '')\n .replace(/It was not written by [^.]*\\./gi, '')\n .trim();\n}\n\nfunction truncate(str, n) {\n return str.length > n ? str.slice(0, n) + '\u2026' : str;\n}\n\nfor (const block of itemBlocks) {\n const title = decodeEntities(getTag(block, 'title'));\n const link = getTag(block, 'link');\n const creator = decodeEntities(getTag(block, 'dc:creator'));\n const pubDate = getTag(block, 'pubDate');\n const categories = getAllTags(block, 'category');\n const isSponsored = categories.some(c => c.toLowerCase() === 'sponsored');\n\n const rawDescription = getTag(block, 'description');\n const rawContent = (block.match(/<content:encoded>([\\s\\S]*?)<\\/content:encoded>/) || ['', ''])[1]\n .replace('<![CDATA[', '').replace(']]>', '').trim();\n\n const cleanDescription = stripBoilerplate(stripHtml(rawDescription));\n const cleanContent = stripBoilerplate(stripHtml(rawContent));\n const snippetText = cleanDescription || cleanContent;\n\n items.push({\n json: {\n title,\n link,\n creator: creator || null,\n pubDate,\n isoDate: pubDate ? new Date(pubDate).toISOString() : null,\n categories,\n isSponsored,\n contentSnippet: truncate(snippetText, 300),\n content: cleanContent\n }\n });\n}\n\nreturn items;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1792,
80
],
"id": "630a77c6-70be-45d8-862b-b221cf68f47d",
"name": "XML_Code ",
"onError": "continueErrorOutput"
},
{
"parameters": {
"numberInputs": 3
},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
-1632,
-176
],
"id": "ea75af3f-17ce-467e-98fb-1e7339033e81",
"name": "Data from multiple feed"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "cd20e1fa-ccb9-475f-af87-9cac13fe54c0",
"leftValue": "={{ $json.title }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
},
{
"id": "5cb4eee6-5c07-4d07-b311-c5c6d02e799d",
"leftValue": "={{ Date.parse($json.isoDate || $json.pubDate || 0) }}",
"rightValue": "={{ Date.now() - 7*24*60*60*1000 }}",
"operator": {
"type": "number",
"operation": "gte"
}
},
{
"id": "28e3b497-6f74-484d-a2da-25e8edbda35d",
"leftValue": "={{ $json.isSponsored === true }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.filter",
"typeVersion": 2.3,
"position": [
-1440,
-112
],
"id": "e1427446-da58-40ac-b7f4-6e3b13491939",
"name": "Filter for last 7 day posts"
},
{
"parameters": {
"aggregate": "aggregateAllItemData",
"destinationFieldName": "=articles",
"options": {}
},
"type": "n8n-nodes-base.aggregate",
"typeVersion": 1,
"position": [
-1248,
-112
],
"id": "af735e8f-5bce-4950-a404-983d94eacf7f",
"name": "Combine all the data"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "523564c7-cbe0-4c39-a2ff-7c299ea2287c",
"leftValue": "={{ $json.articles.length }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "gt"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-1056,
-96
],
"id": "51c7b60c-b5a9-4954-8a93-2c57ef2a3326",
"name": "condition to check if the content is empty"
},
{
"parameters": {
"jsCode": "const articles = $input.first().json.articles || [];\n\nfunction stripHtml(html) {\n if (!html) return '';\n return String(html).replace(/<[^>]*>/g, ' ').replace(/\\s+/g, ' ').trim();\n}\n\nfunction truncate(str, n) {\n str = stripHtml(str);\n return str.length > n ? str.slice(0, n) + '\u2026' : str;\n}\n\nfunction sourceFromLink(link) {\n if (!link) return 'unknown-source';\n const match = String(link).match(/^https?:\\/\\/(?:www\\.)?([^\\/]+)/i);\n return match ? match[1] : 'unknown-source';\n}\n\nfunction getAuthor(creator) {\n if (!creator) return null;\n if (typeof creator === 'string') return creator; // MIT shape\n if (creator?.name?.[0]) return creator.name[0]; // Google shape (nested object)\n return null;\n}\n\nconst blocks = articles.map((a, i) => {\n const title = a.title || 'Untitled';\n const link = a.link || '';\n const snippet = truncate(a.contentSnippet || a.content || '', 300);\n const source = sourceFromLink(link);\n const author = getAuthor(a.creator);\n return `${i + 1}. [${source}]${author ? ` by ${author}` : ''} ${title}\\nLink: ${link}\\nSnippet: ${snippet}`;\n}).join('\\n\\n');\n\nreturn [{\n json: {\n text: blocks,\n articleCount: articles.length\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-848,
-224
],
"id": "0c612109-0ad6-45c1-8043-acc5dfd3197d",
"name": "Parse XML"
},
{
"parameters": {
"jsCode": "const item = $input.first().json;\n\n// Extract the text from the Responses API structure\nconst raw = item.output?.[0]?.content?.[0]?.text\n ?? item.message?.content\n ?? item.content\n ?? item.text\n ?? '';\n\nlet takeaways = [];\ntry {\n const cleaned = raw.trim().replace(/^```json\\s*/i, '').replace(/```$/, '').trim();\n takeaways = JSON.parse(cleaned);\n if (!Array.isArray(takeaways)) throw new Error('not an array');\n} catch (e) {\n takeaways = [{\n headline: 'Digest summary (raw, unparsed)',\n summary: String(raw).slice(0, 1500),\n sources: []\n }];\n}\n\nconst today = new Date().toISOString().slice(0, 10);\n\n// Slack mrkdwn (note: single asterisk bold, not **double**)\nlet slackText = `*\ud83d\udcf0 Weekly Competitor News Digest \u2014 ${today}*\\n\\n`;\ntakeaways.forEach((t, i) => {\n slackText += `*${i + 1}. ${t.headline || 'Untitled takeaway'}*\\n${t.summary || ''}\\n`;\n if (t.sources?.length) {\n slackText += t.sources.map(s => `<${s}|source>`).join(' ') + '\\n';\n }\n slackText += '\\n';\n});\n\n// HTML for Gmail\nlet htmlBody = `<h2>\ud83d\udcf0 Weekly Competitor News Digest \u2014 ${today}</h2>`;\ntakeaways.forEach((t, i) => {\n htmlBody += `<h3>${i + 1}. ${t.headline || 'Untitled takeaway'}</h3><p>${t.summary || ''}</p>`;\n if (t.sources?.length) {\n htmlBody += '<p>' + t.sources.map(s => `<a href=\"${s}\">source</a>`).join(' \u00b7 ') + '</p>';\n }\n});\n\nreturn [{\n json: {\n digestMarkdown: slackText.trim(),\n digestHtml: htmlBody,\n takeawayCount: takeaways.length\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-288,
-224
],
"id": "5baa45e4-cc89-4795-825d-12b18413eba9",
"name": "Format Digest Output"
},
{
"parameters": {},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
192,
64
],
"id": "e0093bb1-bc4e-45e1-b17a-b672eca702f7",
"name": "Merge if the conditions fail"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "72bc9c76-35c8-4a32-a225-f0c01c34e0e8",
"leftValue": "={{ $json.digestMarkdown }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-80,
-224
],
"id": "5085953b-2018-42f0-84f6-a892f0539cfc",
"name": "Condition to check on digest is not empty"
}
],
"connections": {
"Schedule Trigger": {
"main": [
[
{
"node": "RSS Read open AI",
"type": "main",
"index": 0
},
{
"node": "Google technology",
"type": "main",
"index": 0
},
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"RSS Read open AI": {
"main": [
[
{
"node": "Data from multiple feed",
"type": "main",
"index": 0
}
],
[
{
"node": "Data from multiple feed",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request": {
"main": [
[
{
"node": "XML_Code ",
"type": "main",
"index": 0
}
],
[
{
"node": "XML_Code ",
"type": "main",
"index": 0
}
]
]
},
"Google technology": {
"main": [
[
{
"node": "Data from multiple feed",
"type": "main",
"index": 1
}
],
[
{
"node": "Data from multiple feed",
"type": "main",
"index": 1
}
]
]
},
"Message a model": {
"main": [
[
{
"node": "Format Digest Output",
"type": "main",
"index": 0
}
]
]
},
"XML_Code ": {
"main": [
[
{
"node": "Data from multiple feed",
"type": "main",
"index": 2
}
],
[
{
"node": "Data from multiple feed",
"type": "main",
"index": 2
}
]
]
},
"Data from multiple feed": {
"main": [
[
{
"node": "Filter for last 7 day posts",
"type": "main",
"index": 0
}
],
[]
]
},
"Filter for last 7 day posts": {
"main": [
[
{
"node": "Combine all the data",
"type": "main",
"index": 0
}
]
]
},
"Combine all the data": {
"main": [
[
{
"node": "condition to check if the content is empty",
"type": "main",
"index": 0
}
],
[]
]
},
"condition to check if the content is empty": {
"main": [
[
{
"node": "Parse XML",
"type": "main",
"index": 0
}
],
[
{
"node": "Merge if the conditions fail",
"type": "main",
"index": 1
}
]
]
},
"Parse XML": {
"main": [
[
{
"node": "Message a model",
"type": "main",
"index": 0
}
]
]
},
"Format Digest Output": {
"main": [
[
{
"node": "Condition to check on digest is not empty",
"type": "main",
"index": 0
}
]
]
},
"Merge if the conditions fail": {
"main": [
[
{
"node": "Send a message",
"type": "main",
"index": 0
}
]
]
},
"Condition to check on digest is not empty": {
"main": [
[
{
"node": "Send a message",
"type": "main",
"index": 0
}
],
[
{
"node": "Merge if the conditions fail",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate",
"availableInMCP": false
},
"versionId": "9aa617e3-3420-4d53-a8d6-44ad0fbc0fe4",
"meta": {
"templateCredsSetupCompleted": true
},
"nodeGroups": [],
"id": "HwHXifVEjF1QqGc8",
"tags": []
}
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.
gmailOAuth2openAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
AI News and weekly digest. Uses rssFeedRead, httpRequest, openAi, gmail. Scheduled trigger; 15 nodes.
Source: https://gist.github.com/Prasanth-Gururaj/129acbf720d0cbe6a9f0850d9f675291 — 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.
Workflow runs at 08:00 (UK), AI stories from TechCrunch, The Verge, and the OpenAI Blog via RSS, AI to select and editorially shape one signal into a post and image prompt, generates and QA-checks a p
A scheduled process aggregates content from eight distinct data sources and standardizes all inputs into a unified format. AI models perform sentiment scoring, detect conspiracy or misinformation sign
This workflow monitors filesystem sync and backup jobs by validating their execution logs, not by running or inspecting the jobs themselves.
Stop wasting billable hours on manual time-tracking. AutoTimesheet Pro uses AI to collect emails, meetings, and GitHub work, then writes a clean timesheet straight into Google Sheets. Perfect for deve
Imagine a dedicated financial expert tirelessly working behind the scenes, sifting through every transaction, every investment move, and every accounting entry. That's exactly what this automated syst