This workflow corresponds to n8n.io template #13818 — we link there as the canonical source.
This workflow follows the Chainllm → Google Sheets recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "Oe9Mqmy5E2qtce6V",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Sentiment & Trend Radar Monitoring",
"tags": [],
"nodes": [
{
"id": "4eb997f2-e6bc-47ad-82a8-379c37e49b30",
"name": "Schedule Trigger (Daily 8AM)",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
9552,
3408
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"hoursInterval": 24
}
]
}
},
"typeVersion": 1.2
},
{
"id": "038f79fe-d0eb-4e11-9d7f-7625888eb167",
"name": "Loop Each News Source",
"type": "n8n-nodes-base.splitInBatches",
"position": [
10000,
3408
],
"parameters": {
"options": {
"reset": false
}
},
"typeVersion": 3
},
{
"id": "6c52dfa0-0381-42cb-bf42-1d573228b2d1",
"name": "MrScraper - Discover Article URLs",
"type": "n8n-nodes-mrscraper.mrscraper",
"position": [
10224,
3280
],
"parameters": {
"url": "=// Input Your url (required)",
"limit": 10,
"operation": "mapAgent",
"scraperId": "=// Input Your scraperId from mrscraper (required)",
"requestOptions": {},
"excludePatterns": "// Input Your Exclude Pattern (Optional)",
"includePatterns": "// Input Your Include Pattern (optional)"
},
"credentials": {
"mrscraperApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 1,
"waitBetweenTries": 3000
},
{
"id": "be2601e1-2ee2-4024-b31e-12a6a95ce483",
"name": "Loop Each Article URL",
"type": "n8n-nodes-base.splitInBatches",
"position": [
10672,
3392
],
"parameters": {
"options": {
"reset": false
}
},
"typeVersion": 3
},
{
"id": "623e9df1-880e-4775-8793-4822f1551199",
"name": "MrScraper - Extract Article Content",
"type": "n8n-nodes-mrscraper.mrscraper",
"position": [
10928,
3280
],
"parameters": {
"url": "=// Input Your url (required)",
"operation": "generalAgent",
"scraperId": "=// Input Your scraperId from Mrscraper (required)",
"requestOptions": {}
},
"credentials": {
"mrscraperApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 1,
"continueOnFail": true
},
{
"id": "d125b8df-1d90-4976-a6f2-62b2d631d193",
"name": "Pick Best Content + Filter Irrelevant",
"type": "n8n-nodes-base.code",
"position": [
11152,
3280
],
"parameters": {
"jsCode": "// Get input\nconst item = $input.item.json;\n\n// Handle possible nesting:\n// item.json.data.data\n// item.json.data.data.data\nlet rootData =\n item?.data?.data?.data ||\n item?.data?.data ||\n item?.data ||\n {};\n\n// Extract main fields\nconst title = rootData.title || rootData.headline || '';\nconst article =\n rootData.article ||\n rootData.content ||\n rootData.text ||\n rootData.body ||\n rootData.description ||\n '';\n\n// Calculate wordCount (like your example)\nconst wordCount =\n article\n ?.split(/\\s+/)\n .filter(w => w.length > 0).length || 0;\n\n// Inject wordCount into rootData (like isPaywalled)\nrootData.wordCount = wordCount;\n\n// Combine text for relevance checking\nconst combinedText = (title + ' ' + article).toLowerCase();\n\n// You can replace this with dynamic keywords if needed\nconst TopicBrandKeywords = ['Hukum', 'iran', 'education']; \n\nconst isRelevant = TopicBrandKeywords.some(keyword =>\n combinedText.includes(keyword.toLowerCase())\n);\n\n// Skip if not relevant OR too short\nif (!isRelevant || wordCount < 50) {\n return [{\n json: {\n skip: true,\n reason: !isRelevant\n ? 'Not relevant to topic/brand'\n : 'Content too short',\n url: rootData.url || item?.data?.url || '',\n wordCount\n }\n }];\n}\n\n// Return enriched rootData\nreturn [{\n json: {\n ...rootData,\n wordCount,\n skip:false\n }\n}];"
},
"typeVersion": 2
},
{
"id": "c7ee7399-a11c-459a-a786-9d4e378b2134",
"name": "Keep Only Relevant Articles",
"type": "n8n-nodes-base.if",
"position": [
11344,
3280
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "filter-001",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.skip }}",
"rightValue": "100"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "3ae96703-6dbb-49e3-a25f-bb841d2275d5",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
11584,
3424
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "gpt-4o-mini"
},
"options": {},
"builtInTools": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "b217216c-e5c8-49e3-bdbf-ab96aa1641cd",
"name": "Sentiment & Summary AI Agent",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
11568,
3264
],
"parameters": {
"text": "=Analyze the following news article about the topic / brand \"Law\".\n\nArticle Title: {{ $json.title }}\nPublished: {{ $json.publishedAt }}\nAuthor: {{ $json.author }}\nSource URL: {{ $json.sourceUrl }}\n\nArticle Content:\n{{ $json.content }}\n\nReturn your result in this exact JSON structure:\n\n{\n\"title\",\n\"published_date\",\n\"author\",\n\"source_url\",\n\"summary\",\n\"sentiment\": \"positive | neutral | negative\",\n\"sentiment_score\": number,\n\"summary\": \"string (max 3 sentences)\",\n\"key_topics\": [\"law\"],\n\"tone\": \"formal | critical | alarming | investigative | neutral\",\n\"action_required\": true or false,\n\"action_reason\": \"string\",\n\"quote\": \"string (most important sentence from article)\"\n}\n\nRemember:\n\nOutput must be valid JSON only.\n\nAll fields are required.\n\nNever return null.\n\nNever return markdown.",
"messages": {
"messageValues": [
{
"message": "You are a news sentiment analysis engine. You MUST return ONLY valid JSON. Do NOT include markdown. Do NOT include explanations. Do NOT wrap the response in backticks. Do NOT add extra text. Always return all fields in the schema. If information is missing, use default values. Sentiment rules: positive \u2192 good news, legal success, improvement negative \u2192 scandal, legal case, corruption, controversy, crime escalation neutral \u2192 factual, informational, balanced Sentiment score rules: Range from -1 to 1 Negative = below 0 Neutral = 0 -elopen Positive = above 0"
}
]
},
"promptType": "define"
},
"typeVersion": 1.5
},
{
"id": "a7f6a4ec-77fd-468f-a908-cef3041466c1",
"name": "Parse AI Response & Format Output",
"type": "n8n-nodes-base.code",
"position": [
11920,
3264
],
"parameters": {
"jsCode": "// Parse the AI response and merge with article metadata\nconst item = $input.item.json;\nconst articleMeta = $('Keep Only Relevant Articles').item.json;\n\nlet analysis = {};\ntry {\n const raw = item.text || item.response || item.output || '';\n // Strip any accidental markdown fences\n const cleaned = raw.replace(/```json|```/g, '').trim();\n analysis = JSON.parse(cleaned);\n} catch (e) {\n analysis = {\n sentiment: 'neutral',\n sentiment_score: 0,\n summary: 'Could not parse AI response.',\n key_topics: [],\n brand_mentions: 0,\n tone: 'neutral',\n action_required: false,\n action_reason: '',\n quote: ''\n };\n}\n\n// Sanitise types\nconst sentiment = String(analysis.sentiment || 'neutral').toLowerCase();\nconst sentiment_score = parseFloat(analysis.sentiment_score) || 0;\nconst action_required = analysis.action_required === true || analysis.action_required === 'true';\n\n// Emoji map for sentiment\nconst emojiMap = { positive: '\ud83d\udfe2', neutral: '\ud83d\udfe1', negative: '\ud83d\udd34' };\nconst sentimentEmoji = emojiMap[sentiment] || '\u26aa';\n\nconst sentimentUpper = sentiment.toUpperCase();\n\nreturn [{\n json: {\n // Article info\n title: articleMeta.title,\n sourceUrl: articleMeta.sourceUrl,\n publishedAt: articleMeta.publishedAt,\n author: articleMeta.author,\n brandName: articleMeta.brandName,\n runDate: articleMeta.runDate,\n slackChannel: articleMeta.slackChannel,\n notionDatabaseId: articleMeta.notionDatabaseId,\n // AI analysis\n sentiment,\n sentiment_score,\n sentimentEmoji,\n summary: analysis.summary || '',\n key_topics: (analysis.key_topics || []).join(', '),\n tone: analysis.tone || 'neutral',\n action_required,\n action_reason: analysis.action_reason || '',\n quote: analysis.quote || '',\n // Formatted for Slack \u2014 uses sentimentUpper variable (no Jinja filter)\n slackMessage: `${sentimentEmoji} *${sentimentUpper}* | ${articleMeta.title}\\n${analysis.summary}\\n\ud83d\udd17 ${articleMeta.sourceUrl}`\n }\n}];"
},
"typeVersion": 2
},
{
"id": "a05e5f63-b8a6-4147-8114-a92962937eec",
"name": "Needs Urgent Alert?",
"type": "n8n-nodes-base.if",
"position": [
12144,
3296
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "urgent-001",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.action_required }}"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "420367fa-cdc7-4691-b593-356f4f963bef",
"name": "Slack - Urgent Alert",
"type": "n8n-nodes-base.slack",
"position": [
12592,
3248
],
"parameters": {
"text": "=\ud83d\udea8 *URGENT BRAND ALERT* \u2014 Action Required\n\n{{ $json.sentimentEmoji }} *Sentiment:* {{ $json.sentiment.toUpperCase() }} (Score: {{ $json.sentiment_score }})\n*Article:* {{ $json.title }}\n*Tone:* {{ $json.tone }}\n\n*Summary:*\n{{ $json.summary }}\n\n*Why action is needed:*\n{{ $json.action_reason }}\n\n*Key Topics:* {{ $json.key_topics }}\n*Quote:* _\"{{ $json.quote }}\"_\n\n\ud83d\udd17 <{{ $json.sourceUrl }}|Read full article>\n_Scraped: {{ $json.publishedAt }}_",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "=// input your channel here"
},
"otherOptions": {
"mrkdwn": true
}
},
"typeVersion": 2.3
},
{
"id": "c6987d4c-dcd0-4b2a-b3e3-fa098ea44206",
"name": "Notion - Save Article Analysis",
"type": "n8n-nodes-base.notion",
"position": [
12752,
3360
],
"parameters": {
"title": "={{ $json.title }}",
"options": {},
"resource": "databasePage",
"databaseId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.notionDatabaseId }}"
},
"propertiesUi": {
"propertyValues": [
{
"key": "Source URL",
"urlValue": "={{ $json.sourceUrl }}"
},
{
"key": "Sentiment",
"selectValue": "={{ $json.sentiment }}"
},
{
"key": "Sentiment Score",
"numberValue": "={{ $json.sentiment_score }}"
},
{
"key": "Tone",
"selectValue": "={{ $json.tone }}"
},
{
"key": "Key Topics"
},
{
"key": "Summary"
},
{
"key": "Brand Mentions",
"numberValue": "={{ $json.brand_mentions }}"
},
{
"key": "Action Required",
"checkboxValue": "={{ $json.action_required }}"
},
{
"key": "Action Reason"
},
{
"key": "Published At"
},
{
"key": "Scraped At"
}
]
}
},
"typeVersion": 2.2,
"continueOnFail": true
},
{
"id": "450b484b-451b-4c7b-8926-6bc91914f62d",
"name": "Build Daily Digest Summary",
"type": "n8n-nodes-base.code",
"position": [
12288,
3392
],
"parameters": {
"jsCode": "// Aggregate all processed articles into a single daily digest\nconst allItems = $input.all();\n\nif (!allItems || allItems.length === 0) {\n return [{\n json: {\n digestMessage: `\ud83d\udcf0 *Daily Sentiment Report*\\n\\nNo articles were processed today.`,\n total: 0,\n counts: { positive: 0, neutral: 0, negative: 0 },\n avgScore: \"0.00\",\n actionCount: 0\n }\n }];\n}\n\nconst articles = allItems.map(i => i.json);\n\n// Count sentiments\nconst counts = { positive: 0, neutral: 0, negative: 0 };\nfor (const a of articles) {\n const s = (a.sentiment || \"neutral\").toLowerCase();\n if (counts[s] !== undefined) {\n counts[s]++;\n }\n}\n\nconst total = articles.length;\n\nconst avgScore = (\n articles.reduce((sum, a) => sum + (parseFloat(a.sentiment_score) || 0), 0) \n / total\n).toFixed(2);\n\nconst actionItems = articles.filter(a => a.action_required === true);\n\n// Use current date automatically\nconst runDate = new Date().toISOString();\n\n// Build Slack digest\nlet digest = `\ud83d\udcf0 *Daily Sentiment Report*\\n`;\ndigest += `\ud83d\udcc5 ${runDate.slice(0, 10)}\\n\\n`;\ndigest += `*Overview:* ${total} articles analyzed\\n`;\ndigest += `\ud83d\udfe2 Positive: ${counts.positive} | \ud83d\udfe1 Neutral: ${counts.neutral} | \ud83d\udd34 Negative: ${counts.negative}\\n`;\ndigest += `\ud83d\udcca Avg Sentiment Score: ${avgScore}\\n\\n`;\n\nif (actionItems.length > 0) {\n digest += `\u26a0\ufe0f *${actionItems.length} item(s) need attention:*\\n`;\n for (const a of actionItems) {\n digest += `\u2022 ${a.title} \u2014 ${a.action_reason}\\n`;\n digest += ` \ud83d\udd17 ${a.sourceUrl}\\n`;\n }\n digest += \"\\n\";\n}\n\ndigest += `*Top Articles Today:*\\n`;\nfor (const a of articles.slice(0, 5)) {\n const summaryPreview = (a.summary || \"\").slice(0, 120);\n digest += `${a.sentimentEmoji || \"\u26aa\"} ${a.title}\\n`;\n digest += ` _${summaryPreview}..._\\n`;\n digest += ` \ud83d\udd17 ${a.sourceUrl}\\n\\n`;\n}\n\nreturn [{\n json: {\n digestMessage: digest,\n total,\n counts,\n avgScore,\n actionCount: actionItems.length,\n runDate\n }\n}];"
},
"typeVersion": 2
},
{
"id": "98e87aff-dc32-4d11-b24b-38e6be975ca5",
"name": "Slack - Daily Digest",
"type": "n8n-nodes-base.slack",
"position": [
12512,
3392
],
"parameters": {
"text": "={{ $json.digestMessage }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "={{ $json.slackChannel }}"
},
"otherOptions": {
"mrkdwn": true
}
},
"typeVersion": 2.3
},
{
"id": "c1dcafb0-0e37-455b-b676-62c1c306c74e",
"name": "Sticky Note - Setup",
"type": "n8n-nodes-base.stickyNote",
"position": [
8704,
3232
],
"parameters": {
"width": 720,
"height": 1104,
"content": "## Phase 0 \u2014 Setup & Configuration\n\n### Before Running This Workflow\n\n**A) MrScraper Setup (required)**\nCreate TWO scrapers in your MrScraper dashboard:\n\n1. **Map Agent Scraper** \u2014 discovers article URLs from news/search pages\n - Copy its `scraperId` \u2192 paste into `mapScraperId` in Workflow Config\n\n2. **General Agent Scraper** \u2014 extracts full article content (title, body, date, author)\n - Target fields: `title`, `content` or `text` or `markdown`, `published_at`, `author`\n - Copy its `scraperId` \u2192 paste into `generalScraperId` in Workflow Config\n3. **Enable AI Scraper API access** in your MrScraper account (so n8n can run scrapers via API).\n\n\n**B) Credentials Needed in n8n**\n- `MrScraper` API credential \u2192 connect to both MrScraper nodes.\n- `OpenAI` API credential \u2192 connect to OpenAI Chat Model.\n- `Slack` OAuth credential \u2192 connect to both Slack nodes.\n- `Notion` OAuth credential \u2192 connect to Notion node.\n\n**C) Workflow Config Node \u2014 Fill in these values:**\n- `brandName`: your brand (e.g. `Anthropic`)\n- `brandKeywords`: comma-separated keywords (e.g. `Anthropic, Claude, claude.ai`)\n- `newsUrls`: comma-separated news search URLs to scrape\n- `mapScraperId` + `generalScraperId`: from MrScraper\n- `slackChannel`: Slack channel name (e.g. `#brand-monitoring`)\n- `notionDatabaseId`: your Notion database ID (from the URL)\n- `maxArticles`: max articles to analyze per run (default: 10)\n\n**D) Notion Database Setup**\nCreate a Notion database with these properties (optional):\n- `Title` (title)\n- `Source URL` (URL)\n- `Sentiment` (select: positive / neutral / negative)\n- `Sentiment Score` (number)\n- `Tone` (select)\n- `Key Topics` (rich text)\n- `Summary` (rich text)\n- `Brand Mentions` (number)\n- `Action Required` (checkbox)\n- `Action Reason` (rich text)\n- `Published At` (rich text)\n- `Scraped At` (rich text)"
},
"typeVersion": 1
},
{
"id": "a886e19a-e2cf-4844-bd79-11600c3e7267",
"name": "Sticky Note - Phase 1",
"type": "n8n-nodes-base.stickyNote",
"position": [
9504,
3584
],
"parameters": {
"color": 4,
"width": 432,
"height": 336,
"content": "### What To Do\n* Set the workflow to run **daily at 8:00 AM** (or adjust to your preferred time).\n* Configure all global variables:\n brand name, keywords, scraper IDs,\n Slack channel, and Notion database ID.\n* Provide the list of news source URLs.\n* Ensure the URLs are split into individual items\n so they can be processed in parallel.\n\n### Key Settings to Change\n* **Trigger time** \u2192 edit the *Schedule* node.\n* **Brand & integration config** \u2192 edit the **Workflow Config** node.\n"
},
"typeVersion": 1
},
{
"id": "c3d30579-51ce-43c3-a5ac-d0794fbeb6ed",
"name": "Sticky Note - Phase 2",
"type": "n8n-nodes-base.stickyNote",
"position": [
9952,
3568
],
"parameters": {
"color": 2,
"width": 880,
"height": 384,
"content": "### What To Do\n1. Run **Map Agent (Rerun)** using your domain as input (Please enter the maximum depth, maximum pages, and include pattern (includePattern) as needed.)\n2. Extract and clean the URLs:\n * Remove tag, category, and author pages\n * Remove non-http(s) links\n * Deduplicate results\n * Limit the output to `maxArticles` per run\n3. Add a fallback condition:\n * If no valid URLs are found, return a *skip item* instead of stopping the workflow.\n\n### Tips\n* Use Google News search URLs for broader coverage, for example:\n [https://news.google.com/search?q=YOUR_BRAND](https://news.google.com/search?q=YOUR_BRAND) or YOUR_TOPIC\n* You can also define specific URL patterns from the target news site to avoid collecting unwanted links.\n* Adjust `maxArticles` to balance speed and processing cost.\n"
},
"typeVersion": 1
},
{
"id": "a0749d5e-270f-4144-afbe-f6029968c9a2",
"name": "Sticky Note - Phase 3",
"type": "n8n-nodes-base.stickyNote",
"position": [
10864,
3568
],
"parameters": {
"color": 5,
"width": 640,
"height": 416,
"content": "### What To Do\n1. For each URL, call the **General Agent (Rerun)** using the scraperId from the mrscraper platform that was previously created to scrape each article URL\n and extract the full content (title, body, date, author).\n2. You can adjust TopicBrandKeywords to ensure the article is relevant to your topic / brand\n3. Skip any article with fewer than 50 word.\n4. Skip any article that does not mention your defined brand keywords.\n5. Ensure all `skip=true` items continue through the workflow safely (do not break execution).\n\n### Expected Output Per Article (adjust according to the scraper output you have already built)\n* `title`\n* `article` (trimmed to a maximum of 4000 characters)\n* `publishedAt`\n* `author`\n* `sourceUrl`\n* Include the configuration data so it can be used in the LLM step.\n"
},
"typeVersion": 1
},
{
"id": "84e904ab-db14-4dd4-a3f5-7ad09a47afb3",
"name": "Sticky Note - Phase 4",
"type": "n8n-nodes-base.stickyNote",
"position": [
11520,
3568
],
"parameters": {
"color": 6,
"width": 580,
"height": 544,
"content": "### What To Do\n1. Send each processed article to **GPT-4o-mini**\n using a structured analysis prompt.\n2. Define the **system prompt directly inside the OpenAI Chat Model node**\n to ensure `chainLlm` correctly passes the instructions.\n3. Configure the model to return structured JSON containing:\n * `sentiment` (positive / neutral / negative)\n * `sentiment_score` (-1.0 to 1.0)\n * `summary` (2\u20133 sentences)\n * `key_topics` (array of detected topics)\n * `tone` (praise / criticism / neutral / etc.)\n * `action_required` (true if the brand should respond)\n * `action_reason` (explanation if action is needed)\n * `quote` (most impactful quote from the article)\n4. Update the Slack formatting logic:\n * Remove the `| upper` Jinja filter\n * Use JavaScript `.toUpperCase()` instead\n5. Add a condition:\n * If `action_required = true`, send an **immediate Slack alert**.\n\n### Cost Tip\n* Use `gpt-4o-mini` for low-cost processing (around ~$0.0001 per article).\n* Switch to `gpt-4o` only if higher analysis quality is required.\n"
},
"typeVersion": 1
},
{
"id": "95419708-8e4a-44e1-94b4-d5e31359a755",
"name": "Sticky Note - Phase 5",
"type": "n8n-nodes-base.stickyNote",
"position": [
12112,
3568
],
"parameters": {
"color": 3,
"width": 804,
"height": 624,
"content": "### What To Do\n1. Configure **Notion** to save every analyzed article\n as a new database page with complete metadata.\n * Ensure all Notion property `textValue` fields are properly mapped and populated.\n2. Build a **Daily Digest** aggregation step that:\n * Counts total analyzed articles\n * Calculates sentiment breakdown (positive / neutral / negative)\n * Computes average sentiment score\n * Lists all flagged action items\n * Selects the top 5 article summaries\n * Connects to the **Loop Each Article URL \u2192 done output (output 0)** to ensure proper execution order.\n3. Configure **Slack Daily Digest** to send the final summary report\n to your Slack channel at the end of the workflow.\n * Add an empty-run fallback so the digest still sends\n even if 0 articles were processed.\n\n### Slack Alert Types\n* **Urgent Alert** \u2014 Trigger immediately when\n `action_required = true`.\n* **Daily Digest** \u2014 Send once at the end of the run\n with a complete summary of all processed articles.\n\n### Customize\n* Update Notion database properties directly in the Notion node.\n* Modify Slack message formatting inside the Code node.\n* Optionally add a Gmail node after the Slack Digest step\n to send the report via email.\n"
},
"typeVersion": 1
},
{
"id": "ab9057d6-3a66-4a47-94dd-7fc431a7e4c4",
"name": "Get Target News Website",
"type": "n8n-nodes-base.googleSheets",
"position": [
9776,
3408
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1-NGutUhBF7I2pFEzPqsK2IN_d74v8CoKEsC48t750VA/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1-NGutUhBF7I2pFEzPqsK2IN_d74v8CoKEsC48t750VA",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1-NGutUhBF7I2pFEzPqsK2IN_d74v8CoKEsC48t750VA/edit?usp=drivesdk",
"cachedResultName": "Target Website"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "3dff21e9-7d4c-4996-b88a-bdaec891a894",
"name": "Sticky Note - Phase ",
"type": "n8n-nodes-base.stickyNote",
"position": [
9504,
3312
],
"parameters": {
"color": 4,
"width": 432,
"height": 256,
"content": "## Phase 1 \u2014 Trigger & Config"
},
"typeVersion": 1
},
{
"id": "c69f77d7-2eec-45c0-86f8-5f3521d48d26",
"name": "Sticky Note - Phase 6",
"type": "n8n-nodes-base.stickyNote",
"position": [
9952,
3216
],
"parameters": {
"color": 2,
"width": 880,
"height": 336,
"content": "## Phase 2 \u2014 URL Discovery"
},
"typeVersion": 1
},
{
"id": "d0c6aa04-718f-485a-b6c6-81236ee2ca35",
"name": "Sticky Note - Phase 7",
"type": "n8n-nodes-base.stickyNote",
"position": [
10864,
3216
],
"parameters": {
"color": 5,
"width": 640,
"height": 336,
"content": "## Phase 3 \u2014 Article Extraction"
},
"typeVersion": 1
},
{
"id": "076b8730-b119-4a97-9c4d-4dfb66304df1",
"name": "Sticky Note - Phase 8",
"type": "n8n-nodes-base.stickyNote",
"position": [
11520,
3216
],
"parameters": {
"color": 6,
"width": 580,
"height": 336,
"content": "## Phase 4 \u2014 AI Sentiment Analysis\n"
},
"typeVersion": 1
},
{
"id": "cb91a350-fff8-4351-af78-c26c8bbddbb8",
"name": "Sticky Note - Phase 9",
"type": "n8n-nodes-base.stickyNote",
"position": [
12112,
3216
],
"parameters": {
"color": 3,
"width": 804,
"height": 336,
"content": "## Phase 5 \u2014 Storage & Reporting"
},
"typeVersion": 1
},
{
"id": "767918c2-8a6c-44f7-ae3c-bb59b2d06d20",
"name": "Extract URLs",
"type": "n8n-nodes-base.code",
"position": [
10448,
3280
],
"parameters": {
"jsCode": "// Get data from previous node\nconst inputData = $input.all();\n\n// Default empty array\nlet urls = [];\n\n// Extract URLs safely (correct nested path)\nif (\n inputData.length > 0 &&\n inputData[0].json?.data?.data?.urls &&\n Array.isArray(inputData[0].json.data.data.urls)\n) {\n urls = inputData[0].json.data.data.urls;\n}\n\n// Return each URL as separate item\nreturn urls.map((url, index) => ({\n json: {\n url,\n index: index + 1\n }\n}));"
},
"typeVersion": 2
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "426e85af-2aaf-425d-976a-8ab5e7bfdc13",
"connections": {
"Extract URLs": {
"main": [
[
{
"node": "Loop Each Article URL",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Sentiment & Summary AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Needs Urgent Alert?": {
"main": [
[
{
"node": "Slack - Urgent Alert",
"type": "main",
"index": 0
}
],
[
{
"node": "Notion - Save Article Analysis",
"type": "main",
"index": 0
}
]
]
},
"Slack - Urgent Alert": {
"main": [
[
{
"node": "Notion - Save Article Analysis",
"type": "main",
"index": 0
}
]
]
},
"Loop Each Article URL": {
"main": [
[
{
"node": "Loop Each News Source",
"type": "main",
"index": 0
}
],
[
{
"node": "MrScraper - Extract Article Content",
"type": "main",
"index": 0
}
]
]
},
"Loop Each News Source": {
"main": [
[
{
"node": "Build Daily Digest Summary",
"type": "main",
"index": 0
}
],
[
{
"node": "MrScraper - Discover Article URLs",
"type": "main",
"index": 0
}
]
]
},
"Get Target News Website": {
"main": [
[
{
"node": "Loop Each News Source",
"type": "main",
"index": 0
}
]
]
},
"Build Daily Digest Summary": {
"main": [
[
{
"node": "Slack - Daily Digest",
"type": "main",
"index": 0
}
]
]
},
"Keep Only Relevant Articles": {
"main": [
[
{
"node": "Sentiment & Summary AI Agent",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger (Daily 8AM)": {
"main": [
[
{
"node": "Get Target News Website",
"type": "main",
"index": 0
}
]
]
},
"Sentiment & Summary AI Agent": {
"main": [
[
{
"node": "Parse AI Response & Format Output",
"type": "main",
"index": 0
}
]
]
},
"Notion - Save Article Analysis": {
"main": [
[
{
"node": "Loop Each Article URL",
"type": "main",
"index": 0
}
]
]
},
"MrScraper - Discover Article URLs": {
"main": [
[
{
"node": "Extract URLs",
"type": "main",
"index": 0
}
]
]
},
"Parse AI Response & Format Output": {
"main": [
[
{
"node": "Needs Urgent Alert?",
"type": "main",
"index": 0
}
]
]
},
"MrScraper - Extract Article Content": {
"main": [
[
{
"node": "Pick Best Content + Filter Irrelevant",
"type": "main",
"index": 0
}
]
]
},
"Pick Best Content + Filter Irrelevant": {
"main": [
[
{
"node": "Keep Only Relevant Articles",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleSheetsOAuth2ApimrscraperApiopenAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This n8n template automatically monitors news sources daily, analyzes article sentiment using AI, and delivers structured intelligence reports to your team — all without any manual reading. It uses MrScraper to discover and extract articles, GPT-4o-mini to score sentiment and…
Source: https://n8n.io/workflows/13818/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This n8n template gives ecommerce brands a fully automated review intelligence system — running every morning to scrape, analyze, and report on what customers are actually saying across every platform
Revenue operations teams, SaaS growth managers, and sales directors who need automated weekly insights from their Stripe payment data. Perfect for small to medium businesses tracking subscription reve
This template is perfect for content creators, marketers, and researchers managing WeChat public account articles! 🚀 It’s ideal for n8n newcomers or anyone wanting to save time on manual content analy
Product managers, customer success teams, and small business owners who collect feedback via Google Forms and want automated sentiment analysis without manual review. Ideal for teams processing 10-100
This is an enterprise-grade Intelligent Document Processing (IDP) hub designed to handle high-volume document ingestion, classification, and departmental routing. It transforms monolithic bulk scans i