AutomationFlowsAI & RAG › Draft Weekly Newsletter Digests From RSS Feeds with Openai and Kit

Draft Weekly Newsletter Digests From RSS Feeds with Openai and Kit

ByZiad Karim @ziadkarim on n8n.io

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 emails the draft via Gmail, with an optional Slack notification. Runs every Monday…

Cron / scheduled trigger★★★★☆ complexity16 nodesRSS Feed ReadHTTP RequestGmail
AI & RAG Trigger: Cron / scheduled Nodes: 16 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #17539 — 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": "NwsRssAiDraft000001",
  "name": "Auto-draft your weekly newsletter from RSS feeds with AI",
  "nodes": [
    {
      "id": "ae6b0fcf-3eac-4950-aba4-5bf6301e8bdc",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1560,
        -80
      ],
      "parameters": {
        "width": 540,
        "height": 1180,
        "content": "## Auto-draft your weekly newsletter from RSS feeds with AI\n\n### How it works\n\nThis workflow runs weekly, pulls recent articles from configured RSS feeds, and filters them to the last seven days. It uses OpenAI to draft a newsletter digest, formats the result as email-safe HTML, then creates a **draft** broadcast in Kit (`send_at: null` \u2014 it never emails your subscribers by itself; you review, edit and send). It can also notify Slack and includes an alternate branch for emailing the draft to yourself through Gmail.\n\n### Setup steps\n\n- Edit the \"Define Source Feeds\" code node with the RSS feed URLs you want to include.\n- Configure the schedule trigger if Monday at 05:00 is not the desired run time.\n- On \"Post to OpenAI API\", create a **Header Auth** credential: name `Authorization`, value `Bearer sk-\u2026`. Any OpenAI-compatible endpoint works, including self-hosted Ollama \u2014 just change the URL.\n- On \"Post to Kit API\", create a second **Header Auth** credential: name `X-Kit-Api-Key`, value = your API key from Kit \u2192 Settings \u2192 Developer.\n- Edit the \"Create AI Prompt\" node to set the newsletter name, tone, model and any editorial instructions.\n- Optional: add a Slack incoming webhook URL to the notification node.\n- Optional: connect Gmail and set the recipient if using the self-email branch. **Keep the delivery branch you use and delete the other** (or keep both).\n\n### Customization\n\nAdjust the feed list, seven-day filter window, 25-story cap, newsletter voice, and HTML wrapper. Prefer another ESP? Swap one HTTP call: beehiiv `POST /v2/publications/{id}/posts`, Mailchimp `POST /campaigns` + `/content` \u2014 or use the Gmail branch and paste the draft anywhere (Substack, Ghost, Buttondown)."
      },
      "typeVersion": 1
    },
    {
      "id": "fc6987a1-5ff7-404b-a01a-3fe37a137af1",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -960,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 680,
        "height": 360,
        "content": "## 1. Schedule and read feeds\n\nStarts the workflow every Monday morning, defines the RSS feed list to monitor, and reads items from those feeds."
      },
      "typeVersion": 1
    },
    {
      "id": "357852c1-e674-4ccb-b342-0c56deff81f6",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -240,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 360,
        "content": "## 2. Filter and prepare prompt\n\nFilters feed entries to recent stories, tidies the fields, limits the story count, and builds the AI prompt with the desired newsletter voice."
      },
      "typeVersion": 1
    },
    {
      "id": "495556f9-0d44-4681-a970-91e37c844f4f",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        260,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 460,
        "height": 360,
        "content": "## 3. Generate newsletter HTML\n\nSends the prepared prompt to OpenAI to draft the digest, then wraps the AI copy in a minimal email-safe HTML layout."
      },
      "typeVersion": 1
    },
    {
      "id": "dc569f4f-8f94-4f9f-ba40-9c9231516afc",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        740,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 520,
        "height": 360,
        "content": "## 4. Create Kit broadcast\n\nCreates a draft broadcast in Kit from the generated HTML and optionally posts a Slack notification that the draft is ready. `send_at: null` is what keeps it a draft \u2014 please keep a human review before sending."
      },
      "typeVersion": 1
    },
    {
      "id": "2b7d012d-d3b0-4fae-9fc1-cdcdad15f120",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        740,
        440
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 340,
        "content": "## 5. Email draft alternative\n\nAn ESP-free branch: the generated draft lands in your own inbox via Gmail instead of (or in addition to) the Kit broadcast. Keep the delivery branch you use and delete the other."
      },
      "typeVersion": 1
    },
    {
      "id": "c4d5e6f0-0002-4b2a-9d11-bb0000000002",
      "name": "When Monday at 5am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -900,
        180
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 5
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "c4d5e6f0-0003-4b2a-9d11-bb0000000003",
      "name": "Define Source Feeds",
      "type": "n8n-nodes-base.code",
      "position": [
        -680,
        180
      ],
      "parameters": {
        "jsCode": "// ============ EDIT ME: your source feeds ============\n// One line per RSS/Atom feed you want in the digest.\n// These two are live examples so you can run the workflow as-is \u2014\n// replace them with the feeds you actually read.\nconst FEEDS = [\n  'https://hnrss.org/frontpage',\n  'https://www.theverge.com/rss/index.xml',\n];\n// =====================================================\nreturn FEEDS.map((url) => ({ json: { url } }));"
      },
      "typeVersion": 2
    },
    {
      "id": "c4d5e6f0-0004-4b2a-9d11-bb0000000004",
      "name": "Fetch RSS Feeds",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        -460,
        180
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "options": {}
      },
      "typeVersion": 1.1
    },
    {
      "id": "c4d5e6f0-0005-4b2a-9d11-bb0000000005",
      "name": "Filter Recent Stories",
      "type": "n8n-nodes-base.code",
      "position": [
        -180,
        180
      ],
      "parameters": {
        "jsCode": "// Keep only the last 7 days, tidy the fields, cap at 25 stories.\nconst cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;\nconst seen = new Set();\nconst stories = [];\n\nfor (const item of $input.all()) {\n  const j = item.json;\n  const dateStr = j.isoDate ?? j.pubDate ?? null;\n  const ts = dateStr ? Date.parse(dateStr) : NaN;\n  if (Number.isNaN(ts) || ts < cutoff) continue;\n  const link = j.link ?? '';\n  if (!link || seen.has(link)) continue;\n  seen.add(link);\n  const snippet = (j.contentSnippet ?? j.content ?? '')\n    .replace(/<[^>]+>/g, ' ')\n    .replace(/\\s+/g, ' ')\n    .trim()\n    .slice(0, 300);\n  let source = '';\n  try { source = new URL(link).hostname.replace(/^www\\./, ''); } catch {}\n  stories.push({ ts, title: j.title ?? '(untitled)', link, snippet, source });\n}\n\nstories.sort((a, b) => b.ts - a.ts);\nif (stories.length === 0) {\n  throw new Error('No stories in the last 7 days \u2014 check your feed URLs.');\n}\nreturn stories.slice(0, 25).map((s) => ({ json: s }));"
      },
      "typeVersion": 2
    },
    {
      "id": "c4d5e6f0-0006-4b2a-9d11-bb0000000006",
      "name": "Create AI Prompt",
      "type": "n8n-nodes-base.code",
      "position": [
        40,
        180
      ],
      "parameters": {
        "jsCode": "// ============ EDIT ME: newsletter voice ============\nconst NEWSLETTER_NAME = 'My Weekly Digest';\nconst TONE = 'friendly, concise, no hype';\nconst MODEL = 'gpt-4o-mini'; // any chat model your API key can use\n// ====================================================\n\nconst stories = $input.all().map((i) => i.json);\nconst list = stories\n  .map((s, n) => `${n + 1}. [${s.source}] ${s.title}\\n${s.snippet}\\nLink: ${s.link}`)\n  .join('\\n\\n');\n\nconst system = `You draft the weekly email digest for \"${NEWSLETTER_NAME}\". Tone: ${TONE}. You write tight, scannable email copy. You never invent facts beyond the provided stories, and every story keeps its link.`;\n\nconst user = `Draft this week's digest from the stories below.\\n\\nRules:\\n- Group related stories into 3\u20135 themed sections with short <h2> headings.\\n- Each story: 1\u20132 sentences in your own words + an <a href> \"Read more\" link.\\n- Open with a 2-sentence intro of the week; close with one sign-off line.\\n- Output CLEAN HTML ONLY (h2, p, a, ul/li). No <html>/<head>/<body> wrapper, no markdown, no code fences.\\n\\nSTORIES:\\n\\n${list}`;\n\nreturn [{\n  json: {\n    body: {\n      model: MODEL,\n      messages: [\n        { role: 'system', content: system },\n        { role: 'user', content: user },\n      ],\n      temperature: 0.4,\n    },\n  },\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c4d5e6f0-0007-4b2a-9d11-bb0000000007",
      "name": "Post to OpenAI API",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        320,
        180
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {
          "timeout": 120000
        },
        "jsonBody": "={{ JSON.stringify($json.body) }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "c4d5e6f0-0008-4b2a-9d11-bb0000000008",
      "name": "Build Email HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        540,
        180
      ],
      "parameters": {
        "jsCode": "// Wrap the AI copy in a minimal, email-safe shell.\nlet html = $input.first().json.choices?.[0]?.message?.content ?? '';\nhtml = html.replace(/^```(html)?/m, '').replace(/```\\s*$/m, '').trim();\nif (!html) throw new Error('AI returned no content \u2014 check the previous node.');\n\nconst subject = `Your weekly digest \u2014 ${$now.toFormat('d LLLL')}`;\nconst content = `<div style=\"font-family:Georgia,serif;font-size:16px;line-height:1.6;color:#222;\">${html}</div>`;\n\nreturn [{\n  json: {\n    subject,\n    preview_text: 'The week, in five minutes.',\n    content,\n  },\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c4d5e6f0-0009-4b2a-9d11-bb0000000009",
      "name": "Post to Kit API",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        820,
        180
      ],
      "parameters": {
        "url": "https://api.kit.com/v4/broadcasts",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ subject: $json.subject, preview_text: $json.preview_text, content: $json.content, description: 'Auto-drafted by n8n \u2014 review before sending', public: false, send_at: null }) }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "c4d5e6f0-0010-4b2a-9d11-bb0000000010",
      "name": "Notify Slack",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1040,
        180
      ],
      "parameters": {
        "url": "https://hooks.slack.com/services/PASTE/YOUR/SLACK_WEBHOOK",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "text",
              "value": "=\ud83d\udcf0 This week's newsletter draft is ready in Kit \u2192 Broadcasts. Review, edit, send: https://app.kit.com/campaigns"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "c4d5e6f0-0012-4b2a-9d11-bb0000000012",
      "name": "Send Draft via Gmail",
      "type": "n8n-nodes-base.gmail",
      "position": [
        820,
        620
      ],
      "parameters": {
        "sendTo": "you@example.com",
        "message": "={{ $json.content }}",
        "options": {},
        "subject": "={{ '[DRAFT] ' + $json.subject }}"
      },
      "typeVersion": 2.1
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Fetch RSS Feeds": {
      "main": [
        [
          {
            "node": "Filter Recent Stories",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post to Kit API": {
      "main": [
        [
          {
            "node": "Notify Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Email HTML": {
      "main": [
        [
          {
            "node": "Post to Kit API",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Draft via Gmail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create AI Prompt": {
      "main": [
        [
          {
            "node": "Post to OpenAI API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post to OpenAI API": {
      "main": [
        [
          {
            "node": "Build Email HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Monday at 5am": {
      "main": [
        [
          {
            "node": "Define Source Feeds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Define Source Feeds": {
      "main": [
        [
          {
            "node": "Fetch RSS Feeds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Recent Stories": {
      "main": [
        [
          {
            "node": "Create AI Prompt",
            "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 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 emails the draft via Gmail, with an optional Slack notification. Runs every Monday…

Source: https://n8n.io/workflows/17539/ — 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

AI Daily Tech Briefing (Schedule → RSS → Anthropic Claude → Gmail). Uses rssFeedRead, httpRequest, gmail. Scheduled trigger; 5 nodes.

RSS Feed Read, HTTP Request, Gmail
AI & RAG

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

RSS Feed Read, OpenAI, Airtable +2
AI & RAG

Smart Morning Guard: Weather & Train AI Alert. Uses httpRequest, rssFeedRead, googleGemini, gmail. Scheduled trigger; 19 nodes.

HTTP Request, RSS Feed Read, Google Gemini +1
AI & RAG

AI News and weekly digest. Uses rssFeedRead, httpRequest, openAi, gmail. Scheduled trigger; 15 nodes.

RSS Feed Read, HTTP Request, OpenAI +1
AI & RAG

Automatically fetch, rank, and summarize the top financial stories from curated RSS feeds each day, then deliver a concise AI-written digest to Telegram and log the run to Google Sheets. Runs on a dai

Google Sheets, HTTP Request, RSS Feed Read