{
  "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
          }
        ]
      ]
    }
  }
}