AutomationFlowsEmail & Gmail › Send a Daily RSS News Digest Email with Gmail

Send a Daily RSS News Digest Email with Gmail

ByPrecision Tech @precision-tech on n8n.io

Automatically combine any number of RSS feeds into one daily HTML email digest with deduplication and a configurable time window. Runs daily on a cron schedule (default 8:00 AM). Generates a list of RSS feed URLs and digest settings (recipient email, lookback window, and max…

Cron / scheduled trigger★★★★☆ complexity11 nodesRSS Feed ReadGmail
Email & Gmail Trigger: Cron / scheduled Nodes: 11 Complexity: ★★★★☆ Added:

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

This workflow follows the Gmail → RSS Feed Read 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
{
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "name": "Daily RSS Email Digest",
  "tags": [],
  "nodes": [
    {
      "id": "b18be4d3-54fe-4990-a287-48eb87bbe8c2",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -608,
        64
      ],
      "parameters": {
        "width": 480,
        "height": 720,
        "content": "## Daily RSS Email Digest\n\n### How it works\n\nThis workflow runs on a daily schedule, loads the configured RSS feed list, and reads articles from those feeds. It deduplicates and filters the articles, checks whether there is anything new to send, then builds an HTML digest and emails it through Gmail.\n\n### Setup steps\n\n- Configure the schedule trigger with the desired daily run time and timezone.\n- Edit the **Configure Feed Settings** code node with your RSS feed URLs and digest settings.\n- Connect valid Gmail credentials to the **Send Email Digest** node and verify the recipient, subject, and HTML body mappings.\n\n### Customization\n\nAdjust the feed list, filtering window, deduplication logic, email template, and recipient list to match the topics and format you want in the daily digest."
      },
      "typeVersion": 1
    },
    {
      "id": "89e31a13-46f0-461e-a469-d9d348b72a3b",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 320,
        "content": "## Schedule and feed setup\n\nStarts the workflow on a daily schedule and prepares the RSS feed configuration used by the rest of the digest process."
      },
      "typeVersion": 1
    },
    {
      "id": "ddf014ad-0473-4bac-bf4e-4b5c2eb7c2a5",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 688,
        "height": 304,
        "content": "## Read and filter articles\n\nFetches RSS items, removes duplicates, applies article filtering, and checks whether any articles remain for the digest."
      },
      "typeVersion": 1
    },
    {
      "id": "bddf5dc9-108d-462d-b339-0ced2328915e",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1216,
        64
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 320,
        "content": "## Build and send digest\n\nCreates the HTML email content for the filtered articles and sends the completed digest through Gmail."
      },
      "typeVersion": 1
    },
    {
      "id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
      "name": "When 8AM Daily",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        0,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "f1a2b3c4-d5e6-7890-abcd-111111111111",
      "name": "Configure Feed Settings",
      "type": "n8n-nodes-base.code",
      "position": [
        260,
        300
      ],
      "parameters": {
        "jsCode": "// ===== EDIT YOUR SETTINGS HERE =====\nconst YOUR_EMAIL = 'user@example.com';\nconst RSS_FEEDS = [\n  'https://feeds.feedburner.com/TechCrunch',\n  'https://hnrss.org/frontpage',\n];\nconst MAX_ARTICLES = 15;\nconst HOURS_BACK = 24;\n\nreturn RSS_FEEDS.map((feedUrl) => ({\n  json: {\n    feedUrl,\n    recipientEmail: YOUR_EMAIL,\n    maxArticles: MAX_ARTICLES,\n    hoursBack: HOURS_BACK,\n  },\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "d4e5f6a7-b8c9-0123-def0-234567890123",
      "name": "Fetch RSS Feed Articles",
      "type": "n8n-nodes-base.rssFeedRead",
      "onError": "continueRegularOutput",
      "position": [
        520,
        300
      ],
      "parameters": {
        "url": "={{ $json.feedUrl }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "a7b8c9d0-e1f2-3456-0123-567890123456",
      "name": "Process Feed Articles",
      "type": "n8n-nodes-base.code",
      "position": [
        780,
        300
      ],
      "parameters": {
        "jsCode": "const config = $('Configure Feed Settings').first().json;\nconst maxArticles = config.maxArticles ?? 15;\nconst hoursBack = config.hoursBack ?? 24;\nconst cutoff = Date.now() - hoursBack * 60 * 60 * 1000;\n\nconst items = $input.all();\nconst seen = new Set();\n\nconst articles = items\n  .map((item) => item.json)\n  .filter((article) => {\n    const link = article.link || article.guid || article.id;\n    if (!link || seen.has(link)) {\n      return false;\n    }\n\n    seen.add(link);\n\n    const published = new Date(\n      article.pubDate || article.isoDate || article.published || article.updated || 0,\n    ).getTime();\n\n    if (Number.isNaN(published)) {\n      return true;\n    }\n\n    return published >= cutoff;\n  })\n  .sort((a, b) => {\n    const dateA = new Date(a.pubDate || a.isoDate || a.published || 0).getTime();\n    const dateB = new Date(b.pubDate || b.isoDate || b.published || 0).getTime();\n    return dateB - dateA;\n  })\n  .slice(0, maxArticles);\n\nreturn articles.map((article) => ({ json: article }));"
      },
      "typeVersion": 2
    },
    {
      "id": "b8c9d0e1-f2a3-4567-1234-678901234567",
      "name": "Check Articles Availability",
      "type": "n8n-nodes-base.if",
      "position": [
        1020,
        300
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-articles-check",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $input.all().length }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "c9d0e1f2-a3b4-5678-2345-789012345678",
      "name": "Construct HTML Email",
      "type": "n8n-nodes-base.code",
      "position": [
        1260,
        220
      ],
      "parameters": {
        "jsCode": "const articles = $input.all().map((item) => item.json);\nconst config = $('Configure Feed Settings').first().json;\n\nconst today = new Date().toLocaleDateString('en-US', {\n  weekday: 'long',\n  year: 'numeric',\n  month: 'long',\n  day: 'numeric',\n});\n\nconst escapeHtml = (value) =>\n  String(value ?? '')\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;');\n\nconst rows = articles\n  .map((article) => {\n    const title = escapeHtml(article.title || 'Untitled');\n    const link = escapeHtml(article.link || article.guid || '#');\n    const author = escapeHtml(\n      article.creator || article.author || article['dc:creator'] || 'Unknown source',\n    );\n    const published = article.pubDate || article.isoDate || article.published;\n    const dateLabel = published\n      ? new Date(published).toLocaleString('en-US', {\n          month: 'short',\n          day: 'numeric',\n          hour: 'numeric',\n          minute: '2-digit',\n        })\n      : '';\n    const snippet = escapeHtml(\n      (article.contentSnippet || article.summary || article.description || '')\n        .replace(/<[^>]*>/g, ' ')\n        .trim()\n        .slice(0, 180),\n    );\n\n    return `\n      <tr>\n        <td style=\"padding: 16px 0; border-bottom: 1px solid #e5e7eb;\">\n          <a href=\"${link}\" style=\"color: #111827; font-size: 16px; font-weight: 600; text-decoration: none;\">${title}</a>\n          <div style=\"color: #6b7280; font-size: 12px; margin-top: 6px;\">${author}${dateLabel ? ` \u00b7 ${dateLabel}` : ''}</div>\n          ${snippet ? `<div style=\"color: #374151; font-size: 14px; margin-top: 8px; line-height: 1.5;\">${snippet}${snippet.length >= 180 ? '\u2026' : ''}</div>` : ''}\n        </td>\n      </tr>\n    `;\n  })\n  .join('');\n\nconst html = `\n  <div style=\"font-family: Arial, sans-serif; max-width: 640px; margin: 0 auto; color: #111827;\">\n    <h1 style=\"font-size: 24px; margin-bottom: 8px;\">Your Daily Digest</h1>\n    <p style=\"color: #6b7280; margin-top: 0;\">${today} \u00b7 ${articles.length} article${articles.length === 1 ? '' : 's'}</p>\n    <table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"border-collapse: collapse;\">\n      ${rows}\n    </table>\n  </div>\n`;\n\nreturn [\n  {\n    json: {\n      subject: `Daily Digest \u2014 ${today}`,\n      html,\n      articleCount: articles.length,\n      recipientEmail: config.recipientEmail,\n    },\n  },\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "d0e1f2a3-b4c5-6789-3456-890123456789",
      "name": "Send Email Digest",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1500,
        220
      ],
      "parameters": {
        "sendTo": "={{ $json.recipientEmail }}",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "={{ $json.subject }}",
        "resource": "message",
        "emailType": "html",
        "operation": "send"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "rss-digest-v2",
  "connections": {
    "When 8AM Daily": {
      "main": [
        [
          {
            "node": "Configure Feed Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Construct HTML Email": {
      "main": [
        [
          {
            "node": "Send Email Digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Feed Articles": {
      "main": [
        [
          {
            "node": "Check Articles Availability",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure Feed Settings": {
      "main": [
        [
          {
            "node": "Fetch RSS Feed Articles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch RSS Feed Articles": {
      "main": [
        [
          {
            "node": "Process Feed Articles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Articles Availability": {
      "main": [
        [
          {
            "node": "Construct HTML Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

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.

Pro

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

About this workflow

Automatically combine any number of RSS feeds into one daily HTML email digest with deduplication and a configurable time window. Runs daily on a cron schedule (default 8:00 AM). Generates a list of RSS feed URLs and digest settings (recipient email, lookback window, and max…

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

More Email & Gmail workflows → · Browse all categories →

Related workflows

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

Email & Gmail

The workflow is triggered automatically every day at 12:00 PM using a Cron node.

RSS Feed Read, Google Sheets, Gmail +1
Email & Gmail

Trello Limit. Uses scheduleTrigger, rssFeedRead, sort, limit. Scheduled trigger; 15 nodes.

RSS Feed Read, Trello, Gmail
Email & Gmail

This workflow is designed for professionals and teams who need to monitor multiple RSS feeds, filter the latest content, and distribute actionable updates as a Trello comment. Ideal for content manage

RSS Feed Read, Trello, Gmail
Email & Gmail

Freelancers and agencies who track new Upwork leads via Vollna RSS and want clean logging to Google Sheets with instant Slack alerts.

Google Sheets, RSS Feed Read, Slack +1
Email & Gmail

This workflow turns news monitoring into an early-warning demand engine. It continuously ingests Google Alert RSS feeds, extracts the full text of every article, and runs real-time purchase-intent mod

HTTP Request, Gmail, RSS Feed Read