{
  "id": "ILoEiELklK8FtIKu",
  "name": "AI Web Scraper News Briefing Agent using Claude and Notion",
  "tags": [],
  "nodes": [
    {
      "id": "7d48c54f-2789-45d2-b37e-ffaafbc9fb13",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -752,
        80
      ],
      "parameters": {
        "color": 0,
        "width": 500,
        "height": 796,
        "content": "## AI Web Scraper News Briefing Agent using Claude and Notion\n\nThis workflow scrapes article URLs from Google Sheets every day, uses Claude to write a structured briefing for each article, saves each briefing to Notion, sends a Slack digest, and logs all results to a Google Sheets log.\n\n## How it works\n\n1. Schedule Trigger fires daily at your chosen time\n2. Configure Settings node defines all keys, IDs, and prompts in one place\n3. Google Sheets reads the list of article URLs to process\n4. HTTP Request fetches the raw text of each article URL\n5. Claude AI chain reads the content and writes a structured briefing: headline, summary, why it matters, and a relevance score\n6. Notion creates a new briefing page for each article\n7. Slack posts a daily digest of all briefings to your channel\n8. Google Sheets log appends every briefing for record keeping\n\n## Setup steps\n\n- [ ] **Google Sheets URL list** \u2014 Create a sheet with a column named url and paste article URLs one per row\n- [ ] **Anthropic credential** \u2014 Add your Anthropic API key in n8n credentials as an Anthropic Account\n- [ ] **Notion integration** \u2014 Add your Notion token and replace YOUR_NOTION_DATABASE_ID in the Configure Settings node\n- [ ] **Slack credential** \u2014 Add your Slack credentials and replace YOUR_SLACK_CHANNEL with your target channel\n- [ ] **Sheet IDs** \u2014 Replace YOUR_GOOGLE_SHEET_ID and YOUR_LOG_SHEET_ID in Configure Settings\n- [ ] **Schedule** \u2014 Open the Schedule Trigger and set your preferred time\n\n### Customization\n\nAdd more source URLs to the Google Sheets list at any time. Adjust the Claude prompt in Configure Settings to change the briefing format or focus area."
      },
      "typeVersion": 1
    },
    {
      "id": "9c5fe344-6021-41ae-b3a2-06bf476d2ad4",
      "name": "Section: Trigger and Config",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -144,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 420,
        "height": 508,
        "content": "## Trigger and config\n\nThe workflow starts on a daily schedule and reads all user-editable settings from the Configure Settings node. Edit only this node to change API keys, IDs, channels, and prompts."
      },
      "typeVersion": 1
    },
    {
      "id": "07ad6b22-e3c1-41e9-aab6-0645ef210b31",
      "name": "Section: Scrape and Analyse",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        336,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 1352,
        "height": 700,
        "content": "## Scrape and analyse\n\nReads article URLs from Google Sheets, fetches the page content of each URL, then passes each article to Claude to generate a structured briefing with headline, summary, relevance score, and key insight."
      },
      "typeVersion": 1
    },
    {
      "id": "770e78fe-be57-4341-92b8-e76db118d032",
      "name": "Section: Store and Notify",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1776,
        -32
      ],
      "parameters": {
        "color": 7,
        "width": 1012,
        "height": 716,
        "content": "## Store and notify\n\nSaves each Claude briefing as a new Notion page in your briefings database, then sends a Slack digest of all articles and logs every record to Google Sheets for long-term storage."
      },
      "typeVersion": 1
    },
    {
      "id": "6b6edad2-7680-4b3e-8bf6-99ecb8e079e9",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -80,
        288
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 7 * * 1-5"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "26384f26-76bc-404e-b9eb-50d3e6172665",
      "name": "Configure Settings",
      "type": "n8n-nodes-base.code",
      "position": [
        144,
        288
      ],
      "parameters": {
        "jsCode": "const config = {\n  googleSheetId: 'YOUR_GOOGLE_SHEET_ID',\n  googleSheetName: 'urls',\n  logSheetId: 'YOUR_LOG_SHEET_ID',\n  logSheetName: 'log',\n  notionDatabaseId: 'YOUR_NOTION_DATABASE_ID',\n  slackChannel: 'YOUR_SLACK_CHANNEL',\n  maxArticles: 10,\n  briefingPrompt: 'You are a professional news analyst. Read the article text provided and write a structured briefing. Return only a JSON object with these exact keys: headline (string, max 12 words), summary (string, 2 sentences), why_it_matters (string, 1 sentence), relevance_score (integer 1-10). Do not include any text outside the JSON object.',\n  digestIntro: 'Daily AI news briefing is ready. Here are the top stories for today.'\n};\n\nreturn [{ json: config }];"
      },
      "typeVersion": 2
    },
    {
      "id": "ffcf3ca6-7318-4fed-a7a6-9513817325d1",
      "name": "Read URL List from Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        384,
        288
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $json.googleSheetName }}"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.googleSheetId }}"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "99f4b456-2b23-4baf-99c8-7d111b6c543f",
      "name": "Limit to Max Articles",
      "type": "n8n-nodes-base.limit",
      "position": [
        608,
        288
      ],
      "parameters": {
        "maxItems": 10
      },
      "typeVersion": 1
    },
    {
      "id": "35c056a4-fc69-4d52-afed-2d4923a4b0a9",
      "name": "Fetch Article Content",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        816,
        288
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "options": {
          "timeout": 15000,
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "d7637555-b160-48e6-934b-08e37d95572c",
      "name": "Prepare Article Text",
      "type": "n8n-nodes-base.code",
      "position": [
        1040,
        288
      ],
      "parameters": {
        "jsCode": "const rawHtml = $input.item.json.data || '';\nconst url = $input.item.json.url || $('Read URL List from Sheets').item.json.url || '';\n\n// Strip HTML tags without regex lookbehind\nconst noScript = rawHtml.split('<script').map(function(p, i) { return i === 0 ? p : p.split('</script>').slice(1).join('</script>'); }).join('');\nconst noStyle = noScript.split('<style').map(function(p, i) { return i === 0 ? p : p.split('</style>').slice(1).join('</style>'); }).join('');\nconst noTags = noStyle.replace(/<[^>]+>/g, ' ');\nconst decoded = noTags.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '\"').replace(/&#39;/g, \"'\").replace(/&nbsp;/g, ' ');\nconst clean = decoded.replace(/\\s+/g, ' ').trim();\nconst truncated = clean.length > 4000 ? clean.slice(0, 4000) + '...' : clean;\n\nreturn [{\n  json: {\n    url: url,\n    articleText: truncated,\n    charCount: truncated.length\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "48d1fe53-cfb4-47ea-b19c-664c20b79778",
      "name": "Claude AI Chain",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        1200,
        288
      ],
      "parameters": {
        "text": "={{ 'You are a professional news analyst. Read the article text below and write a structured briefing. Return ONLY a valid JSON object with these exact keys: headline (string, max 12 words), summary (string, 2 sentences), why_it_matters (string, 1 sentence), relevance_score (integer 1-10). Do not add any text outside the JSON object.\\n\\nARTICLE TEXT:\\n' + $json.articleText }}",
        "promptType": "define"
      },
      "typeVersion": 1.4
    },
    {
      "id": "6043bf3a-6f6d-4194-b29b-08abba5149ad",
      "name": "Claude Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        1232,
        496
      ],
      "parameters": {
        "model": "claude-sonnet-4-6",
        "options": {
          "temperature": 0.3
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "ed4c8236-7f7e-4c66-ade1-2d3d0a930236",
      "name": "Parse Claude Briefing",
      "type": "n8n-nodes-base.code",
      "position": [
        1504,
        272
      ],
      "parameters": {
        "jsCode": "const raw = $input.item.json.text || '';\nconst url = $('Prepare Article Text').item.json.url || '';\n\nlet briefing = { headline: 'No headline', summary: '', why_it_matters: '', relevance_score: 5 };\n\ntry {\n  const startBrace = raw.indexOf('{');\n  const endBrace = raw.lastIndexOf('}');\n  if (startBrace !== -1 && endBrace !== -1) {\n    const jsonStr = raw.slice(startBrace, endBrace + 1);\n    briefing = JSON.parse(jsonStr);\n  }\n} catch (e) {\n  briefing.summary = 'Could not parse Claude response';\n}\n\nreturn [{\n  json: {\n    url: url,\n    headline: briefing.headline || 'No headline',\n    summary: briefing.summary || '',\n    why_it_matters: briefing.why_it_matters || '',\n    relevance_score: briefing.relevance_score || 5,\n    processedAt: new Date().toISOString()\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "95d7bf29-2144-4e70-b9f6-d4638a897c75",
      "name": "Save to Notion",
      "type": "n8n-nodes-base.notion",
      "position": [
        1936,
        240
      ],
      "parameters": {
        "title": "={{ $json.headline }}",
        "pageId": {
          "__rl": true,
          "mode": "url",
          "value": ""
        },
        "options": {}
      },
      "typeVersion": 2.2
    },
    {
      "id": "5f6a3984-5929-4fbf-a4f4-bb8757694852",
      "name": "Log to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2160,
        240
      ],
      "parameters": {
        "columns": {
          "value": {},
          "mappingMode": "autoMapInputData"
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "log"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_LOG_SHEET_ID"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "e4f7c8eb-6b10-4ad1-a705-ef0eaf8da009",
      "name": "Aggregate Briefings",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        2160,
        432
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "bd03a76a-b7c9-4515-9c00-28ef5dcdeb68",
      "name": "Build Slack Digest",
      "type": "n8n-nodes-base.code",
      "position": [
        2384,
        432
      ],
      "parameters": {
        "jsCode": "const items = $input.item.json.data || [];\nconst today = new Date().toDateString();\n\nconst lines = items.map(function(item, i) {\n  const rank = i + 1;\n  const score = item.relevance_score || '?';\n  const hl = item.headline || 'No headline';\n  const summ = item.summary || '';\n  const url = item.url || '';\n  return rank + '. [Score: ' + score + '/10] ' + hl + ' -- ' + summ + ' Source: ' + url;\n});\n\nconst message = 'Daily AI News Briefing | ' + today + '\\n' + lines.join('\\n\\n');\n\nreturn [{ json: { message: message, count: items.length } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "d83466de-00e9-4a9d-bbea-bae4127b824f",
      "name": "Send Slack Digest",
      "type": "n8n-nodes-base.slack",
      "position": [
        2608,
        432
      ],
      "parameters": {
        "text": "={{ $json.message }}",
        "otherOptions": {}
      },
      "typeVersion": 2.2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "f28cd6a1-6f53-4d32-911f-3bc05821fd44",
  "connections": {
    "Claude Model": {
      "ai_languageModel": [
        [
          {
            "node": "Claude AI Chain",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Save to Notion": {
      "main": [
        [
          {
            "node": "Log to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude AI Chain": {
      "main": [
        [
          {
            "node": "Parse Claude Briefing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Configure Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Slack Digest": {
      "main": [
        [
          {
            "node": "Send Slack Digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure Settings": {
      "main": [
        [
          {
            "node": "Read URL List from Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Briefings": {
      "main": [
        [
          {
            "node": "Build Slack Digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Article Text": {
      "main": [
        [
          {
            "node": "Claude AI Chain",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Article Content": {
      "main": [
        [
          {
            "node": "Prepare Article Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Limit to Max Articles": {
      "main": [
        [
          {
            "node": "Fetch Article Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Claude Briefing": {
      "main": [
        [
          {
            "node": "Save to Notion",
            "type": "main",
            "index": 0
          },
          {
            "node": "Aggregate Briefings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read URL List from Sheets": {
      "main": [
        [
          {
            "node": "Limit to Max Articles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}