{
  "id": "dXrHZjJdzpNh79lJ",
  "name": "Intelligent AI Digest for Security, Privacy, and Compliance Feeds",
  "tags": [
    {
      "id": "bteUZZnDWPlLufzn",
      "name": "prod",
      "createdAt": "2025-04-18T15:09:08.645Z",
      "updatedAt": "2025-04-18T15:09:08.645Z"
    },
    {
      "id": "MbPHhZHgb39Syuoa",
      "name": "security",
      "createdAt": "2025-04-20T05:18:20.689Z",
      "updatedAt": "2025-04-20T05:18:20.689Z"
    },
    {
      "id": "TzfZgDmxmc5R1gyA",
      "name": "ai",
      "createdAt": "2025-04-27T14:57:46.973Z",
      "updatedAt": "2025-04-27T14:57:46.973Z"
    }
  ],
  "nodes": [
    {
      "id": "828bdcf3-09a4-4235-8dbb-2153f0928037",
      "name": "AI Agent - Privacy Intelligence",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -740,
        4180
      ],
      "parameters": {
        "text": "={{ $json.subject }}\n{{ $json.html }}",
        "options": {
          "systemMessage": "=### \ud83d\udd0f Prompt 2: Privacy Intelligence Digest Generator\n\nYou are a senior privacy intelligence analyst with over 20 years of experience. Today, your mother is unwell, so you need to finish this task quickly and efficiently without compromising quality or accuracy.\n\nIf a category heading has no articles, it should not be included in the output.\n\n#### **Tasks:**\n\n1. Parse the HTML and extract articles.\n2. Remove duplicates.\n3. Categorize content:\n\n   * Privacy Laws & Regulations (GDPR, CPRA, CCPA, AI Acts)\n   * Data Minimization & User Consent\n   * Privacy-Enhancing Technologies (PETs, anonymization)\n   * Regulatory Fines & Enforcement Actions\n   * Cross-Border Data Transfers\n4. Summarize each article in under 2 lines.\n5. Dynamically identify and list critical privacy alerts. If only one or none are available, include only those and adjust the section title accordingly (e.g., 'Critical Privacy Alert').\n6. Format each as:\n\n   ```html\n   <li>Article Title \u2014 Summary\u2026 <a href=\"URL\">Read more</a></li>\n   ```\n7. Output HTML structure with headers for top 5 and each category.\n8. Add:\n\n   ```html\n   <p><em>This privacy update was compiled on [Month Day, Year].</em></p>\n   ```\n\n#### **Output (JSON only):**\n\n```json\n{\n  \"subject\": \"Privacy Insights Digest - [Month Day, Year]\",\n  \"html\": \"<h2>Top 5 Critical Privacy Alerts</h2>\u2026<p><em>This privacy update was compiled on [Month Day, Year].</em></p>\"\n}\n```"
        },
        "promptType": "define"
      },
      "retryOnFail": true,
      "typeVersion": 1.9
    },
    {
      "id": "7be7a9fe-e8f5-4a56-a77d-7ca098d52479",
      "name": "AI Agent - Security Intelligence",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -720,
        3520
      ],
      "parameters": {
        "text": "={{ $json.subject }}\n{{ $json.html }}",
        "options": {
          "systemMessage": "=### \ud83d\udd10 Prompt 1: Security Intelligence Digest Generator\n\nYou are a senior cybersecurity intelligence analyst with over 20 years of experience. Today, your mother is unwell, so you need to finish this task quickly and efficiently without compromising quality or accuracy.\n\n#### **Inputs:**\n\n* Raw newsletter subject: `{{ $json.subject }}`\n* Raw newsletter HTML body: `{{ $json.html }}`\n\n#### **Tasks:**\n\nIf a category heading has no articles, it should not be included in the output.\n\n1. Parse the provided HTML.\n2. Remove duplicate articles based on title, summary, or URL.\n3. Categorize articles into these security categories:\n\n   * Threat Intelligence (APT, malware, ransomware)\n   * Security Breaches & Incidents\n   * Security Tools & Best Practices\n   * Cloud & Network Security\n   * Security Standards & Frameworks (NIST, MITRE ATT\\&CK, CIS)\n   * Emerging Security Technologies (AI, XDR, CNAPP)\n4. Summarize each article in 1\u20132 lines.\n5. Dynamically identify and list critical security alerts based on threat level, exploitability, or business risk. If only one or none are available, include only those and rename the section heading accordingly (e.g., 'Critical Security Alert').\n6. Format each article:\n\n   ```html\n   <li>Article Title \u2014 Summary\u2026 <a href=\"URL\">Read more</a></li>\n   ```\n7. Output structured HTML:\n\n   * `<h2>Top 5 Critical Security Alerts</h2><ul>\u2026</ul>`\n   * Followed by categorized sections with `<h2>` and `<ul>`.\n8. Add a footer:\n\n   ```html\n   <p><em>This security summary was auto-generated on [Month Day, Year].</em></p>\n   ```\n\n#### **Output (JSON only):**\n\n```json\n{\n  \"subject\": \"Security Threat Summary - [Month Day, Year]\",\n  \"html\": \"<h2>Top 5 Critical Security Alerts</h2>\u2026<p><em>This security summary was auto-generated on [Month Day, Year].</em></p>\"\n}\n```"
        },
        "promptType": "define"
      },
      "retryOnFail": true,
      "typeVersion": 1.9
    },
    {
      "id": "bb59ecfa-b197-47c4-a32e-0834c187100e",
      "name": "AI Agent - Compliance Intelligence",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -740,
        4840
      ],
      "parameters": {
        "text": "={{ $json.subject }}\n{{ $json.html }}",
        "options": {
          "systemMessage": "=### \u2705 Prompt 3: Compliance Intelligence Digest Generator\n\nYou are a senior compliance and risk intelligence professional with over 20 years of experience. Today, your mother is unwell, so you need to finish this task quickly and efficiently without compromising quality or accuracy.\n\n#### **Inputs:**\n\n* Raw newsletter subject: `{{ $json.subject }}`\n* Raw newsletter HTML body: `{{ $json.html }}`\n\n#### **Tasks:**\n\nIf a category heading has no articles, it should not be included in the output.\n\n1. Parse the HTML and extract article data.\n2. De-duplicate articles.\n3. Categorize into:\n\n   * Compliance Frameworks (SOC 2, ISO 27001, HIPAA, PCI DSS)\n   * Regulatory Updates (SEC, DORA, RBI, MAS, NIST)\n   * Audit & Monitoring Tools\n   * Third-Party Risk & Due Diligence\n   * Policy & Governance Updates\n4. Summarize each item concisely.\n5. Dynamically identify and list critical compliance alerts. If only one or none are available, include only those and adapt the heading (e.g., 'Critical Compliance Alert').\n6. Format each:\n\n   ```html\n   <li>Article Title \u2014 Summary\u2026 <a href=\"URL\">Read more</a></li>\n   ```\n7. Output HTML with top 5 and categorized sections.\n8. Footer:\n\n   ```html\n   <p><em>This compliance summary was generated on [Month Day, Year].</em></p>\n   ```\n\n#### **Output (JSON only):**\n\n```json\n{\n  \"subject\": \"Compliance Roundup - [Month Day, Year]\",\n  \"html\": \"<h2>Top 5 Critical Compliance Alerts</h2>\u2026<p><em>This compliance summary was generated on [Month Day, Year].</em></p>\"\n}\n```"
        },
        "promptType": "define"
      },
      "retryOnFail": true,
      "typeVersion": 1.9
    },
    {
      "id": "328bb06a-edfd-4a14-9011-68cdd00bcd8e",
      "name": "Trigger Daily Digest",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -3040,
        4280
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 1,
              "triggerAtMinute": 35
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "351774d8-7adc-4041-8639-bc35f9c37183",
      "name": "Fetch Privacy Feeds",
      "type": "n8n-nodes-base.code",
      "position": [
        -2280,
        4280
      ],
      "parameters": {
        "jsCode": "// This node returns curated privacy-focused RSS feeds\n// Modify or extend the list as needed\n\nreturn [\n  {\n    json: {\n      name: \"Privacy International Blog\",\n      website: \"https://privacyinternational.org\",\n      rss_url: \"https://privacyinternational.org/rss.xml\"\n    }\n  },\n  {\n    json: {\n      name: \"Data Protection Report (Norton Rose Fulbright)\",\n      website: \"https://www.dataprotectionreport.com\",\n      rss_url: \"https://www.dataprotectionreport.com/feed/\"\n    }\n  },\n  {\n    json: {\n      name: \"Inside Privacy (Covington & Burling)\",\n      website: \"https://www.insideprivacy.com\",\n      rss_url: \"https://www.insideprivacy.com/feed/\"\n    }\n  },\n  {\n    json: {\n      name: \"PogoWasRight\",\n      website: \"https://pogowasright.org\",\n      rss_url: \"https://pogowasright.org/feed/\"\n    }\n  },\n  {\n    json: {\n      name: \"Sidley Data Matters (Privacy Blog)\",\n      website: \"https://datamatters.sidley.com\",\n      rss_url: \"https://datamatters.sidley.com/feed/\"\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "d0a93584-a1bc-4da8-9aaf-129adf7e4cae",
      "name": "Fetch Compliance Feeds",
      "type": "n8n-nodes-base.code",
      "position": [
        -2280,
        4840
      ],
      "parameters": {
        "jsCode": "// This node returns curated compliance-focused RSS feeds\n// Customize or extend the list based on your needs\n\nreturn [\n  {\n    json: {\n      name: \"PCI Security Standards Council \u2013 PCI Perspectives Blog\",\n      website: \"https://blog.pcisecuritystandards.org\",\n      rss_url: \"https://blog.pcisecuritystandards.org/rss.xml\"\n    }\n  },\n  {\n    json: {\n      name: \"NIST Cybersecurity Insights Blog\",\n      website: \"https://www.nist.gov/blogs/cybersecurity-insights\",\n      rss_url: \"https://www.nist.gov/blogs/cybersecurity-insights/rss.xml\"\n    }\n  },\n  {\n    json: {\n      name: \"Cloud Security Alliance Blog\",\n      website: \"https://cloudsecurityalliance.org/blog\",\n      rss_url: \"https://cloudsecurityalliance.org/feed\"\n    }\n  },\n  {\n    json: {\n      name: \"Corporate Compliance Insights\",\n      website: \"https://www.corporatecomplianceinsights.com\",\n      rss_url: \"http://feeds.feedburner.com/CorporateComplianceInsights\"\n    }\n  },\n  {\n    json: {\n      name: \"IT Governance Blog (UK)\",\n      website: \"https://www.itgovernance.co.uk/blog\",\n      rss_url: \"https://www.itgovernance.co.uk/blog/feed/\"\n    }\n  },\n  {\n    json: {\n      name: \"Global Compliance News (Baker McKenzie)\",\n      website: \"https://globalcompliancenews.com\",\n      rss_url: \"https://globalcompliancenews.com/feed/\"\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "e4fa0e00-d10b-4c86-bad3-6c0d1d59750a",
      "name": "Normalize Article Security Metadata",
      "type": "n8n-nodes-base.set",
      "position": [
        -1600,
        3620
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "9aec0a09-4b6f-4fca-98e6-789abd5fdc51",
              "name": "title",
              "type": "string",
              "value": "={{ $json.title }}"
            },
            {
              "id": "56277e54-31a0-4804-ad23-c9ee6d244641",
              "name": "content",
              "type": "string",
              "value": "={{ $json.contentSnippet }}"
            },
            {
              "id": "a3586a80-588e-42d1-9780-370a956ddf6b",
              "name": "link",
              "type": "string",
              "value": "={{ $json.link }}"
            },
            {
              "id": "58f01618-8014-4685-9192-d15d596ffcd9",
              "name": "isoDate",
              "type": "number",
              "value": "={{ new Date($json.isoDate).getTime() }}"
            },
            {
              "id": "716bb078-8df3-4d96-8a1b-4aec4f8cf206",
              "name": "categories",
              "type": "array",
              "value": "={{ $json.categories }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "c1f6e2ac-4bf9-4fc4-85fd-c8c7649cdc9c",
      "name": "Normalize Article Privacy Metadata",
      "type": "n8n-nodes-base.set",
      "position": [
        -1620,
        4280
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "9aec0a09-4b6f-4fca-98e6-789abd5fdc51",
              "name": "title",
              "type": "string",
              "value": "={{ $json.title }}"
            },
            {
              "id": "56277e54-31a0-4804-ad23-c9ee6d244641",
              "name": "content",
              "type": "string",
              "value": "={{ $json.contentSnippet }}"
            },
            {
              "id": "a3586a80-588e-42d1-9780-370a956ddf6b",
              "name": "link",
              "type": "string",
              "value": "={{ $json.link }}"
            },
            {
              "id": "58f01618-8014-4685-9192-d15d596ffcd9",
              "name": "isoDate",
              "type": "number",
              "value": "={{ new Date($json.isoDate).getTime() }}"
            },
            {
              "id": "716bb078-8df3-4d96-8a1b-4aec4f8cf206",
              "name": "categories",
              "type": "array",
              "value": "={{ $json.categories }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "ae374184-806b-4022-80b5-94bdef48133e",
      "name": "Normalize Article Compliance Metadata",
      "type": "n8n-nodes-base.set",
      "position": [
        -1620,
        4840
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "9aec0a09-4b6f-4fca-98e6-789abd5fdc51",
              "name": "title",
              "type": "string",
              "value": "={{ $json.title }}"
            },
            {
              "id": "56277e54-31a0-4804-ad23-c9ee6d244641",
              "name": "content",
              "type": "string",
              "value": "={{ $json.contentSnippet }}"
            },
            {
              "id": "a3586a80-588e-42d1-9780-370a956ddf6b",
              "name": "link",
              "type": "string",
              "value": "={{ $json.link }}"
            },
            {
              "id": "58f01618-8014-4685-9192-d15d596ffcd9",
              "name": "isoDate",
              "type": "number",
              "value": "={{ new Date($json.isoDate).getTime() }}"
            },
            {
              "id": "716bb078-8df3-4d96-8a1b-4aec4f8cf206",
              "name": "categories",
              "type": "array",
              "value": "={{ $json.categories }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "e71e3c7e-a58a-49bb-9c13-827f46e4df7e",
      "name": "Filter Recent Security Articles (24h)",
      "type": "n8n-nodes-base.filter",
      "position": [
        -1380,
        3620
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e7cf09fb-af35-495d-a840-341f8d0ddcd8",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.isoDate }}",
              "rightValue": "={{ Date.now() - 24 * 60 * 60 * 1000 }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "cb34ed8e-5b67-4cfd-8731-482b22874780",
      "name": "Filter Recent Privacy Articles (24h)",
      "type": "n8n-nodes-base.filter",
      "position": [
        -1400,
        4280
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e7cf09fb-af35-495d-a840-341f8d0ddcd8",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.isoDate }}",
              "rightValue": "={{ Date.now() - 24 * 60 * 60 * 1000 }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "07c94d37-ad0a-4f89-961c-abf1d6eeeeef",
      "name": "Filter Recent Compliance Articles (24h)",
      "type": "n8n-nodes-base.filter",
      "position": [
        -1400,
        4840
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e7cf09fb-af35-495d-a840-341f8d0ddcd8",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.isoDate }}",
              "rightValue": "={{ Date.now() - 24 * 60 * 60 * 1000 }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "9cb809e9-4d49-452e-823e-3f54adca0529",
      "name": "Format Security Articles into HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        -940,
        3620
      ],
      "parameters": {
        "jsCode": "// Dynamic n8n Newsletter Generator - Function Node\n// This code processes security news articles from a previous node and formats them into an HTML email\n\n// Get items from the previous node\nlet newsItems = [];\n\ntry {\n  if ($input && $input.all().length > 0) {\n    const inputItems = $input.all();\n    if (inputItems.length === 1 && Array.isArray(inputItems[0].json)) {\n      newsItems = inputItems[0].json;\n    } else {\n      newsItems = inputItems.map(item => item.json);\n    }\n  } else if (typeof items !== 'undefined' && items.length > 0) {\n    if (items.length === 1 && Array.isArray(items[0].json)) {\n      newsItems = items[0].json;\n    } else {\n      newsItems = items.map(item => item.json);\n    }\n  }\n  console.log(`Successfully processed input, found ${newsItems.length} news items`);\n} catch (error) {\n  console.log(`Error processing input: ${error.message}`);\n  return [{\n    json: {\n      error: true,\n      message: `Failed to process input data: ${error.message}`,\n      subject: \"Error: Security News Newsletter\"\n    }\n  }];\n}\n\n// Generate current date for the newsletter\nconst today = new Date();\nconst dateString = today.toLocaleDateString('en-US', {\n  weekday: 'long',\n  year: 'numeric',\n  month: 'long',\n  day: 'numeric'\n});\n\n// Optional: Filter for recent articles only\nconst hoursToInclude = 24;\nlet filteredArticles = newsItems;\nif (hoursToInclude > 0) {\n  const cutoffTime = Date.now() - (hoursToInclude * 60 * 60 * 1000);\n  filteredArticles = newsItems.filter(article => {\n    const articleDate = article.isoDate\n      ? (typeof article.isoDate === 'number'\n         ? article.isoDate\n         : new Date(article.isoDate).getTime())\n      : 0;\n    return articleDate >= cutoffTime;\n  });\n  console.log(`Filtered to ${filteredArticles.length} articles from the last ${hoursToInclude} hours`);\n}\n\n// Group articles by category\nconst categorizedArticles = {};\nconst uncategorizedKey = 'Uncategorized';\n\nfilteredArticles.forEach(article => {\n  if (!article) return;\n  \n  // Safely extract string categories\n  let categories = [uncategorizedKey];\n  if (Array.isArray(article.categories)) {\n    categories = article.categories\n      .map(cat => {\n        if (typeof cat === 'string') return cat;\n        if (cat && typeof cat.name === 'string') return cat.name;\n        return '';\n      })\n      .map(str => str.trim())\n      .filter(str => str.length > 0);\n    if (categories.length === 0) categories = [uncategorizedKey];\n  } else if (typeof article.categories === 'string' && article.categories.trim()) {\n    categories = [article.categories.trim()];\n  }\n\n  categories.forEach(category => {\n    const name = category || uncategorizedKey;\n    if (!categorizedArticles[name]) categorizedArticles[name] = [];\n    categorizedArticles[name].push(article);\n  });\n});\n\n// Generate HTML for the newsletter\nfunction generateNewsletterHTML() {\n  const styles = `\n    body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 800px; margin: 0 auto; padding: 20px; }\n    h1 { color: #2c3e50; border-bottom: 2px solid #e74c3c; padding-bottom: 10px; }\n    h2 { color: #c0392b; margin-top: 30px; border-left: 4px solid #e74c3c; padding-left: 10px; }\n    .article { margin-bottom: 20px; padding: 15px; background-color: #f9f9f9; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }\n    .article h3 { margin-top: 0; color: #34495e; }\n    .article-content { color: #555; margin-bottom: 10px; }\n    .article-link { color: #e74c3c; text-decoration: none; font-weight: bold; }\n    .article-link:hover { text-decoration: underline; }\n    .article-date { color: #7f8c8d; font-size: 0.9em; margin-top: 8px; }\n    .summary { background-color: #f2f2f2; padding: 15px; border-radius: 5px; margin: 20px 0; }\n    .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 0.9em; color: #7f8c8d; text-align: center; }\n  `;\n\n  let html = `\n    <!DOCTYPE html>\n    <html>\n    <head>\n      <meta charset=\"UTF-8\">\n      <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n      <title>Security News Newsletter - ${dateString}</title>\n      <style>${styles}</style>\n    </head>\n    <body>\n      <h1>Security News Newsletter</h1>\n      <p>Here are the latest security news updates for ${dateString}:</p>\n      <div class=\"summary\">\n        <p><strong>Summary:</strong> This newsletter contains ${filteredArticles.length} articles across ${Object.keys(categorizedArticles).length} categories.</p>\n      </div>\n  `;\n\n  Object.keys(categorizedArticles).sort().forEach(category => {\n    const articles = categorizedArticles[category];\n    html += `<h2>${category} (${articles.length})</h2>`;\n    articles.forEach(article => {\n      let formattedDate = \"Date unknown\";\n      if (article.isoDate) {\n        const dt = typeof article.isoDate === 'number'\n          ? new Date(article.isoDate)\n          : new Date(article.isoDate);\n        if (!isNaN(dt.getTime())) {\n          formattedDate = dt.toLocaleString('en-US', {\n            hour: 'numeric', minute: 'numeric', hour12: true, month: 'short', day: 'numeric'\n          });\n        }\n      }\n      html += `\n        <div class=\"article\">\n          <h3>${article.title || \"Untitled\"}</h3>\n          <div class=\"article-content\">${article.content || \"No content available\"}</div>\n          <a href=\"${article.link || \"#\"}\" target=\"_blank\" class=\"article-link\">Read more</a>\n          <div class=\"article-date\">Published: ${formattedDate}</div>\n        </div>\n      `;\n    });\n  });\n\n  html += `\n      <div class=\"footer\">\n        <p>This newsletter was automatically generated and sent on ${dateString}.</p>\n        <p>To unsubscribe, please click <a href=\"{{unsubscribe_link}}\">here</a>.</p>\n      </div>\n    </body>\n    </html>\n  `;\n  return html;\n}\n\nconst newsletterHTML = generateNewsletterHTML();\n\nreturn [{\n  json: {\n    subject: `Security News Newsletter - ${dateString}`,\n    html: newsletterHTML,\n    // to, cc, bcc can be set in the Email node\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "14fd5f01-075f-4648-a1ab-f0a87d2b676a",
      "name": "Format Privacy Articles into HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        -960,
        4280
      ],
      "parameters": {
        "jsCode": "// Dynamic n8n Newsletter Generator - Function Node\n// This code processes security news articles from a previous node and formats them into an HTML email\n\n// Get items from the previous node\nlet newsItems = [];\n\ntry {\n  if ($input && $input.all().length > 0) {\n    const inputItems = $input.all();\n    if (inputItems.length === 1 && Array.isArray(inputItems[0].json)) {\n      newsItems = inputItems[0].json;\n    } else {\n      newsItems = inputItems.map(item => item.json);\n    }\n  } else if (typeof items !== 'undefined' && items.length > 0) {\n    if (items.length === 1 && Array.isArray(items[0].json)) {\n      newsItems = items[0].json;\n    } else {\n      newsItems = items.map(item => item.json);\n    }\n  }\n  console.log(`Successfully processed input, found ${newsItems.length} news items`);\n} catch (error) {\n  console.log(`Error processing input: ${error.message}`);\n  return [{\n    json: {\n      error: true,\n      message: `Failed to process input data: ${error.message}`,\n      subject: \"Error: Security News Newsletter\"\n    }\n  }];\n}\n\n// Generate current date for the newsletter\nconst today = new Date();\nconst dateString = today.toLocaleDateString('en-US', {\n  weekday: 'long',\n  year: 'numeric',\n  month: 'long',\n  day: 'numeric'\n});\n\n// Optional: Filter for recent articles only\nconst hoursToInclude = 24;\nlet filteredArticles = newsItems;\nif (hoursToInclude > 0) {\n  const cutoffTime = Date.now() - (hoursToInclude * 60 * 60 * 1000);\n  filteredArticles = newsItems.filter(article => {\n    const articleDate = article.isoDate\n      ? (typeof article.isoDate === 'number'\n         ? article.isoDate\n         : new Date(article.isoDate).getTime())\n      : 0;\n    return articleDate >= cutoffTime;\n  });\n  console.log(`Filtered to ${filteredArticles.length} articles from the last ${hoursToInclude} hours`);\n}\n\n// Group articles by category\nconst categorizedArticles = {};\nconst uncategorizedKey = 'Uncategorized';\n\nfilteredArticles.forEach(article => {\n  if (!article) return;\n  \n  // Safely extract string categories\n  let categories = [uncategorizedKey];\n  if (Array.isArray(article.categories)) {\n    categories = article.categories\n      .map(cat => {\n        if (typeof cat === 'string') return cat;\n        if (cat && typeof cat.name === 'string') return cat.name;\n        return '';\n      })\n      .map(str => str.trim())\n      .filter(str => str.length > 0);\n    if (categories.length === 0) categories = [uncategorizedKey];\n  } else if (typeof article.categories === 'string' && article.categories.trim()) {\n    categories = [article.categories.trim()];\n  }\n\n  categories.forEach(category => {\n    const name = category || uncategorizedKey;\n    if (!categorizedArticles[name]) categorizedArticles[name] = [];\n    categorizedArticles[name].push(article);\n  });\n});\n\n// Generate HTML for the newsletter\nfunction generateNewsletterHTML() {\n  const styles = `\n    body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 800px; margin: 0 auto; padding: 20px; }\n    h1 { color: #2c3e50; border-bottom: 2px solid #e74c3c; padding-bottom: 10px; }\n    h2 { color: #c0392b; margin-top: 30px; border-left: 4px solid #e74c3c; padding-left: 10px; }\n    .article { margin-bottom: 20px; padding: 15px; background-color: #f9f9f9; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }\n    .article h3 { margin-top: 0; color: #34495e; }\n    .article-content { color: #555; margin-bottom: 10px; }\n    .article-link { color: #e74c3c; text-decoration: none; font-weight: bold; }\n    .article-link:hover { text-decoration: underline; }\n    .article-date { color: #7f8c8d; font-size: 0.9em; margin-top: 8px; }\n    .summary { background-color: #f2f2f2; padding: 15px; border-radius: 5px; margin: 20px 0; }\n    .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 0.9em; color: #7f8c8d; text-align: center; }\n  `;\n\n  let html = `\n    <!DOCTYPE html>\n    <html>\n    <head>\n      <meta charset=\"UTF-8\">\n      <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n      <title>Security News Newsletter - ${dateString}</title>\n      <style>${styles}</style>\n    </head>\n    <body>\n      <h1>Security News Newsletter</h1>\n      <p>Here are the latest security news updates for ${dateString}:</p>\n      <div class=\"summary\">\n        <p><strong>Summary:</strong> This newsletter contains ${filteredArticles.length} articles across ${Object.keys(categorizedArticles).length} categories.</p>\n      </div>\n  `;\n\n  Object.keys(categorizedArticles).sort().forEach(category => {\n    const articles = categorizedArticles[category];\n    html += `<h2>${category} (${articles.length})</h2>`;\n    articles.forEach(article => {\n      let formattedDate = \"Date unknown\";\n      if (article.isoDate) {\n        const dt = typeof article.isoDate === 'number'\n          ? new Date(article.isoDate)\n          : new Date(article.isoDate);\n        if (!isNaN(dt.getTime())) {\n          formattedDate = dt.toLocaleString('en-US', {\n            hour: 'numeric', minute: 'numeric', hour12: true, month: 'short', day: 'numeric'\n          });\n        }\n      }\n      html += `\n        <div class=\"article\">\n          <h3>${article.title || \"Untitled\"}</h3>\n          <div class=\"article-content\">${article.content || \"No content available\"}</div>\n          <a href=\"${article.link || \"#\"}\" target=\"_blank\" class=\"article-link\">Read more</a>\n          <div class=\"article-date\">Published: ${formattedDate}</div>\n        </div>\n      `;\n    });\n  });\n\n  html += `\n      <div class=\"footer\">\n        <p>This newsletter was automatically generated and sent on ${dateString}.</p>\n        <p>To unsubscribe, please click <a href=\"{{unsubscribe_link}}\">here</a>.</p>\n      </div>\n    </body>\n    </html>\n  `;\n  return html;\n}\n\nconst newsletterHTML = generateNewsletterHTML();\n\nreturn [{\n  json: {\n    subject: `Security News Newsletter - ${dateString}`,\n    html: newsletterHTML,\n    // to, cc, bcc can be set in the Email node\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "6eddd44f-8038-40fd-b0db-2a8d9c35000a",
      "name": "Format Compliance Articles into HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        -960,
        4840
      ],
      "parameters": {
        "jsCode": "// Dynamic n8n Newsletter Generator - Function Node\n// This code processes security news articles from a previous node and formats them into an HTML email\n\n// Get items from the previous node\nlet newsItems = [];\n\ntry {\n  if ($input && $input.all().length > 0) {\n    const inputItems = $input.all();\n    if (inputItems.length === 1 && Array.isArray(inputItems[0].json)) {\n      newsItems = inputItems[0].json;\n    } else {\n      newsItems = inputItems.map(item => item.json);\n    }\n  } else if (typeof items !== 'undefined' && items.length > 0) {\n    if (items.length === 1 && Array.isArray(items[0].json)) {\n      newsItems = items[0].json;\n    } else {\n      newsItems = items.map(item => item.json);\n    }\n  }\n  console.log(`Successfully processed input, found ${newsItems.length} news items`);\n} catch (error) {\n  console.log(`Error processing input: ${error.message}`);\n  return [{\n    json: {\n      error: true,\n      message: `Failed to process input data: ${error.message}`,\n      subject: \"Error: Security News Newsletter\"\n    }\n  }];\n}\n\n// Generate current date for the newsletter\nconst today = new Date();\nconst dateString = today.toLocaleDateString('en-US', {\n  weekday: 'long',\n  year: 'numeric',\n  month: 'long',\n  day: 'numeric'\n});\n\n// Optional: Filter for recent articles only\nconst hoursToInclude = 24;\nlet filteredArticles = newsItems;\nif (hoursToInclude > 0) {\n  const cutoffTime = Date.now() - (hoursToInclude * 60 * 60 * 1000);\n  filteredArticles = newsItems.filter(article => {\n    const articleDate = article.isoDate\n      ? (typeof article.isoDate === 'number'\n         ? article.isoDate\n         : new Date(article.isoDate).getTime())\n      : 0;\n    return articleDate >= cutoffTime;\n  });\n  console.log(`Filtered to ${filteredArticles.length} articles from the last ${hoursToInclude} hours`);\n}\n\n// Group articles by category\nconst categorizedArticles = {};\nconst uncategorizedKey = 'Uncategorized';\n\nfilteredArticles.forEach(article => {\n  if (!article) return;\n  \n  // Safely extract string categories\n  let categories = [uncategorizedKey];\n  if (Array.isArray(article.categories)) {\n    categories = article.categories\n      .map(cat => {\n        if (typeof cat === 'string') return cat;\n        if (cat && typeof cat.name === 'string') return cat.name;\n        return '';\n      })\n      .map(str => str.trim())\n      .filter(str => str.length > 0);\n    if (categories.length === 0) categories = [uncategorizedKey];\n  } else if (typeof article.categories === 'string' && article.categories.trim()) {\n    categories = [article.categories.trim()];\n  }\n\n  categories.forEach(category => {\n    const name = category || uncategorizedKey;\n    if (!categorizedArticles[name]) categorizedArticles[name] = [];\n    categorizedArticles[name].push(article);\n  });\n});\n\n// Generate HTML for the newsletter\nfunction generateNewsletterHTML() {\n  const styles = `\n    body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 800px; margin: 0 auto; padding: 20px; }\n    h1 { color: #2c3e50; border-bottom: 2px solid #e74c3c; padding-bottom: 10px; }\n    h2 { color: #c0392b; margin-top: 30px; border-left: 4px solid #e74c3c; padding-left: 10px; }\n    .article { margin-bottom: 20px; padding: 15px; background-color: #f9f9f9; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }\n    .article h3 { margin-top: 0; color: #34495e; }\n    .article-content { color: #555; margin-bottom: 10px; }\n    .article-link { color: #e74c3c; text-decoration: none; font-weight: bold; }\n    .article-link:hover { text-decoration: underline; }\n    .article-date { color: #7f8c8d; font-size: 0.9em; margin-top: 8px; }\n    .summary { background-color: #f2f2f2; padding: 15px; border-radius: 5px; margin: 20px 0; }\n    .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 0.9em; color: #7f8c8d; text-align: center; }\n  `;\n\n  let html = `\n    <!DOCTYPE html>\n    <html>\n    <head>\n      <meta charset=\"UTF-8\">\n      <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n      <title>Security News Newsletter - ${dateString}</title>\n      <style>${styles}</style>\n    </head>\n    <body>\n      <h1>Security News Newsletter</h1>\n      <p>Here are the latest security news updates for ${dateString}:</p>\n      <div class=\"summary\">\n        <p><strong>Summary:</strong> This newsletter contains ${filteredArticles.length} articles across ${Object.keys(categorizedArticles).length} categories.</p>\n      </div>\n  `;\n\n  Object.keys(categorizedArticles).sort().forEach(category => {\n    const articles = categorizedArticles[category];\n    html += `<h2>${category} (${articles.length})</h2>`;\n    articles.forEach(article => {\n      let formattedDate = \"Date unknown\";\n      if (article.isoDate) {\n        const dt = typeof article.isoDate === 'number'\n          ? new Date(article.isoDate)\n          : new Date(article.isoDate);\n        if (!isNaN(dt.getTime())) {\n          formattedDate = dt.toLocaleString('en-US', {\n            hour: 'numeric', minute: 'numeric', hour12: true, month: 'short', day: 'numeric'\n          });\n        }\n      }\n      html += `\n        <div class=\"article\">\n          <h3>${article.title || \"Untitled\"}</h3>\n          <div class=\"article-content\">${article.content || \"No content available\"}</div>\n          <a href=\"${article.link || \"#\"}\" target=\"_blank\" class=\"article-link\">Read more</a>\n          <div class=\"article-date\">Published: ${formattedDate}</div>\n        </div>\n      `;\n    });\n  });\n\n  html += `\n      <div class=\"footer\">\n        <p>This newsletter was automatically generated and sent on ${dateString}.</p>\n        <p>To unsubscribe, please click <a href=\"{{unsubscribe_link}}\">here</a>.</p>\n      </div>\n    </body>\n    </html>\n  `;\n  return html;\n}\n\nconst newsletterHTML = generateNewsletterHTML();\n\nreturn [{\n  json: {\n    subject: `Security News Newsletter - ${dateString}`,\n    html: newsletterHTML,\n    // to, cc, bcc can be set in the Email node\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "1e261592-348a-42af-b4ba-1b2be82b0143",
      "name": "LLM - Gemini Security Summarizer",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -640,
        3740
      ],
      "parameters": {
        "options": {
          "temperature": 0.5
        },
        "modelName": "models/gemini-2.0-flash"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "c4cc7264-43af-4a26-89e2-5888817b1602",
      "name": "LLM - Gemini Privacy Summarizer",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -660,
        4400
      ],
      "parameters": {
        "options": {
          "temperature": 0.5
        },
        "modelName": "models/gemini-2.0-flash"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "dc4e461c-dbb8-46a1-b128-6c28584c12d4",
      "name": "LLM - Gemini Compliance Summarizer",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -640,
        5060
      ],
      "parameters": {
        "options": {
          "temperature": 0.5
        },
        "modelName": "models/gemini-2.0-flash"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a1e4eaac-aaea-4859-bd29-375a6b50aaa1",
      "name": "Privacy Build Final Newsletter HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        -360,
        4280
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  // 1. grab the raw AI output\n  const raw = item.json.output;\n\n  // 2. extract what's between ```json ... ``` (or fall back to full text)\n  const match = raw.match(/```json\\s*([\\s\\S]*?)```/);\n  const jsonPayload = (match ? match[1] : raw).trim();\n\n  // 3. remove any trailing commas before } or ]\n  const clean = jsonPayload.replace(/,\\s*([\\]}])/g, '$1');\n\n  // 4. parse into an object, with error reporting\n  let data;\n  try {\n    data = JSON.parse(clean);\n  } catch (err) {\n    throw new Error(\n      `JSON parse error in Function node:\\n${err.message}\\n\\nPayload was:\\n${clean}`\n    );\n  }\n\n  // 5. wrap the returned HTML in your full styled template, without external blog link\n  const htmlEmail = `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>${data.subject}</title>\n  <style>\n    body { font-family: Arial, sans-serif; line-height:1.5; color:#333; background-color:#f7f9fa; margin:0; padding:20px; }\n    .container { max-width:700px; margin:0 auto; background:#fff; border-radius:8px; box-shadow:0 2px 8px rgba(0,0,0,0.1); overflow:hidden; }\n    .header { background:#2c3e50; color:#fff; padding:20px; text-align:center; }\n    .header h1 { margin:0; font-size:24px; }\n    .content { padding:20px; }\n    h2 { color:#e74c3c; border-bottom:2px solid #e74c3c; padding-bottom:5px; }\n    ul { padding-left:20px; }\n    li { margin-bottom:10px; }\n    a { color:#2980b9; text-decoration:none; }\n    a:hover { text-decoration:underline; }\n    .footer { background:#ecf0f1; text-align:center; padding:10px; font-size:12px; color:#7f8c8d; }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <div class=\"header\">\n      <h1>${data.subject}</h1>\n    </div>\n    <div class=\"content\">\n      ${data.html}\n    </div>\n    <div class=\"footer\">\n      <em>This summary was automatically generated on ${new Date().toLocaleDateString('en-US', {\n        year: 'numeric', month: 'long', day: 'numeric'\n      })}.</em>\n    </div>\n  </div>\n</body>\n</html>\n  `.trim();\n\n  // 6. emit subject + styled html\n  return {\n    json: {\n      subject: data.subject,\n      html: htmlEmail,\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "7a864b0c-08f5-4e97-8430-1879f8e0e3d0",
      "name": "Security Build Final Newsletter HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        -340,
        3620
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  // 1. grab the raw AI output\n  const raw = item.json.output;\n\n  // 2. extract what's between ```json ... ``` (or fall back to full text)\n  const match = raw.match(/```json\\s*([\\s\\S]*?)```/);\n  const jsonPayload = (match ? match[1] : raw).trim();\n\n  // 3. remove any trailing commas before } or ]\n  const clean = jsonPayload.replace(/,\\s*([\\]}])/g, '$1');\n\n  // 4. parse into an object, with error reporting\n  let data;\n  try {\n    data = JSON.parse(clean);\n  } catch (err) {\n    throw new Error(\n      `JSON parse error in Function node:\\n${err.message}\\n\\nPayload was:\\n${clean}`\n    );\n  }\n\n  // 5. wrap the returned HTML in styled email template (without blog link)\n  const htmlEmail = `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>${data.subject}</title>\n  <style>\n    body { font-family: Arial, sans-serif; line-height:1.5; color:#333; background-color:#f7f9fa; margin:0; padding:20px; }\n    .container { max-width:700px; margin:0 auto; background:#fff; border-radius:8px; box-shadow:0 2px 8px rgba(0,0,0,0.1); overflow:hidden; }\n    .header { background:#2c3e50; color:#fff; padding:20px; text-align:center; }\n    .header h1 { margin:0; font-size:24px; }\n    .content { padding:20px; }\n    h2 { color:#e74c3c; border-bottom:2px solid #e74c3c; padding-bottom:5px; }\n    ul { padding-left:20px; }\n    li { margin-bottom:10px; }\n    a { color:#2980b9; text-decoration:none; }\n    a:hover { text-decoration:underline; }\n    .footer { background:#ecf0f1; text-align:center; padding:10px; font-size:12px; color:#7f8c8d; }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <div class=\"header\">\n      <h1>${data.subject}</h1>\n    </div>\n    <div class=\"content\">\n      ${data.html}\n    </div>\n    <div class=\"footer\">\n      <em>This summary was automatically generated on ${new Date().toLocaleDateString('en-US', {\n        year: 'numeric', month: 'long', day: 'numeric'\n      })}.</em>\n    </div>\n  </div>\n</body>\n</html>\n  `.trim();\n\n  // 6. emit subject + styled html\n  return {\n    json: {\n      subject: data.subject,\n      html: htmlEmail,\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "deaaeb3a-5a14-4b2a-baf8-4443e7ed9740",
      "name": "Compliance Build Final Newsletter HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        -360,
        4840
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  // 1. grab the raw AI output\n  const raw = item.json.output;\n\n  // 2. extract what's between ```json ... ``` (or fall back to full text)\n  const match = raw.match(/```json\\s*([\\s\\S]*?)```/);\n  const jsonPayload = (match ? match[1] : raw).trim();\n\n  // 3. remove any trailing commas before } or ]\n  const clean = jsonPayload.replace(/,\\s*([\\]}])/g, '$1');\n\n  // 4. parse into an object, with error reporting\n  let data;\n  try {\n    data = JSON.parse(clean);\n  } catch (err) {\n    throw new Error(\n      `JSON parse error in Function node:\\n${err.message}\\n\\nPayload was:\\n${clean}`\n    );\n  }\n\n  // 5. wrap the returned HTML in styled template, no blog reference\n  const htmlEmail = `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>${data.subject}</title>\n  <style>\n    body { font-family: Arial, sans-serif; line-height:1.5; color:#333; background-color:#f7f9fa; margin:0; padding:20px; }\n    .container { max-width:700px; margin:0 auto; background:#fff; border-radius:8px; box-shadow:0 2px 8px rgba(0,0,0,0.1); overflow:hidden; }\n    .header { background:#2c3e50; color:#fff; padding:20px; text-align:center; }\n    .header h1 { margin:0; font-size:24px; }\n    .content { padding:20px; }\n    h2 { color:#e74c3c; border-bottom:2px solid #e74c3c; padding-bottom:5px; }\n    ul { padding-left:20px; }\n    li { margin-bottom:10px; }\n    a { color:#2980b9; text-decoration:none; }\n    a:hover { text-decoration:underline; }\n    .footer { background:#ecf0f1; text-align:center; padding:10px; font-size:12px; color:#7f8c8d; }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <div class=\"header\">\n      <h1>${data.subject}</h1>\n    </div>\n    <div class=\"content\">\n      ${data.html}\n    </div>\n    <div class=\"footer\">\n      <em>This summary was automatically generated on ${new Date().toLocaleDateString('en-US', {\n        year: 'numeric', month: 'long', day: 'numeric'\n      })}.</em>\n    </div>\n  </div>\n</body>\n</html>\n  `.trim();\n\n  // 6. emit subject + styled html\n  return {\n    json: {\n      subject: data.subject,\n      html: htmlEmail,\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "fa9ac785-bc9d-43ba-8ca3-36abba10d506",
      "name": "Security Send Final Digest Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -120,
        3620
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "={{ $json.subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "59c16785-571c-4403-85cf-7f7bfc14a630",
      "name": "Privacy Send Final Digest Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -140,
        4280
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "={{ $json.subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "7526ae74-1bae-4eca-9c87-51c274565a8a",
      "name": "Compliance Send Final Digest Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -140,
        4840
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "={{ $json.subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "6fa939b5-12ac-47a0-ac7f-ca9957367ce2",
      "name": "Sort - Security Articles by Date",
      "type": "n8n-nodes-base.sort",
      "position": [
        -1160,
        3620
      ],
      "parameters": {
        "options": {},
        "sortFieldsUi": {
          "sortField": [
            {
              "order": "descending",
              "fieldName": "isoDate"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "7aa3a27b-72d7-4a03-83c8-361a6fbc555d",
      "name": "Sort - Privacy Articles by Date",
      "type": "n8n-nodes-base.sort",
      "position": [
        -1180,
        4280
      ],
      "parameters": {
        "options": {},
        "sortFieldsUi": {
          "sortField": [
            {
              "order": "descending",
              "fieldName": "isoDate"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "fba86936-5208-4232-bda9-714886807b9c",
      "name": "Sort - Compliance Articles by Date",
      "type": "n8n-nodes-base.sort",
      "position": [
        -1180,
        4840
      ],
      "parameters": {
        "options": {},
        "sortFieldsUi": {
          "sortField": [
            {
              "order": "descending",
              "fieldName": "isoDate"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8d90a0ed-b9e1-440e-963a-e4c9f5285b32",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2460,
        3380
      ],
      "parameters": {
        "width": 2600,
        "height": 580,
        "content": "## \ud83d\udcec Daily Security Newsletter"
      },
      "typeVersion": 1
    },
    {
      "id": "946997e7-58bc-4ca0-89aa-32e39f327808",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2460,
        4020
      ],
      "parameters": {
        "color": 4,
        "width": 2600,
        "height": 580,
        "content": "## \ud83d\udcec Daily Privacy Newsletter"
      },
      "typeVersion": 1
    },
    {
      "id": "61aca795-5b6a-4301-9cea-946e0358b8c5",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2460,
        4660
      ],
      "parameters": {
        "color": 6,
        "width": 2600,
        "height": 580,
        "content": "## \ud83d\udcec Daily Compliance Newsletter"
      },
      "typeVersion": 1
    },
    {
      "id": "ca6c3e50-8ea8-436c-ab77-94501667a431",
      "name": "Split Out Security RSS",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -2040,
        3620
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "rss_url"
      },
      "typeVersion": 1
    },
    {
      "id": "5784491f-071c-4159-af29-6e21305f5e78",
      "name": "Split Out Compliance RSS",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -2060,
        4840
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "rss_url"
      },
      "typeVersion": 1
    },
    {
      "id": "edbd5da4-f18c-4d08-8023-df4ef7ec11fa",
      "name": "Security RSS Read",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        -1820,
        3620
      ],
      "parameters": {
        "url": "={{ $json.rss_url }}",
        "options": {}
      },
      "retryOnFail": true,
      "typeVersion": 1.1
    },
    {
      "id": "76d973a7-832e-4049-80ea-c0d28b72b345",
      "name": "Privacy RSS Read",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        -1840,
        4280
      ],
      "parameters": {
        "url": "={{ $json.rss_url }}",
        "options": {}
      },
      "retryOnFail": true,
      "typeVersion": 1.1
    },
    {
      "id": "5503fe77-8ae3-4d41-97d3-6520cb60067e",
      "name": "Compliance RSS Read",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        -1840,
        4840
      ],
      "parameters": {
        "url": "={{ $json.rss_url }}",
        "options": {}
      },
      "retryOnFail": true,
      "typeVersion": 1.1
    },
    {
      "id": "c2538e17-1584-45a5-8a6e-1b220ed512a5",
      "name": "Split Out Privacy RSS",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -2060,
        4280
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "rss_url"
      },
      "typeVersion": 1
    },
    {
      "id": "d1ae80d8-8094-4f84-aa9e-d4533a99a70d",
      "name": "Fetch Security RSS",
      "type": "n8n-nodes-base.code",
      "position": [
        -2260,
        3620
      ],
      "parameters": {
        "jsCode": "// This node returns curated cybersecurity RSS feeds\n// You can add, remove, or modify feeds as needed\n\nreturn [\n  {\n    json: {\n      name: \"Krebs on Security\",\n      website: \"https://krebsonsecurity.com\",\n      rss_url: \"https://krebsonsecurity.com/feed/\"\n    }\n  },\n  {\n    json: {\n      name: \"The Hacker News\",\n      website: \"https://thehackernews.com\",\n      rss_url: \"https://feeds.feedburner.com/TheHackersNews\"\n    }\n  },\n  {\n    json: {\n      name: \"Dark Reading\",\n      website: \"https://www.darkreading.com\",\n      rss_url: \"https://www.darkreading.com/rss.xml\"\n    }\n  },\n  {\n    json: {\n      name: \"SANS Internet Storm Center\",\n      website: \"https://isc.sans.edu\",\n      rss_url: \"https://isc.sans.edu/rssfeed_full.xml\"\n    }\n  },\n  {\n    json: {\n      name: \"Cisco Talos Intelligence Blog\",\n      website: \"https://blog.talosintelligence.com\",\n      rss_url: \"https://blog.talosintelligence.com/rss/\"\n    }\n  },\n  {\n    json: {\n      name: \"WeLiveSecurity (ESET)\",\n      website: \"https://www.welivesecurity.com\",\n      rss_url: \"https://feeds.feedburner.com/eset/blog\"\n    }\n  },\n  {\n    json: {\n      name: \"Graham Cluley Security Blog\",\n      website: \"https://grahamcluley.com\",\n      rss_url: \"https://grahamcluley.com/feed/\"\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "fbec32fd-2ca9-4f1f-84f9-e581bc6a37e5",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -120,
        3480
      ],
      "parameters": {
        "color": 7,
        "height": 100,
        "content": "### Update your email address or distribution list (DL) below\n\u2b07\ufe0f"
      },
      "typeVersion": 1
    },
    {
      "id": "4479495e-3e87-4a75-9fcc-36b230895854",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -140,
        4160
      ],
      "parameters": {
        "color": 7,
        "height": 100,
        "content": "### Update your email address or distribution list (DL) below\n\u2b07\ufe0f"
      },
      "typeVersion": 1
    },
    {
      "id": "18971219-b68d-4423-adbc-2f25174ae8a9",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -140,
        4720
      ],
      "parameters": {
        "color": 7,
        "height": 100,
        "content": "### Update your email address or distribution list (DL) below\n\u2b07\ufe0f"
      },
      "typeVersion": 1
    },
    {
      "id": "349ec0eb-9b5f-4bea-9b83-35d6cb63c1fa",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2320,
        3520
      ],
      "parameters": {
        "color": 7,
        "height": 80,
        "content": "### Update the RSS feed URL as needed to fetch content from your preferred source."
      },
      "typeVersion": 1
    },
    {
      "id": "4f3b0da6-4db7-41e9-aa0a-9bd084fce819",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2340,
        4180
      ],
      "parameters": {
        "color": 7,
        "height": 80,
        "content": "### Update the RSS feed URL as needed to fetch content from your preferred source."
      },
      "typeVersion": 1
    },
    {
      "id": "9c48eaa6-c602-44a3-871c-33ffd9dfa476",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2340,
        4740
      ],
      "parameters": {
        "color": 7,
        "height": 80,
        "content": "### Update the RSS feed URL as needed to fetch content from your preferred source."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "0dc52173-7378-4951-8ebc-e44443c39540",
  "connections": {
    "Privacy RSS Read": {
      "main": [
        [
          {
            "node": "Normalize Article Privacy Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Security RSS Read": {
      "main": [
        [
          {
            "node": "Normalize Article Security Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Security RSS": {
      "main": [
        [
          {
            "node": "Split Out Security RSS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compliance RSS Read": {
      "main": [
        [
          {
            "node": "Normalize Article Compliance Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Privacy Feeds": {
      "main": [
        [
          {
            "node": "Split Out Privacy RSS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trigger Daily Digest": {
      "main": [
        [
          {
            "node": "Fetch Security RSS",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Compliance Feeds",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Privacy Feeds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out Privacy RSS": {
      "main": [
        [
          {
            "node": "Privacy RSS Read",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Compliance Feeds": {
      "main": [
        [
          {
            "node": "Split Out Compliance RSS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out Security RSS": {
      "main": [
        [
          {
            "node": "Security RSS Read",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out Compliance RSS": {
      "main": [
        [
          {
            "node": "Compliance RSS Read",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent - Privacy Intelligence": {
      "main": [
        [
          {
            "node": "Privacy Build Final Newsletter HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LLM - Gemini Privacy Summarizer": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent - Privacy Intelligence",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Sort - Privacy Articles by Date": {
      "main": [
        [
          {
            "node": "Format Privacy Articles into HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent - Security Intelligence": {
      "main": [
        [
          {
            "node": "Security Build Final Newsletter HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LLM - Gemini Security Summarizer": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent - Security Intelligence",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Sort - Security Articles by Date": {
      "main": [
        [
          {
            "node": "Format Security Articles into HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Privacy Articles into HTML": {
      "main": [
        [
          {
            "node": "AI Agent - Privacy Intelligence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent - Compliance Intelligence": {
      "main": [
        [
          {
            "node": "Compliance Build Final Newsletter HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Security Articles into HTML": {
      "main": [
        [
          {
            "node": "AI Agent - Security Intelligence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LLM - Gemini Compliance Summarizer": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent - Compliance Intelligence",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Article Privacy Metadata": {
      "main": [
        [
          {
            "node": "Filter Recent Privacy Articles (24h)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort - Compliance Articles by Date": {
      "main": [
        [
          {
            "node": "Format Compliance Articles into HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Article Security Metadata": {
      "main": [
        [
          {
            "node": "Filter Recent Security Articles (24h)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Privacy Build Final Newsletter HTML": {
      "main": [
        [
          {
            "node": "Privacy Send Final Digest Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Recent Privacy Articles (24h)": {
      "main": [
        [
          {
            "node": "Sort - Privacy Articles by Date",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Compliance Articles into HTML": {
      "main": [
        [
          {
            "node": "AI Agent - Compliance Intelligence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Security Build Final Newsletter HTML": {
      "main": [
        [
          {
            "node": "Security Send Final Digest Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Recent Security Articles (24h)": {
      "main": [
        [
          {
            "node": "Sort - Security Articles by Date",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Article Compliance Metadata": {
      "main": [
        [
          {
            "node": "Filter Recent Compliance Articles (24h)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compliance Build Final Newsletter HTML": {
      "main": [
        [
          {
            "node": "Compliance Send Final Digest Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Recent Compliance Articles (24h)": {
      "main": [
        [
          {
            "node": "Sort - Compliance Articles by Date",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}