AutomationFlowsAI & RAG › Send a Daily Ai-ranked News Brief with Anthropic Claude and Gmail

Send a Daily Ai-ranked News Brief with Anthropic Claude and Gmail

ByNocode Expert @iamrup on n8n.io

This workflow runs every morning to fetch recent articles from 50+ curated RSS/Atom feeds, uses Anthropic Claude to rank and rewrite the most relevant items based on your interests, then sends a category-grouped HTML news brief to you via Gmail. Runs daily on a schedule trigger.…

Cron / scheduled trigger★★★★☆ complexity13 nodesHTTP RequestGmail
AI & RAG Trigger: Cron / scheduled Nodes: 13 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #17248 — we link there as the canonical source.

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 →

Download .json
{
  "id": "11dailynewsbrief",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Get a daily AI news brief from 50+ curated feeds, tagged by category and ranked by your interests",
  "tags": [],
  "nodes": [
    {
      "id": "d1fe8f7f-cccc-4133-988b-809f989f691c",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        272,
        608
      ],
      "parameters": {
        "width": 792,
        "height": 680,
        "content": "## Daily category-tagged news brief \u2192 email\n\nPick the topics you care about and get one ranked brief every morning, pulled from a built-in catalog of 50+ quality feeds and grouped by category.\n\n### How it works\nSet your categories (ai, tech, startups, jobs, dev, security, product, science, business, marketing, world) or use all. A Code node expands the baked-in catalog, a native HTTP Request node fetches every feed in those categories, and a Code node parses the results and tags each article with its category, keeps only recent items, and de-duplicates by link. A single AI call drops noise, picks the most relevant stories against your interests while keeping your category mix, rewrites tight headlines, and scores importance. The picks are grouped by category and emailed to you.\n\n### Setup\n- Edit the config node: categories, your interests, look-back window, and the send-to address.\n- Add credentials in the canvas: Anthropic on the ranking HTTP node and Gmail on the send node.\n- The Schedule Trigger runs daily; change the hour to taste.\n\n### Customization\nEdit the catalog in the expand node to add or remove feeds and categories, widen the window for a weekly edition, or swap the email for Slack or Telegram.\n\nBuilt by **nocode.expert** \u2014 done-for-you automation and AI workflows. https://nocode.expert"
      },
      "typeVersion": 1
    },
    {
      "id": "3585e065-d525-4dc0-9318-184a1230ada3",
      "name": "Section: Fetch & tag",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1424,
        880
      ],
      "parameters": {
        "color": 7,
        "width": 600,
        "height": 420,
        "content": "## 1. Fetch & tag\nPick categories in the config. The HTTP node fetches every feed in those categories from the built-in 50+ catalog; the parse node tags each article by category, keeping recent, de-duplicated items."
      },
      "typeVersion": 1
    },
    {
      "id": "b50f404a-fa72-4606-8507-61c843c4e046",
      "name": "Section: Rank with AI",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2096,
        880
      ],
      "parameters": {
        "color": 7,
        "width": 748,
        "height": 420,
        "content": "## 2. Rank vs your interests\nOne AI call drops noise, picks the most relevant stories across categories, and scores importance while keeping the tags."
      },
      "typeVersion": 1
    },
    {
      "id": "6ff819c3-090a-4e16-a354-6cf26170e5b9",
      "name": "Section: Email brief",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2912,
        896
      ],
      "parameters": {
        "color": 7,
        "width": 536,
        "height": 404,
        "content": "## 3. Email, grouped by tag\nPicks are grouped by category and emailed. Swap for Slack or Telegram if you prefer."
      },
      "typeVersion": 1
    },
    {
      "id": "92ac8b8c-ad06-4712-8dd3-cd0161c251e5",
      "name": "Every morning",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        1232,
        1120
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 7
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "df03c912-cde9-413c-9b57-17cf4a946385",
      "name": "Set config: categories & interests",
      "type": "n8n-nodes-base.set",
      "position": [
        1488,
        1120
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "categories",
              "type": "string",
              "value": "ai, tech, startups, jobs"
            },
            {
              "id": "a2",
              "name": "interests",
              "type": "string",
              "value": "practical AI/LLM tooling, funding and startup moves, remote product/eng roles, anything genuinely novel"
            },
            {
              "id": "a3",
              "name": "hoursBack",
              "type": "string",
              "value": "24"
            },
            {
              "id": "a4",
              "name": "perFeed",
              "type": "string",
              "value": "10"
            },
            {
              "id": "a5",
              "name": "maxItems",
              "type": "string",
              "value": "40"
            },
            {
              "id": "a6",
              "name": "topN",
              "type": "string",
              "value": "15"
            },
            {
              "id": "a7",
              "name": "digestTo",
              "type": "string",
              "value": "you@example.com"
            },
            {
              "id": "a8",
              "name": "model",
              "type": "string",
              "value": "claude-haiku-4-5"
            },
            {
              "id": "a9",
              "name": "anthropicKey",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "177800c4-93a0-4204-b62e-f72efb3af6e8",
      "name": "Expand selected feeds",
      "type": "n8n-nodes-base.code",
      "position": [
        1696,
        1120
      ],
      "parameters": {
        "jsCode": "const cfg = $('Set config: categories & interests').first().json;\nconst CATALOG = {\"ai\":[\"https://huggingface.co/blog/feed.xml\",\"https://the-decoder.com/feed/\",\"https://venturebeat.com/category/ai/feed/\",\"https://simonwillison.net/atom/everything/\",\"https://openai.com/blog/rss.xml\",\"https://deepmind.google/blog/rss.xml\",\"https://bair.berkeley.edu/blog/feed.xml\",\"https://jack-clark.net/feed/\",\"https://hnrss.org/newest?q=AI+OR+LLM&points=100\"],\"tech\":[\"https://www.theverge.com/rss/index.xml\",\"https://techcrunch.com/feed/\",\"https://arstechnica.com/feed/\",\"https://www.wired.com/feed/rss\",\"https://www.engadget.com/rss.xml\",\"https://hnrss.org/frontpage\",\"https://www.technologyreview.com/feed/\",\"https://restofworld.org/feed/latest/\"],\"startups\":[\"https://techcrunch.com/category/startups/feed/\",\"https://news.crunchbase.com/feed/\",\"https://www.saastr.com/feed/\",\"https://tomtunguz.com/index.xml\",\"https://andrewchen.com/feed/\"],\"jobs\":[\"https://weworkremotely.com/remote-jobs.rss\",\"https://hnrss.org/jobs\",\"https://hnrss.org/whoishiring/jobs\",\"https://remotive.com/remote-jobs/feed\",\"https://jobicy.com/?feed=job_feed\"],\"dev\":[\"https://dev.to/feed\",\"https://css-tricks.com/feed/\",\"https://martinfowler.com/feed.atom\",\"https://stackoverflow.blog/feed/\",\"https://github.blog/feed/\",\"https://www.smashingmagazine.com/feed/\",\"https://overreacted.io/rss.xml\"],\"product_design\":[\"https://www.lennysnewsletter.com/feed\",\"https://uxdesign.cc/feed\",\"https://www.nngroup.com/feed/rss/\",\"https://uxplanet.org/feed\"],\"security\":[\"https://krebsonsecurity.com/feed/\",\"https://www.bleepingcomputer.com/feed/\",\"https://feeds.feedburner.com/TheHackersNews\",\"https://www.schneier.com/feed/atom/\",\"https://www.darkreading.com/rss.xml\"],\"business_finance\":[\"https://feeds.a.dj.com/rss/RSSMarketsMain.xml\",\"https://www.cnbc.com/id/1+1234567890/device/rss/rss.html\",\"https://feeds.bloomberg.com/technology/news.rss\",\"https://fortune.com/feed/\"],\"science\":[\"https://www.sciencedaily.com/rss/all.xml\",\"https://arstechnica.com/science/feed/\",\"https://www.quantamagazine.org/feed/\",\"https://phys.org/rss-feed/\"],\"marketing\":[\"https://searchengineland.com/feed\",\"https://moz.com/blog/feed\",\"https://www.socialmediaexaminer.com/feed/\",\"https://www.marketingdive.com/feeds/news/\"],\"world\":[\"https://feeds.bbci.co.uk/news/rss.xml\",\"https://www.aljazeera.com/xml/rss/all.xml\",\"https://feeds.npr.org/1001/rss.xml\",\"https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml\"]};\nconst wanted = String(cfg.categories || '').toLowerCase().trim();\nconst cats = (!wanted || wanted === 'all')\n  ? Object.keys(CATALOG)\n  : wanted.split(',').map((s) => s.trim()).filter((c) => CATALOG[c]);\nconst feeds = [];\nfor (const c of cats) for (const url of (CATALOG[c] || [])) feeds.push({ feedUrl: url, category: c });\nreturn feeds.map((f) => ({ json: f }));"
      },
      "typeVersion": 2
    },
    {
      "id": "b94622a5-0ab7-4511-a700-cd84f03fd1aa",
      "name": "Fetch each feed",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        1856,
        1120
      ],
      "parameters": {
        "url": "={{ $json.feedUrl }}",
        "options": {
          "timeout": 15000,
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36"
            },
            {
              "name": "Accept",
              "value": "application/rss+xml, application/atom+xml, application/xml, text/xml, */*"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "b7e61071-d5f9-4a40-bdc5-ebf7fd481c2c",
      "name": "Parse & tag articles",
      "type": "n8n-nodes-base.code",
      "position": [
        2176,
        1120
      ],
      "parameters": {
        "jsCode": "const cfg = $('Set config: categories & interests').first().json;\nconst feedsIn = $('Expand selected feeds').all();\nconst perFeed = Number(cfg.perFeed) || 10;\nconst hours = Number(cfg.hoursBack) || 24;\nconst cutoff = Date.now() - hours * 3600 * 1000;\nconst maxItems = Number(cfg.maxItems) || 40;\n\nconst strip = (h) => String(h || '').replace(/<!\\[CDATA\\[|\\]\\]>/g, '').replace(/<[^>]+>/g, ' ').replace(/&[a-z#0-9]+;/g, ' ').replace(/\\s+/g, ' ').trim();\nconst tag = (b, t) => { const m = new RegExp('<' + t + '[^>]*>([\\\\s\\\\S]*?)</' + t + '>', 'i').exec(b); return m ? m[1] : ''; };\nfunction parse(xml) {\n  const out = []; const re = /<(item|entry)[\\s\\S]*?<\\/\\1>/gi; let m;\n  while ((m = re.exec(xml))) {\n    const b = m[0];\n    let link = strip(tag(b, 'link'));\n    if (!link) { const h = /<link[^>]*href=\"([^\"]+)\"/i.exec(b); if (h) link = h[1]; }\n    out.push({\n      title: strip(tag(b, 'title')), link,\n      snippet: strip(tag(b, 'description') || tag(b, 'summary') || tag(b, 'content')).slice(0, 400),\n      date: strip(tag(b, 'pubDate') || tag(b, 'updated') || tag(b, 'published') || tag(b, 'dc:date')),\n    });\n  }\n  return out;\n}\n// map each HTTP response back to the feed (and its category) that produced it\nconst catOf = (it, idx) => {\n  let pi = it.pairedItem;\n  let p = (typeof pi === 'number') ? pi : (Array.isArray(pi) ? (pi[0] || {}).item : (pi || {}).item);\n  if (p === undefined || p === null) p = idx;\n  return (feedsIn[p] && feedsIn[p].json.category) || 'general';\n};\n\nconst seen = new Set(); const items = [];\nconst responses = $input.all();\nfor (let idx = 0; idx < responses.length; idx++) {\n  const j = responses[idx].json || {};\n  const xml = typeof j.data === 'string' ? j.data : (typeof j.body === 'string' ? j.body : (typeof responses[idx].json === 'string' ? responses[idx].json : ''));\n  if (!xml) continue;\n  const category = catOf(responses[idx], idx);\n  for (const a of parse(xml).slice(0, perFeed)) {\n    if (!a.title || !a.link) continue;\n    const ts = Date.parse(a.date) || Date.now();\n    if (ts < cutoff) continue;\n    const key = a.link.toLowerCase();\n    if (seen.has(key)) continue;\n    seen.add(key);\n    items.push({ title: a.title, link: a.link, snippet: a.snippet, category, ts });\n  }\n}\nitems.sort((x, y) => y.ts - x.ts);\n// balance the pool round-robin across categories so a high-volume tag (e.g. tech) does not crowd out others\nconst buckets = {};\nfor (const a of items) (buckets[a.category] = buckets[a.category] || []).push(a); // already recency-sorted\nconst catKeys = Object.keys(buckets);\nconst balanced = []; let added = true;\nwhile (balanced.length < maxItems && added) {\n  added = false;\n  for (const c of catKeys) { if (buckets[c].length) { balanced.push(buckets[c].shift()); added = true; if (balanced.length >= maxItems) break; } }\n}\nconst capped = balanced.map((a, i) => ({ i, title: a.title, link: a.link, snippet: a.snippet, category: a.category }));\nreturn [{ json: { items: capped, count: capped.length } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "54bbbf71-77be-459d-ae58-bdf5d5a46ced",
      "name": "Build AI ranking request",
      "type": "n8n-nodes-base.code",
      "position": [
        2368,
        1120
      ],
      "parameters": {
        "jsCode": "const cfg = $('Set config: categories & interests').first().json;\nconst items = $json.items || [];\nconst schema = { type: 'object', additionalProperties: false, required: ['overview', 'picks'], properties: {\n  overview: { type: 'string' },\n  picks: { type: 'array', items: { type: 'object', additionalProperties: false,\n    required: ['i', 'headline', 'why', 'category', 'importance'], properties: {\n      i: { type: 'integer' }, headline: { type: 'string' }, why: { type: 'string' },\n      category: { type: 'string' }, importance: { type: 'integer' } } } } } };\nconst system = 'You are a sharp editor building a personalised brief for ONE reader. Each article already has a category tag. Drop near-duplicates and low-signal noise, then pick the most relevant to the reader interests across ALL categories. For each pick: headline = a tight rewrite, why = one sentence on why it matters to this reader, category = keep the article tag, importance = 0-100. overview = a 2-sentence read of the day. Keep the mix of categories the reader selected; do not over-index on one. Be terse, no hype.';\nconst user = 'READER INTERESTS: ' + cfg.interests + '\\n\\nSelected categories present: ' + (items.length ? [...new Set(items.map((a) => a.category))].join(', ') : '') +\n  '. Pick the top ' + (cfg.topN || 15) + ' overall.\\n\\nARTICLES (reference each by index i):\\n' +\n  items.map((a) => '[' + a.i + '] (' + a.category + ') ' + a.title + '\\n' + a.snippet).join('\\n\\n');\nreturn [{ json: { body: {\n  model: cfg.model, max_tokens: 2500, system,\n  messages: [{ role: 'user', content: user }],\n  output_config: { format: { type: 'json_schema', schema } },\n} } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "e7c07a4e-6de4-4cb5-95f2-22f56244a4ea",
      "name": "Rank articles with Claude",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2576,
        1120
      ],
      "parameters": {
        "url": "https://api.anthropic.com/v1/messages",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify($json.body) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "x-api-key",
              "value": "={{ $('Set config: categories & interests').first().json.anthropicKey }}"
            },
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            },
            {
              "name": "content-type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "09393dc4-edbb-497b-a802-42c9f249fbb8",
      "name": "Format the brief by category",
      "type": "n8n-nodes-base.code",
      "position": [
        3008,
        1120
      ],
      "parameters": {
        "jsCode": "const cfg = $('Set config: categories & interests').first().json;\nconst items = $('Parse & tag articles').first().json.items || [];\nconst labels = {\"ai\":\"AI\",\"tech\":\"Tech\",\"startups\":\"Startups\",\"jobs\":\"Jobs\",\"dev\":\"Dev\",\"product_design\":\"Product & Design\",\"security\":\"Security\",\"business_finance\":\"Business & Finance\",\"science\":\"Science\",\"marketing\":\"Marketing\",\"world\":\"World\"};\nconst top = Number(cfg.topN) || 15;\nconst blocks = ($json.content || []);\nconst text = (blocks.find((b) => b.type === 'text') || {}).text || '{\"overview\":\"\",\"picks\":[]}';\nlet parsed = { overview: '', picks: [] };\ntry { parsed = JSON.parse(text); } catch (e) {}\nconst esc = (s) => String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\nconst picks = (parsed.picks || []).map((p) => ({ ...p, ...(items[p.i] || {}), category: p.category || (items[p.i] || {}).category }))\n  .filter((p) => p.link).sort((a, b) => b.importance - a.importance).slice(0, top);\nconst groups = {};\nfor (const p of picks) (groups[p.category] = groups[p.category] || []).push(p);\nlet body = '';\nfor (const cat of Object.keys(groups)) {\n  body += '<h3 style=\"margin:16px 0 6px;text-transform:uppercase;letter-spacing:.04em;color:#111\">' + esc(labels[cat] || cat) + '</h3><ul style=\"padding-left:18px;margin:0\">';\n  for (const p of groups[cat]) body += '<li style=\"margin:8px 0\"><a href=\"' + esc(p.link) + '\" style=\"font-weight:700\">' + esc(p.headline) + '</a> <span style=\"color:#999\">\u00b7 ' + p.importance + '</span><br><span style=\"color:#333\">' + esc(p.why) + '</span></li>';\n  body += '</ul>';\n}\nconst html = '<h2 style=\"font-family:system-ui,Arial\">Your morning brief</h2>' +\n  (parsed.overview ? '<p style=\"color:#444;font-family:system-ui,Arial\">' + esc(parsed.overview) + '</p>' : '') +\n  '<div style=\"font-family:system-ui,Arial\">' + body + '</div>' +\n  '<p style=\"color:#999;font-size:12px\">' + picks.length + ' picks from ' + items.length + ' recent articles across ' + Object.keys(groups).length + ' categories.</p>';\nconst subject = picks.length ? 'Brief: ' + picks.length + ' picks \u00b7 ' + Object.keys(groups).map((c) => labels[c] || c).join(', ') : 'Brief: quiet news day';\nreturn [{ json: { subject, html, count: picks.length } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "57fbe61e-bf19-4b70-809e-d5af8f94899d",
      "name": "Email the brief",
      "type": "n8n-nodes-base.gmail",
      "position": [
        3296,
        1120
      ],
      "parameters": {
        "sendTo": "={{ $('Set config: categories & interests').first().json.digestTo }}",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "={{ $json.subject }}"
      },
      "typeVersion": 2.1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "8a4ee16a-f258-4b33-bfc5-8ad6532cf74a",
  "nodeGroups": [],
  "connections": {
    "Every morning": {
      "main": [
        [
          {
            "node": "Set config: categories & interests",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch each feed": {
      "main": [
        [
          {
            "node": "Parse & tag articles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse & tag articles": {
      "main": [
        [
          {
            "node": "Build AI ranking request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Expand selected feeds": {
      "main": [
        [
          {
            "node": "Fetch each feed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build AI ranking request": {
      "main": [
        [
          {
            "node": "Rank articles with Claude",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rank articles with Claude": {
      "main": [
        [
          {
            "node": "Format the brief by category",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format the brief by category": {
      "main": [
        [
          {
            "node": "Email the brief",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set config: categories & interests": {
      "main": [
        [
          {
            "node": "Expand selected feeds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

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

About this workflow

This workflow runs every morning to fetch recent articles from 50+ curated RSS/Atom feeds, uses Anthropic Claude to rank and rewrite the most relevant items based on your interests, then sends a category-grouped HTML news brief to you via Gmail. Runs daily on a schedule trigger.…

Source: https://n8n.io/workflows/17248/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

This workflow runs twice daily to fetch recent Instagram post comments via the Instagram Graph API, uses OpenAI to classify sentiment, saves new positive comments to Google Sheets, and emails an HTML

HTTP Request, Google Sheets, Gmail
AI & RAG

This workflow runs every 6 hours to read new NPS responses from Google Sheets, uses OpenAI to draft a personalized reply, sends it via Gmail, routes respondents by NPS segment, logs promoter quotes ba

Google Sheets, HTTP Request, Gmail
AI & RAG

This workflow runs daily to pull job applicants from Google Sheets, score each candidate against role requirements using the Google Gemini API, log results back to Google Sheets, and notify a Slack ch

Google Sheets, HTTP Request, Slack +1
AI & RAG

Founder's Discovery Engine. Uses googleSheets, googleDrive, httpRequest, gmail. Scheduled trigger; 18 nodes.

Google Sheets, Google Drive, HTTP Request +1
AI & RAG

This workflow runs every Monday, pulls stories from your chosen RSS feeds, uses OpenAI Chat Completions to draft a grouped HTML newsletter, and saves it as a draft broadcast in Kit (ConvertKit) or ema

RSS Feed Read, HTTP Request, Gmail