{
  "id": "",
  "name": "Automated News Newsletter powered by Bright Data",
  "tags": [],
  "nodes": [
    {
      "id": "5992efd8-9c4d-493a-94bd-a39f9887e2eb",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -608,
        -304
      ],
      "parameters": {
        "width": 480,
        "height": 864,
        "content": "## Automated News Newsletter powered by Bright Data\n\n### How it works\n\nThis workflow runs on a schedule to build an automated news newsletter. It uses Bright Data to scrape Google News, filters the results to the top five stories, then loops through each story to scrape and clean the full article content. After aggregating the cleaned articles, it asks Claude to write a newsletter and sends the final result via Gmail.\n\n### Setup steps\n\n- Configure the Schedule Trigger with the desired newsletter frequency.\n- Add Bright Data credentials and configure the Google News scrape target or query in the Bright Data nodes.\n- Add Anthropic credentials for the Claude newsletter-writing node and verify the prompt matches the desired newsletter style.\n- Connect Gmail credentials and configure the sender, recipient list, subject line, and email body mapping.\n\n### Customization\n\nAdjust the Google News query, the number of stories selected in the filter code, the article-cleaning logic, Claude's writing prompt, and the Gmail recipients or subject line to match the target audience."
      },
      "typeVersion": 1
    },
    {
      "id": "c02b0735-eed3-4cbb-8f24-362b1ae58feb",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 816,
        "height": 304,
        "content": "## Schedule and select stories\n\nStarts the workflow on a schedule, scrapes Google News with Bright Data, filters the result down to the top five stories, and initializes batch processing for each story."
      },
      "typeVersion": 1
    },
    {
      "id": "cb033b18-d636-4723-8e8f-c9f0c42a99ed",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        880,
        64
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 496,
        "content": "## Scrape article content\n\nFor each selected story, fetches the full article through Bright Data and normalizes the returned content into a clean format before sending control back to the story loop."
      },
      "typeVersion": 1
    },
    {
      "id": "d0a5c922-5f0c-4897-8533-2fbf08e8897e",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1088,
        -304
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 336,
        "content": "## Generate newsletter draft\n\nCollects all cleaned article content after the loop completes and sends the aggregated material to Claude to write the newsletter."
      },
      "typeVersion": 1
    },
    {
      "id": "9af25b69-66fc-464d-8ced-2b6ce3a987b0",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1632,
        -176
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 320,
        "content": "## Send final newsletter\n\nEmails the generated newsletter through Gmail to the configured recipients."
      },
      "typeVersion": 1
    },
    {
      "id": "c4270d50-be44-4f68-9b85-2435cca03fbc",
      "name": "When Week Starts",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        0,
        0
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ]
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "1bf2bab6-b236-4a8c-85d0-399389413d6d",
      "name": "Extract Google News",
      "type": "@brightdata/n8n-nodes-brightdata.brightData",
      "position": [
        208,
        0
      ],
      "parameters": {
        "url": "https://www.google.com/search?q=AI+automation&tbm=nws&gl=us&hl=en&brd_json=1",
        "zone": {
          "__rl": true,
          "mode": "list",
          "value": "n8n_unlocker"
        },
        "country": {
          "__rl": true,
          "mode": "list",
          "value": "us"
        },
        "requestOptions": {}
      },
      "credentials": {
        "brightdataApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8567ccda-8df4-46d1-aa8f-12f7e64b5468",
      "name": "Select Top 5 News Stories",
      "type": "n8n-nodes-base.code",
      "position": [
        416,
        0
      ],
      "parameters": {
        "jsCode": "// Get the news array from the Bright Data node\nconst news = $input.first().json.news || [];\n\n// Filter out YouTube videos, keep only real news\nconst filtered = news.filter(item => {\n  const link = (item.link || \"\").toLowerCase();\n  const isVideo = link.includes(\"youtube.com\") || link.includes(\"youtu.be\");\n  return !isVideo && item.title && item.link;\n});\n\n// Dedupe by title\nconst seen = new Set();\nconst deduped = filtered.filter(item => {\n  if (seen.has(item.title)) return false;\n  seen.add(item.title);\n  return true;\n});\n\n// Keep top 5\nconst top5 = deduped.slice(0, 5);\n\n// Return each story as a separate item (for the loop ahead)\nreturn top5.map(item => ({ json: item }));"
      },
      "typeVersion": 2
    },
    {
      "id": "9f5c9a8b-15d1-4ab6-86fd-26a466743031",
      "name": "Loop Over News Stories",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        624,
        0
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "8f3cee52-99b4-4463-84a0-fdbc4454f741",
      "name": "Fetch Article Content",
      "type": "@brightdata/n8n-nodes-brightdata.brightData",
      "position": [
        928,
        240
      ],
      "parameters": {
        "url": "={{ $json.link }}",
        "zone": {
          "__rl": true,
          "mode": "list",
          "value": "n8n_unlocker"
        },
        "country": {
          "__rl": true,
          "mode": "list",
          "value": "us"
        },
        "data_format": "markdown",
        "requestOptions": {}
      },
      "credentials": {
        "brightdataApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "4581608c-ac48-41e8-b696-b77607db0027",
      "name": "Sanitize Article Content",
      "type": "n8n-nodes-base.code",
      "position": [
        1152,
        400
      ],
      "parameters": {
        "jsCode": "// Web Unlocker may return [\"markdown...\"], a string, or an object\nlet raw = $input.first().json;\n\nlet content = \"\";\nif (Array.isArray(raw)) {\n  content = raw[0] || \"\";\n} else if (typeof raw === \"string\") {\n  content = raw;\n} else {\n  // Object: take the longest string found\n  for (const key in raw) {\n    if (typeof raw[key] === \"string\" && raw[key].length > content.length) {\n      content = raw[key];\n    }\n    // Sometimes it's raw.data[0] or similar\n    if (Array.isArray(raw[key]) && typeof raw[key][0] === \"string\") {\n      if (raw[key][0].length > content.length) content = raw[key][0];\n    }\n  }\n}\n\n// Try to drop everything before the real article start\n// Many sites put the title as \"# \" then the body \u2014 keep the last H1 section\nconst h1Split = content.split(/\\n# /);\nif (h1Split.length > 1) {\n  content = \"# \" + h1Split[h1Split.length - 1];\n}\n\n// Strip markdown links [text](url) \u2192 keep the text\ncontent = content.replace(/\\[([^\\]]*)\\]\\([^)]*\\)/g, \"$1\");\n// Strip images ![...](...)\ncontent = content.replace(/!\\[[^\\]]*\\]\\([^)]*\\)/g, \"\");\n\n// Keep only real text lines (> 50 chars)\nlet lines = content.split(\"\\n\").map(l => l.trim()).filter(l => l.length > 50);\nlet cleaned = lines.join(\"\\n\\n\").slice(0, 2500);\n\nreturn [{\n  json: {\n    title: $('Loop Over News Stories').item.json.title,\n    link: $('Loop Over News Stories').item.json.link,\n    source: $('Loop Over News Stories').item.json.source,\n    content: cleaned\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "5cf7d631-ed3c-4e4a-81af-d183c8f0d13e",
      "name": "Compile All Articles",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        1136,
        -128
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "f0fec1de-3d66-4e3f-94b7-a9c112abfe38",
      "name": "Compose Newsletter with Claude",
      "type": "@n8n/n8n-nodes-langchain.anthropic",
      "position": [
        1328,
        -128
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "claude-opus-4-8",
          "cachedResultName": "claude-opus-4-8"
        },
        "options": {
          "system": "=You are a newsletter editor. You write concise, engaging newsletters from industry news articles.\n\nFor each article you receive, write:\n- A bold headline (use the article title)\n- Two or three sentences summarizing the key points IN YOUR OWN WORDS, based on the article content provided\n- A \"Read more\" link using the article's link\n\nFormat the whole thing as clean HTML ready to drop into an email: <h2> for the newsletter title at the top, <h3> for each headline, <p> for summaries, <a> for the links. Start with a short one-sentence intro before the first story.\n\nOutput ONLY the HTML. No markdown code fences, no preamble, no explanation."
        },
        "messages": {
          "values": [
            {
              "content": "=Here are today's top 5 industry news articles. Write the newsletter.\n\n{{ JSON.stringify($json.data.map(a => ({ title: a.title, link: a.link, source: a.source, content: a.content }))) }}"
            }
          ]
        }
      },
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "120f68c8-9057-4481-a77f-5d205405a585",
      "name": "Email Newsletter via Gmail",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1680,
        -16
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "={{ $json.content[0].text }}",
        "options": {},
        "subject": "Your AI Industry Brief"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "",
  "nodeGroups": [],
  "connections": {
    "When Week Starts": {
      "main": [
        [
          {
            "node": "Extract Google News",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Google News": {
      "main": [
        [
          {
            "node": "Select Top 5 News Stories",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compile All Articles": {
      "main": [
        [
          {
            "node": "Compose Newsletter with Claude",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Article Content": {
      "main": [
        [
          {
            "node": "Sanitize Article Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over News Stories": {
      "main": [
        [
          {
            "node": "Compile All Articles",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Fetch Article Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sanitize Article Content": {
      "main": [
        [
          {
            "node": "Loop Over News Stories",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select Top 5 News Stories": {
      "main": [
        [
          {
            "node": "Loop Over News Stories",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compose Newsletter with Claude": {
      "main": [
        [
          {
            "node": "Email Newsletter via Gmail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}