This workflow corresponds to n8n.io template #16037 — we link there as the canonical source.
This workflow follows the Gmail → OpenAI 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 Content Digest \u2192 Email",
"nodes": [
{
"id": "sticky-01-overview",
"name": "Overview & Setup",
"type": "n8n-nodes-base.stickyNote",
"position": [
-780,
100
],
"parameters": {
"color": 3,
"width": 560,
"height": 560,
"content": "## \ud83d\udcf0 AI Content Digest \u2192 Email\n\nTurns your own list of RSS feeds into one clean, AI-curated HTML briefing in your inbox \u2014 on a schedule. Zero manual reading.\n\n### \ud83d\udc64 Who's it for\nFounders, marketers and researchers who want a personalised weekly briefing instead of doomscrolling feeds.\n\n### \u2699\ufe0f How it works\n1. **Schedule** fires weekly (Mon 08:00).\n2. **RSS Read** pulls every feed; articles are merged, deduped, filtered to the last 7 days and ranked.\n3. **OpenAI** groups them into 2\u20134 themes and writes a one-sentence summary per item as ready-to-send HTML.\n4. **Gmail** sends the branded email.\n\n### \ud83d\udd27 Set up (~5 min)\n- Add an **OpenAI** credential (default `gpt-4o-mini`) and a **Gmail OAuth2** credential.\n- Put your feeds in **Define Feed Sources** and the recipient in **Build Email HTML**.\n- Tune `LOOKBACK_DAYS` / `MAX_ARTICLES` to taste."
},
"typeVersion": 1
},
{
"id": "sticky-01-s1",
"name": "Section 1 \u00b7 Sources",
"type": "n8n-nodes-base.stickyNote",
"position": [
-150,
100
],
"parameters": {
"color": 7,
"width": 520,
"height": 360,
"content": "### 1. Sources\nRuns weekly, then emits one item per feed. Edit the feed list in **Define Feed Sources**."
},
"typeVersion": 1
},
{
"id": "sticky-01-s2",
"name": "Section 2 \u00b7 Fetch & Rank",
"type": "n8n-nodes-base.stickyNote",
"position": [
510,
100
],
"parameters": {
"color": 7,
"width": 540,
"height": 360,
"content": "### 2. Fetch & Rank\nRSS Read runs once per feed and fans out into articles, which are merged, filtered to the last 7 days and ranked newest-first."
},
"typeVersion": 1
},
{
"id": "sticky-01-s3",
"name": "Section 3 \u00b7 AI Curation",
"type": "n8n-nodes-base.stickyNote",
"position": [
1190,
100
],
"parameters": {
"color": 7,
"width": 200,
"height": 360,
"content": "### 3. AI Curation\nOpenAI acts as an editor: groups articles into 2\u20134 themes and writes a one-sentence summary per item as HTML."
},
"typeVersion": 1
},
{
"id": "sticky-01-s4",
"name": "Section 4 \u00b7 Send",
"type": "n8n-nodes-base.stickyNote",
"position": [
1530,
100
],
"parameters": {
"color": 7,
"width": 540,
"height": 360,
"content": "### 4. Send\nThe AI HTML is wrapped in a branded email shell and sent via Gmail. Set the recipient in **Build Email HTML**."
},
"typeVersion": 1
},
{
"id": "schedule-trigger",
"name": "Weekly Monday 08:00",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-100,
220
],
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"triggerAtDay": [
1
],
"triggerAtHour": 8,
"weeksInterval": 1,
"triggerAtMinute": 0
}
]
}
},
"typeVersion": 1.3
},
{
"id": "define-feeds",
"name": "Define Feed Sources",
"type": "n8n-nodes-base.code",
"position": [
220,
220
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// EDIT ME: your RSS feed sources.\n// Add one entry per feed you want in your digest.\n// `source` is a friendly label shown in the email; `url` is the feed.\n// The next node (RSS Read) runs ONCE PER ITEM returned here.\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst FEEDS = [\n { source: 'TechCrunch', url: 'https://techcrunch.com/feed/' },\n { source: 'The Verge', url: 'https://www.theverge.com/rss/index.xml' },\n { source: 'Hacker News', url: 'https://hnrss.org/frontpage' },\n];\n\nreturn FEEDS.map((feed) => ({ json: feed }));"
},
"typeVersion": 2
},
{
"id": "rss-read",
"name": "Read RSS Feed",
"type": "n8n-nodes-base.rssFeedRead",
"onError": "continueRegularOutput",
"position": [
560,
220
],
"parameters": {
"url": "={{ $json.url }}",
"options": {}
},
"typeVersion": 1.2
},
{
"id": "merge-rank",
"name": "Merge, Filter & Rank Articles",
"type": "n8n-nodes-base.code",
"position": [
900,
220
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "// Combine every article from every feed, keep only recent ones,\n// rank newest-first, take the top N, and build a compact text block\n// that the AI can summarize cheaply.\n\nconst LOOKBACK_DAYS = 7; // only keep articles newer than this\nconst MAX_ARTICLES = 15; // cap to control token cost\n\nconst cutoff = Date.now() - LOOKBACK_DAYS * 24 * 60 * 60 * 1000;\n\nconst stripHtml = (s) => String(s || '').replace(/<[^>]*>/g, ' ').replace(/\\s+/g, ' ').trim();\n\nconst seen = new Set();\nconst articles = [];\n\nfor (const item of $input.all()) {\n const a = item.json || {};\n const title = stripHtml(a.title);\n const link = a.link || a.guid || '';\n if (!title || !link) continue;\n\n // Dedupe by link, then by lowercased title\n const key = String(link).split('?')[0].toLowerCase();\n const titleKey = title.toLowerCase();\n if (seen.has(key) || seen.has(titleKey)) continue;\n seen.add(key);\n seen.add(titleKey);\n\n // Parse publish date; if missing/invalid, keep the article (treat as 'now')\n const raw = a.isoDate || a.pubDate || a.published || a.date;\n const ts = raw ? Date.parse(raw) : NaN;\n const ms = Number.isNaN(ts) ? Date.now() : ts;\n if (!Number.isNaN(ts) && ms < cutoff) continue;\n\n const source = a.source || a.creator || (a.meta && a.meta.title) || 'Source';\n const snippet = stripHtml(a.contentSnippet || a.content || a.summary || a.description).slice(0, 220);\n\n articles.push({ title, link, source, snippet, ms });\n}\n\narticles.sort((x, y) => y.ms - x.ms);\nconst top = articles.slice(0, MAX_ARTICLES);\n\nconst articlesText = top\n .map((a, i) => `${i + 1}. ${a.title} \u2014 ${a.source} \u2014 ${a.link}\\n ${a.snippet}`)\n .join('\\n\\n');\n\nreturn [{\n json: {\n articleCount: top.length,\n generatedFor: new Date().toISOString().slice(0, 10),\n articlesText: articlesText || 'No new articles were found in the lookback window.',\n },\n}];"
},
"typeVersion": 2
},
{
"id": "openai-curate",
"name": "Curate & Summarize Digest",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
1240,
220
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "gpt-4o-mini"
},
"options": {
"maxTokens": 1800,
"temperature": 0.4,
"instructions": "You are an expert newsletter editor. From the provided list of articles, produce a concise weekly digest as VALID HTML (a fragment, no <html>/<body> wrapper).\n\nRequirements:\n- Start with a single <p> one-line intro summarizing the week's theme.\n- Group the items into 2\u20134 thematic sections. Each section starts with an <h2> heading.\n- Under each heading, output a <ul> where every <li> is: <a href=\"LINK\">Title</a> \u2014 a one-sentence summary in your own words, then the source in parentheses.\n- Drop near-duplicate stories; keep the strongest version.\n- Do NOT invent links or facts; only use the provided articles.\n- Output ONLY the HTML fragment, with no markdown code fences and no commentary."
},
"simplify": true,
"responses": {
"values": [
{
"role": "user",
"type": "text",
"content": "=Here are this week's candidate articles ({{ $json.articleCount }} items). Curate them into the HTML digest as instructed.\n\n--- ARTICLES ---\n{{ $json.articlesText }}\n--- END ARTICLES ---"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "build-email",
"name": "Build Email HTML",
"type": "n8n-nodes-base.set",
"position": [
1580,
220
],
"parameters": {
"mode": "manual",
"options": {},
"assignments": {
"assignments": [
{
"id": "assign-recipient",
"name": "recipient",
"type": "string",
"value": "you@example.com"
},
{
"id": "assign-digestDate",
"name": "digestDate",
"type": "string",
"value": "={{ $now.format('cccc, dd LLLL yyyy') }}"
},
{
"id": "assign-html",
"name": "emailHtml",
"type": "string",
"value": "=<!DOCTYPE html>\n<html>\n<body style=\"margin:0;padding:0;background:#f4f5f7;font-family:Arial,Helvetica,sans-serif;color:#1f2933;\">\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#f4f5f7;padding:24px 0;\">\n <tr><td align=\"center\">\n <table role=\"presentation\" width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#ffffff;border-radius:12px;overflow:hidden;\">\n <tr><td style=\"background:#0f172a;padding:24px 32px;\">\n <div style=\"color:#ffffff;font-size:20px;font-weight:bold;\">\ud83d\udcf0 Your Weekly Content Digest</div>\n <div style=\"color:#94a3b8;font-size:13px;margin-top:4px;\">{{ $json.digestDate }}</div>\n </td></tr>\n <tr><td style=\"padding:28px 32px;font-size:15px;line-height:1.6;\">\n {{ $('Curate & Summarize Digest').item.json.content }}\n </td></tr>\n <tr><td style=\"padding:18px 32px;border-top:1px solid #e5e7eb;color:#94a3b8;font-size:12px;line-height:1.5;\">\n Curated automatically with n8n + OpenAI. To change the feeds or schedule, edit your workflow.\n </td></tr>\n </table>\n </td></tr>\n </table>\n</body>\n</html>"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "send-email",
"name": "Send Digest Email",
"type": "n8n-nodes-base.gmail",
"position": [
1920,
220
],
"parameters": {
"sendTo": "={{ $json.recipient }}",
"message": "={{ $json.emailHtml }}",
"options": {
"appendAttribution": false
},
"subject": "=\ud83d\udcf0 Your Weekly Content Digest \u2014 {{ $json.digestDate }}",
"resource": "message",
"emailType": "html",
"operation": "send"
},
"typeVersion": 2.2
}
],
"settings": {
"executionOrder": "v1"
},
"connections": {
"Read RSS Feed": {
"main": [
[
{
"node": "Merge, Filter & Rank Articles",
"type": "main",
"index": 0
}
]
]
},
"Build Email HTML": {
"main": [
[
{
"node": "Send Digest Email",
"type": "main",
"index": 0
}
]
]
},
"Define Feed Sources": {
"main": [
[
{
"node": "Read RSS Feed",
"type": "main",
"index": 0
}
]
]
},
"Weekly Monday 08:00": {
"main": [
[
{
"node": "Define Feed Sources",
"type": "main",
"index": 0
}
]
]
},
"Curate & Summarize Digest": {
"main": [
[
{
"node": "Build Email HTML",
"type": "main",
"index": 0
}
]
]
},
"Merge, Filter & Rank Articles": {
"main": [
[
{
"node": "Curate & Summarize Digest",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow runs every week, reads articles from a configurable list of RSS feeds, deduplicates and ranks recent items, uses OpenAI to curate them into a themed HTML digest, and sends the finished newsletter to your inbox via Gmail. Runs weekly on Monday at 08:00. Generates…
Source: https://n8n.io/workflows/16037/ — 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.
What this workflow does Pulls free security/tech headlines from multiple RSS feeds (e.g., CISA, BleepingComputer, Krebs, SecurityWeek, Ars Technica, TechCrunch, Hacker News). De-duplicates stories, ke
This automation fetches daily AI-related articles from trusted RSS feeds, summarizes them using OpenAI (GPT), and generates a ready-to-post LinkedIn update in your writing style. It then emails the po
Pulls free business and economic headlines from multiple publicly available RSS feeds (e.g., Reuters, Wall Street Journal, Federal Reserve, St. Louis Fed, BNP Paribas, WTO). De-duplicates stories, kee
This n8n automation turns any RSS feed into a spoken podcast episode, using OpenAI for summarization and ElevenLabs for voice generation. The final audio is then sent straight to your Telegram for ins
Personalized Outreach & Follow-Up - Phase 2. Uses googleSheets, openAi, gmail, gmailTrigger. Scheduled trigger; 59 nodes.