AutomationFlowsData & Sheets › Monitor Ecommerce Reviews with Mrscraper, Gpt-4o-mini, Slack and Notion

Monitor Ecommerce Reviews with Mrscraper, Gpt-4o-mini, Slack and Notion

Byriandra @riandradiva on n8n.io

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. It uses MrScraper to collect reviews from Tokopedia, Shopee, Lazada,…

Cron / scheduled trigger★★★★☆ complexityAI-powered28 nodesN8N Nodes MrscraperOpenAI ChatChain LlmSlackNotionGoogle Sheets
Data & Sheets Trigger: Cron / scheduled Nodes: 28 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #13819 — 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 →

Download .json
{
  "id": "XwxAhfsVlx68TL5A",
  "name": "BrandPulse 360\u00b0 \u2014 Ecommerce Review Intelligence",
  "tags": [],
  "nodes": [
    {
      "id": "afa13857-44a7-48ba-9ffa-b45edfd45899",
      "name": "Schedule Trigger (Daily 6AM)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        848,
        208
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "1a85bcd1-b359-4147-a183-72d218b78a01",
      "name": "Loop Each Product SKU",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1296,
        208
      ],
      "parameters": {
        "options": {
          "reset": false
        }
      },
      "typeVersion": 3
    },
    {
      "id": "83cb9fda-7141-41f4-8835-d842b2c801db",
      "name": "MrScraper - Discover Review URLs",
      "type": "n8n-nodes-mrscraper.mrscraper",
      "position": [
        1520,
        80
      ],
      "parameters": {
        "url": "={{ $json.Product_URL }}",
        "operation": "mapAgent",
        "scraperId": "=// Input Your Review List Scraper ID from MrScraper (required)",
        "requestOptions": {},
        "excludePatterns": "// e.g. /seller, /similar, /ads",
        "includePatterns": "// e.g. /review, /ulasan, #review-section"
      },
      "credentials": {
        "mrscraperApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1,
      "waitBetweenTries": 3000
    },
    {
      "id": "508a804b-9095-40a7-89cd-e30ff6fc62a3",
      "name": "Loop Each Review",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1968,
        192
      ],
      "parameters": {
        "options": {
          "reset": false
        }
      },
      "typeVersion": 3
    },
    {
      "id": "92a44af9-d943-4f5f-a546-f8a6cb190dde",
      "name": "MrScraper - Extract Review Content",
      "type": "n8n-nodes-mrscraper.mrscraper",
      "position": [
        2224,
        80
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "operation": "generalAgent",
        "scraperId": "=// Input Your Review Detail Scraper ID from MrScraper (required)",
        "requestOptions": {}
      },
      "credentials": {
        "mrscraperApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "97628393-3576-4214-9428-d79886c034e3",
      "name": "Filter & Enrich Review Data",
      "type": "n8n-nodes-base.code",
      "position": [
        2448,
        80
      ],
      "parameters": {
        "jsCode": "// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// FILTER & ENRICH ECOMMERCE REVIEW DATA\n// Handles Tokopedia, Shopee, Lazada, Bukalapak, Amazon\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst item = $input.item.json;\n\n// --- 1. Resolve nested data from MrScraper response ---\nlet rootData =\n  item?.data?.data?.data ||\n  item?.data?.data ||\n  item?.data ||\n  {};\n\n// --- 2. Extract review fields (map common field names across platforms) ---\nconst reviewText =\n  rootData.review_text ||\n  rootData.review ||\n  rootData.content ||\n  rootData.ulasan ||\n  rootData.text ||\n  rootData.body ||\n  '';\n\nconst rating =\n  parseFloat(rootData.rating ||\n  rootData.star ||\n  rootData.stars ||\n  rootData.score || 0);\n\nconst reviewerName =\n  rootData.reviewer ||\n  rootData.reviewer_name ||\n  rootData.user ||\n  rootData.username ||\n  'Anonymous';\n\nconst reviewDate =\n  rootData.review_date ||\n  rootData.date ||\n  rootData.created_at ||\n  rootData.published_at ||\n  new Date().toISOString();\n\nconst photoCount =\n  parseInt(rootData.photo_count ||\n  rootData.images_count ||\n  rootData.photos || 0);\n\nconst helpfulVotes =\n  parseInt(rootData.helpful ||\n  rootData.helpful_votes ||\n  rootData.likes || 0);\n\nconst isVerified =\n  rootData.verified_purchase ||\n  rootData.verified ||\n  false;\n\nconst sellerReply =\n  rootData.seller_reply ||\n  rootData.shop_reply ||\n  rootData.response ||\n  '';\n\n// --- 3. Word count filter ---\nconst wordCount =\n  reviewText.split(/\\s+/).filter(w => w.length > 0).length || 0;\n\n// Skip reviews under 10 words UNLESS rating is very low (1-2 stars = still valuable signal)\nif (wordCount < 10 && rating > 2) {\n  return [{\n    json: {\n      skip: true,\n      reason: 'Review too short and not a critical rating',\n      rating,\n      wordCount\n    }\n  }];\n}\n\n// --- 4. Deduplication hash (reviewer + date + first 50 chars) ---\nconst hashBase = `${reviewerName}|${reviewDate}|${reviewText.slice(0, 50)}`;\nconst reviewHash = hashBase.split('').reduce((acc, char) => {\n  return ((acc << 5) - acc + char.charCodeAt(0)) | 0;\n}, 0).toString(36);\n\n// --- 5. Pre-flag viral risk (before AI \u2014 fast check) ---\nconst viralRiskPreflag =\n  photoCount >= 3 ||\n  helpfulVotes >= 10 ||\n  rating === 1 ||\n  reviewText.length > 500;\n\n// --- 6. Infer sentiment from rating alone for text-less reviews ---\nlet ratingOnlySentiment = null;\nif (wordCount < 10) {\n  if (rating <= 2) ratingOnlySentiment = 'negative';\n  else if (rating === 3) ratingOnlySentiment = 'neutral';\n  else ratingOnlySentiment = 'positive';\n}\n\n// --- 7. Return enriched review ---\nreturn [{\n  json: {\n    skip: false,\n    // Review core data\n    reviewText: reviewText.slice(0, 4000), // Trim to max 4000 chars for AI\n    rating,\n    reviewerName,\n    reviewDate,\n    photoCount,\n    helpfulVotes,\n    isVerified,\n    sellerReply,\n    wordCount,\n    reviewHash,\n    viralRiskPreflag,\n    ratingOnlySentiment,\n    // Product context (passed from Google Sheets)\n    productName: $('Get Target Product List').item.json.Product_Name || rootData.product_name || '',\n    skuCode: $('Get Target Product List').item.json.SKU_Code || '',\n    platform: $('Get Target Product List').item.json.Platform || rootData.platform || '',\n    category: $('Get Target Product List').item.json.Category || '',\n    productUrl: $('Get Target Product List').item.json.Product_URL || '',\n    brandName: $('Get Target Product List').item.json.Brand_Name || 'YourBrand',\n    // Workflow config\n    slackChannel: '// Input your Slack channel name e.g. #brand-monitoring',\n    slackAlertChannel: '// Input your Slack alert channel name e.g. #brand-alerts',\n    notionDatabaseId: '// Input your Notion Database ID',\n    competitorKeywords: 'competitor1, competitor2, competitor3',\n    alertThreshold: 2.5,\n    runDate: new Date().toISOString()\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "ecb54aac-43ae-4f40-896d-f6de93f19d45",
      "name": "Keep Only Valid Reviews",
      "type": "n8n-nodes-base.if",
      "position": [
        2640,
        80
      ],
      "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": "57e08124-c7f1-4c3f-9d82-1a53ecd9fc74",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        2880,
        224
      ],
      "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": "b269bc7f-f1cb-4c34-8b12-ff5a011b994d",
      "name": "Brand Sentiment AI Agent",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        2864,
        64
      ],
      "parameters": {
        "text": "=Analyze the following ecommerce product review for the brand \"{{ $json.brandName }}\".\n\nProduct: {{ $json.productName }}\nSKU: {{ $json.skuCode }}\nPlatform: {{ $json.platform }}\nCategory: {{ $json.category }}\nStar Rating: {{ $json.rating }} / 5\nReviewer: {{ $json.reviewerName }}\nReview Date: {{ $json.reviewDate }}\nVerified Purchase: {{ $json.isVerified }}\nPhoto Count: {{ $json.photoCount }}\nHelpful Votes: {{ $json.helpfulVotes }}\nSeller Reply Exists: {{ $json.sellerReply ? 'Yes' : 'No' }}\n\nReview Text:\n{{ $json.reviewText }}\n\nKnown Competitors to detect: {{ $json.competitorKeywords }}\n\nReturn ONLY this exact JSON structure, no markdown, no explanation:\n\n{\n  \"sentiment_label\": \"positive | neutral | negative\",\n  \"sentiment_score\": number between -1.0 and 1.0,\n  \"emotion_tags\": [\"array of emotions: frustration | delight | anger | loyalty | confusion | disappointment | trust | excitement | skepticism\"],\n  \"product_dimension\": \"quality | delivery | packaging | pricing | authenticity | customer_service | after_sales | overall\",\n  \"key_phrase\": \"most impactful sentence from the review (max 20 words)\",\n  \"summary\": \"2 sentence summary of the review in English\",\n  \"competitor_mentions\": [\"array of competitor brand names mentioned, or empty array []\"],\n  \"urgency_level\": \"low | medium | high | critical\",\n  \"response_suggestion\": \"suggested CS reply to this review (max 40 words)\",\n  \"viral_risk\": true or false,\n  \"viral_reason\": \"why this review could go viral, or empty string\",\n  \"brand_loyalty_signal\": true or false,\n  \"awareness_source\": \"first_time | repeat | referral_mention | unknown\",\n  \"action_required\": true or false,\n  \"action_reason\": \"why action is needed, or empty string\",\n  \"cx_score\": number between 1 and 10\n}",
        "messages": {
          "messageValues": [
            {
              "message": "You are an expert brand intelligence engine specializing in ecommerce review analysis.\n\nYou MUST return ONLY valid JSON. No markdown. No explanations. No backticks. No extra text.\nAlways return all fields. Never return null.\n\nSentiment rules:\n- positive \u2192 praise, satisfaction, recommendation, repeat purchase intent, product quality compliment\n- negative \u2192 complaint, defect, scam accusation, damaged product, bad service, misleading description\n- neutral \u2192 factual description, mixed feedback, neither clearly positive nor negative\n\nSentiment score rules:\n- Range: -1.0 (strongly negative) to +1.0 (strongly positive)\n- 1 star \u2192 typically -0.7 to -1.0\n- 2 stars \u2192 typically -0.3 to -0.7\n- 3 stars \u2192 typically -0.1 to +0.1\n- 4 stars \u2192 typically +0.3 to +0.6\n- 5 stars \u2192 typically +0.6 to +1.0\n\nUrgency rules:\n- critical \u2192 rating 1-2 stars + scam/fake/tipu claim, OR viral risk true, OR competitor switch mentioned\n- high \u2192 rating 1-2 stars OR strong complaint about safety/defect/health\n- medium \u2192 rating 3 stars with complaints OR delivery issue\n- low \u2192 positive or neutral reviews\n\nViral risk = true if:\n- photo count >= 3 in the metadata\n- review text length > 300 characters with strong emotion\n- contains words like 'viral', 'share', 'warning', 'jangan beli', 'scam', 'tipu', 'boycott'\n- helpful votes >= 10\n\nBrand loyalty signal = true if:\n- reviewer mentions repeat purchase, been using for years, always buys this brand, or recommends to others"
            }
          ]
        },
        "promptType": "define"
      },
      "typeVersion": 1.5
    },
    {
      "id": "09945704-cfd1-458f-90b0-904e00fbe30a",
      "name": "Parse AI Response & Format Output",
      "type": "n8n-nodes-base.code",
      "position": [
        3216,
        64
      ],
      "parameters": {
        "jsCode": "// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// PARSE AI RESPONSE & FORMAT ALL OUTPUT FIELDS\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst item = $input.item.json;\nconst reviewMeta = $('Keep Only Valid Reviews').item.json;\n\nlet analysis = {};\ntry {\n  const raw = item.text || item.response || item.output || '';\n  const cleaned = raw.replace(/```json|```/g, '').trim();\n  analysis = JSON.parse(cleaned);\n} catch (e) {\n  // Fallback: infer from star rating if AI parse fails\n  const r = reviewMeta.rating || 3;\n  analysis = {\n    sentiment_label: r >= 4 ? 'positive' : r === 3 ? 'neutral' : 'negative',\n    sentiment_score: r >= 4 ? 0.6 : r === 3 ? 0.0 : -0.6,\n    emotion_tags: [],\n    product_dimension: 'overall',\n    key_phrase: reviewMeta.reviewText?.slice(0, 80) || '',\n    summary: 'AI parse failed \u2014 sentiment inferred from star rating.',\n    competitor_mentions: [],\n    urgency_level: r <= 2 ? 'high' : 'low',\n    response_suggestion: 'Thank you for your feedback. We will look into this.',\n    viral_risk: false,\n    viral_reason: '',\n    brand_loyalty_signal: false,\n    awareness_source: 'unknown',\n    action_required: r <= 2,\n    action_reason: r <= 2 ? 'Low star rating requires CS follow-up' : '',\n    cx_score: r * 2\n  };\n}\n\n// Sanitize types\nconst sentimentLabel = String(analysis.sentiment_label || 'neutral').toLowerCase();\nconst sentimentScore = parseFloat(analysis.sentiment_score) || 0;\nconst actionRequired = analysis.action_required === true || analysis.action_required === 'true';\nconst viralRisk = analysis.viral_risk === true || analysis.viral_risk === 'true' || reviewMeta.viralRiskPreflag;\nconst urgencyLevel = String(analysis.urgency_level || 'low').toLowerCase();\n\n// Emoji maps\nconst sentimentEmoji = { positive: '\ud83d\udfe2', neutral: '\ud83d\udfe1', negative: '\ud83d\udd34' }[sentimentLabel] || '\u26aa';\nconst urgencyEmoji = { critical: '\ud83d\udea8', high: '\u26a0\ufe0f', medium: '\ud83d\udd14', low: '\u2139\ufe0f' }[urgencyLevel] || '\u2139\ufe0f';\nconst platformEmoji = {\n  tokopedia: '\ud83d\udecd\ufe0f', shopee: '\ud83d\udfe0', lazada: '\ud83d\udd35', bukalapak: '\ud83d\udd34', amazon: '\ud83d\udce6'\n}[String(reviewMeta.platform || '').toLowerCase()] || '\ud83d\uded2';\n\n// Star display\nconst starDisplay = '\u2b50'.repeat(Math.round(reviewMeta.rating || 0));\n\n// Competitor mentions as string\nconst competitorStr = Array.isArray(analysis.competitor_mentions) && analysis.competitor_mentions.length > 0\n  ? analysis.competitor_mentions.join(', ')\n  : 'None';\n\n// Emotion tags as string\nconst emotionStr = Array.isArray(analysis.emotion_tags)\n  ? analysis.emotion_tags.join(', ')\n  : String(analysis.emotion_tags || '');\n\n// Slack urgent message\nconst slackUrgentMessage =\n`${urgencyEmoji} *URGENT BRAND ALERT* \u2014 Action Required\n\n${platformEmoji} *Platform:* ${reviewMeta.platform} | ${sentimentEmoji} *Sentiment:* ${sentimentLabel.toUpperCase()} (Score: ${sentimentScore.toFixed(2)})\n*Product:* ${reviewMeta.productName} (${reviewMeta.skuCode})\n*Star Rating:* ${starDisplay} ${reviewMeta.rating}/5\n*Emotion:* ${emotionStr}\n*Product Dimension:* ${analysis.product_dimension || 'overall'}\n\n*Review Excerpt:*\n_\"${reviewMeta.reviewText?.slice(0, 200)}...\"_\n\n*Key Phrase:* \"${analysis.key_phrase || ''}\"\n*Urgency:* ${urgencyLevel.toUpperCase()}\n${viralRisk ? `\ud83d\udd25 *Viral Risk Detected:* ${analysis.viral_reason || 'High engagement signals'}` : ''}\n${competitorStr !== 'None' ? `\u2694\ufe0f *Competitor Mentioned:* ${competitorStr}` : ''}\n\n*Suggested CS Response:*\n_${analysis.response_suggestion || 'Thank you for your feedback.'}_\n\n*Why action is needed:*\n${analysis.action_reason || ''}\n\n\ud83d\udd17 <${reviewMeta.productUrl}|View Product on ${reviewMeta.platform}>\n_Reviewed: ${reviewMeta.reviewDate} | Processed: ${reviewMeta.runDate}_`;\n\nreturn [{\n  json: {\n    // Review metadata\n    productName: reviewMeta.productName,\n    skuCode: reviewMeta.skuCode,\n    platform: reviewMeta.platform,\n    category: reviewMeta.category,\n    productUrl: reviewMeta.productUrl,\n    brandName: reviewMeta.brandName,\n    reviewerName: reviewMeta.reviewerName,\n    reviewDate: reviewMeta.reviewDate,\n    rating: reviewMeta.rating,\n    starDisplay,\n    photoCount: reviewMeta.photoCount,\n    helpfulVotes: reviewMeta.helpfulVotes,\n    isVerified: reviewMeta.isVerified,\n    sellerReply: reviewMeta.sellerReply,\n    wordCount: reviewMeta.wordCount,\n    reviewHash: reviewMeta.reviewHash,\n    reviewExcerpt: reviewMeta.reviewText?.slice(0, 300) || '',\n    runDate: reviewMeta.runDate,\n    // AI analysis fields\n    sentiment_label: sentimentLabel,\n    sentiment_score: sentimentScore,\n    sentimentEmoji,\n    emotion_tags: emotionStr,\n    product_dimension: analysis.product_dimension || 'overall',\n    key_phrase: analysis.key_phrase || '',\n    summary: analysis.summary || '',\n    competitor_mentions: competitorStr,\n    urgency_level: urgencyLevel,\n    urgencyEmoji,\n    response_suggestion: analysis.response_suggestion || '',\n    viral_risk: viralRisk,\n    viral_reason: analysis.viral_reason || '',\n    brand_loyalty_signal: analysis.brand_loyalty_signal === true,\n    awareness_source: analysis.awareness_source || 'unknown',\n    action_required: actionRequired,\n    action_reason: analysis.action_reason || '',\n    cx_score: parseFloat(analysis.cx_score) || (reviewMeta.rating * 2),\n    // Routing\n    slackChannel: reviewMeta.slackChannel,\n    slackAlertChannel: reviewMeta.slackAlertChannel,\n    notionDatabaseId: reviewMeta.notionDatabaseId,\n    // Formatted messages\n    slackUrgentMessage,\n    platformEmoji\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "7af404e6-441a-4f58-a359-f0450977919d",
      "name": "Needs Urgent Alert?",
      "type": "n8n-nodes-base.if",
      "position": [
        3440,
        96
      ],
      "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": "5322273d-f6e8-4b30-b298-69d869d8c30a",
      "name": "Slack - Urgent Brand Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        3888,
        48
      ],
      "parameters": {
        "text": "={{ $json.slackUrgentMessage }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $json.slackAlertChannel }}"
        },
        "otherOptions": {
          "mrkdwn": true
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "8c515320-9a48-4831-807b-b7e135d097e4",
      "name": "Notion - Save Review Analysis",
      "type": "n8n-nodes-base.notion",
      "position": [
        4048,
        160
      ],
      "parameters": {
        "title": "={{ $json.productName }} \u2014 {{ $json.platform }} \u2014 {{ $json.rating }}\u2b50 \u2014 {{ $json.reviewDate }}",
        "options": {},
        "resource": "databasePage",
        "databaseId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.notionDatabaseId }}"
        },
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "SKU Code"
            },
            {
              "key": "Platform",
              "selectValue": "={{ $json.platform }}"
            },
            {
              "key": "Category"
            },
            {
              "key": "Product URL",
              "urlValue": "={{ $json.productUrl }}"
            },
            {
              "key": "Star Rating",
              "numberValue": "={{ $json.rating }}"
            },
            {
              "key": "Sentiment",
              "selectValue": "={{ $json.sentiment_label }}"
            },
            {
              "key": "Sentiment Score",
              "numberValue": "={{ $json.sentiment_score }}"
            },
            {
              "key": "Emotion Tags"
            },
            {
              "key": "Product Dimension",
              "selectValue": "={{ $json.product_dimension }}"
            },
            {
              "key": "Key Phrase"
            },
            {
              "key": "Summary"
            },
            {
              "key": "CX Score",
              "numberValue": "={{ $json.cx_score }}"
            },
            {
              "key": "Competitor Mentions"
            },
            {
              "key": "Urgency Level",
              "selectValue": "={{ $json.urgency_level }}"
            },
            {
              "key": "Response Suggestion"
            },
            {
              "key": "Viral Risk",
              "checkboxValue": "={{ $json.viral_risk }}"
            },
            {
              "key": "Viral Reason"
            },
            {
              "key": "Brand Loyalty Signal",
              "checkboxValue": "={{ $json.brand_loyalty_signal }}"
            },
            {
              "key": "Awareness Source",
              "selectValue": "={{ $json.awareness_source }}"
            },
            {
              "key": "Action Required",
              "checkboxValue": "={{ $json.action_required }}"
            },
            {
              "key": "Action Reason"
            },
            {
              "key": "Reviewer Name"
            },
            {
              "key": "Verified Purchase",
              "checkboxValue": "={{ $json.isVerified }}"
            },
            {
              "key": "Photo Count",
              "numberValue": "={{ $json.photoCount }}"
            },
            {
              "key": "Helpful Votes",
              "numberValue": "={{ $json.helpfulVotes }}"
            },
            {
              "key": "Review Excerpt"
            },
            {
              "key": "Review Date"
            },
            {
              "key": "Processed At"
            }
          ]
        }
      },
      "typeVersion": 2.2,
      "continueOnFail": true
    },
    {
      "id": "84eb269e-b442-4944-9313-3c7e6a3e1451",
      "name": "Build Daily Brand Health Digest",
      "type": "n8n-nodes-base.code",
      "position": [
        3584,
        192
      ],
      "parameters": {
        "jsCode": "// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// BUILD DAILY BRAND HEALTH DIGEST\n// Aggregates all processed reviews into a structured report\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst allItems = $input.all();\n\nif (!allItems || allItems.length === 0) {\n  return [{\n    json: {\n      digestMessage: `\ud83d\udcca *BrandPulse 360\u00b0 \u2014 Daily Brand Health Report*\\n\\nNo reviews were processed today. Check your scraper configuration or product URLs.`,\n      total: 0\n    }\n  }];\n}\n\nconst reviews = allItems.map(i => i.json);\nconst runDate = new Date().toISOString().slice(0, 10);\n\n// \u2500\u2500 Sentiment breakdown \u2500\u2500\nconst sentimentCounts = { positive: 0, neutral: 0, negative: 0 };\nfor (const r of reviews) {\n  const s = (r.sentiment_label || 'neutral').toLowerCase();\n  if (sentimentCounts[s] !== undefined) sentimentCounts[s]++;\n}\n\n// \u2500\u2500 Average rating & sentiment score \u2500\u2500\nconst avgRating = (reviews.reduce((sum, r) => sum + (parseFloat(r.rating) || 0), 0) / reviews.length).toFixed(2);\nconst avgSentimentScore = (reviews.reduce((sum, r) => sum + (parseFloat(r.sentiment_score) || 0), 0) / reviews.length).toFixed(2);\nconst avgCxScore = (reviews.reduce((sum, r) => sum + (parseFloat(r.cx_score) || 0), 0) / reviews.length).toFixed(1);\n\n// \u2500\u2500 Brand Awareness Score (BAS) calculation \u2500\u2500\nconst positiveRate = sentimentCounts.positive / reviews.length;\nconst viralCount = reviews.filter(r => r.viral_risk).length;\nconst loyaltyCount = reviews.filter(r => r.brand_loyalty_signal).length;\nconst actionCount = reviews.filter(r => r.action_required).length;\nconst viralPenalty = Math.min(viralCount * 5, 10); // max -10 pts\nconst BAS = Math.min(100, Math.max(0, Math.round(\n  (parseFloat(avgRating) / 5) * 30 +           // avg rating \u2192 max 30pts\n  positiveRate * 25 +                           // % positive \u2192 max 25pts\n  parseFloat(avgSentimentScore) * 10 + 10 +    // sentiment score \u2192 max 20pts\n  (loyaltyCount / reviews.length) * 10 +       // loyalty signals \u2192 max 10pts\n  (5) -                                         // base 5pts for competitor benchmark (placeholder)\n  viralPenalty                                  // viral risk penalty \u2192 max -10pts\n)));\n\nconst basEmoji = BAS >= 75 ? '\ud83d\udfe2' : BAS >= 50 ? '\ud83d\udfe1' : '\ud83d\udd34';\nconst basLabel = BAS >= 75 ? 'Healthy' : BAS >= 50 ? 'Needs Attention' : 'At Risk';\n\n// \u2500\u2500 Product dimension breakdown \u2500\u2500\nconst dimensionCounts = {};\nfor (const r of reviews) {\n  const d = r.product_dimension || 'overall';\n  dimensionCounts[d] = (dimensionCounts[d] || 0) + 1;\n}\nconst topDimension = Object.entries(dimensionCounts).sort((a, b) => b[1] - a[1])[0]?.[0] || 'N/A';\n\n// \u2500\u2500 Emotion tag frequency \u2500\u2500\nconst emotionFreq = {};\nfor (const r of reviews) {\n  for (const e of (r.emotion_tags || '').split(',').map(t => t.trim()).filter(Boolean)) {\n    emotionFreq[e] = (emotionFreq[e] || 0) + 1;\n  }\n}\nconst topEmotions = Object.entries(emotionFreq).sort((a, b) => b[1] - a[1]).slice(0, 3).map(([e, c]) => `${e} (${c})`).join(', ');\n\n// \u2500\u2500 Competitor mentions \u2500\u2500\nconst competitorMap = {};\nfor (const r of reviews) {\n  if (r.competitor_mentions && r.competitor_mentions !== 'None') {\n    for (const c of r.competitor_mentions.split(',').map(s => s.trim())) {\n      if (c) competitorMap[c] = (competitorMap[c] || 0) + 1;\n    }\n  }\n}\nconst topCompetitors = Object.entries(competitorMap).sort((a, b) => b[1] - a[1]).slice(0, 3);\nconst competitorSummary = topCompetitors.length > 0\n  ? topCompetitors.map(([brand, count]) => `${brand} (${count}x)`).join(', ')\n  : 'None detected';\n\n// \u2500\u2500 Platform breakdown \u2500\u2500\nconst platformCounts = {};\nfor (const r of reviews) {\n  const p = r.platform || 'Unknown';\n  platformCounts[p] = (platformCounts[p] || 0) + 1;\n}\nconst platformBreakdown = Object.entries(platformCounts).map(([p, c]) => `${p}: ${c}`).join(' | ');\n\n// \u2500\u2500 Top 3 praises \u2500\u2500\nconst topPraises = reviews\n  .filter(r => r.sentiment_label === 'positive')\n  .sort((a, b) => b.sentiment_score - a.sentiment_score)\n  .slice(0, 3)\n  .map(r => `\u2022 ${r.key_phrase || r.summary?.slice(0, 80) || ''} [${r.platform}, ${r.rating}\u2b50]`);\n\n// \u2500\u2500 Top 3 complaints \u2500\u2500\nconst topComplaints = reviews\n  .filter(r => r.sentiment_label === 'negative')\n  .sort((a, b) => a.sentiment_score - b.sentiment_score)\n  .slice(0, 3)\n  .map(r => `\u2022 ${r.key_phrase || r.summary?.slice(0, 80) || ''} [${r.platform}, ${r.rating}\u2b50]`);\n\n// \u2500\u2500 Viral risk reviews \u2500\u2500\nconst viralReviews = reviews.filter(r => r.viral_risk).slice(0, 3);\n\n// \u2500\u2500 Action items \u2500\u2500\nconst actionItems = reviews.filter(r => r.action_required).slice(0, 5);\n\n// \u2500\u2500 Build Slack digest message \u2500\u2500\nlet digest = `\ud83d\udcca *BrandPulse 360\u00b0 \u2014 Daily Brand Health Report*\\n`;\ndigest += `\ud83d\udcc5 ${runDate} | \ud83d\uded2 Ecommerce Review Intelligence\\n`;\ndigest += `\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\\n\\n`;\n\ndigest += `${basEmoji} *Brand Awareness Score (BAS): ${BAS}/100 \u2014 ${basLabel}*\\n`;\ndigest += `\u2b50 Avg Rating: ${avgRating}/5 | \ud83d\udcca Avg Sentiment: ${avgSentimentScore} | \ud83c\udfaf CX Score: ${avgCxScore}/10\\n`;\ndigest += `\ud83d\udce6 Total Reviews Processed: ${reviews.length}\\n`;\ndigest += `\ud83d\udfe2 Positive: ${sentimentCounts.positive} | \ud83d\udfe1 Neutral: ${sentimentCounts.neutral} | \ud83d\udd34 Negative: ${sentimentCounts.negative}\\n\\n`;\n\ndigest += `*\ud83d\udccd Platform Breakdown:* ${platformBreakdown}\\n`;\ndigest += `*\ud83c\udfad Top Emotions Detected:* ${topEmotions || 'N/A'}\\n`;\ndigest += `*\ud83d\udd27 Most Discussed Dimension:* ${topDimension}\\n`;\ndigest += `*\u2694\ufe0f Competitor Mentions:* ${competitorSummary}\\n\\n`;\n\nif (topPraises.length > 0) {\n  digest += `*\u2705 Top Praise Signals:*\\n${topPraises.join('\\n')}\\n\\n`;\n}\n\nif (topComplaints.length > 0) {\n  digest += `*\u274c Top Complaint Signals:*\\n${topComplaints.join('\\n')}\\n\\n`;\n}\n\nif (viralReviews.length > 0) {\n  digest += `*\ud83d\udd25 Viral Risk Reviews (${viralCount}):*\\n`;\n  for (const v of viralReviews) {\n    digest += `\u2022 ${v.productName} [${v.platform}, ${v.rating}\u2b50] \u2014 ${v.viral_reason || 'High engagement'}\\n`;\n  }\n  digest += '\\n';\n}\n\nif (actionItems.length > 0) {\n  digest += `*\u26a0\ufe0f ${actionItems.length} Review(s) Requiring Action:*\\n`;\n  for (const a of actionItems) {\n    digest += `\u2022 ${a.productName} [${a.platform}, ${a.rating}\u2b50] \u2014 ${a.action_reason}\\n`;\n  }\n  digest += '\\n';\n}\n\ndigest += `_BrandPulse 360\u00b0 | Powered by MrScraper + GPT-4o-mini + n8n_`;\n\nreturn [{\n  json: {\n    digestMessage: digest,\n    total: reviews.length,\n    sentimentCounts,\n    avgRating,\n    avgSentimentScore,\n    avgCxScore,\n    BAS,\n    basLabel,\n    viralCount,\n    actionCount,\n    competitorSummary,\n    runDate,\n    slackChannel: reviews[0]?.slackChannel || '#brand-monitoring'\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "67db960a-b79f-4e79-bf94-93f08877404d",
      "name": "Slack - Daily Brand Digest",
      "type": "n8n-nodes-base.slack",
      "position": [
        3808,
        192
      ],
      "parameters": {
        "text": "={{ $json.digestMessage }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $json.slackChannel }}"
        },
        "otherOptions": {
          "mrkdwn": true
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "faf60391-e42a-4b7f-92e8-47c06feb72e0",
      "name": "Get Target Product List",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1072,
        208
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "// Input your Google Sheet ID here",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID",
          "cachedResultName": "Target Product List"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "7e73c8d7-a504-4f6d-8458-59c6ae795efc",
      "name": "Extract Review URLs",
      "type": "n8n-nodes-base.code",
      "position": [
        1744,
        80
      ],
      "parameters": {
        "jsCode": "// Extract review URLs from MrScraper map agent response\nconst inputData = $input.all();\nlet urls = [];\n\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// Filter to only include review-related URLs\nconst reviewPatterns = [/review/i, /ulasan/i, /rating/i, /comment/i, /feedback/i];\nconst filteredUrls = urls.filter(url =>\n  reviewPatterns.some(p => p.test(url))\n);\n\n// Use filtered if available, otherwise use all (some platforms keep reviews on product page)\nconst finalUrls = filteredUrls.length > 0 ? filteredUrls : urls;\n\nreturn finalUrls.map((url, index) => ({\n  json: {\n    url,\n    index: index + 1\n  }\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "ba8c747d-377d-4812-b381-6c268504e4df",
      "name": "Sticky Note - Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 720,
        "height": 1200,
        "content": "## \ud83d\uded2 BrandPulse 360\u00b0 \u2014 Setup & Configuration\n\n### Before Running This Workflow\n\n**A) Google Sheets Setup (required)**\nCreate a sheet with these columns (exact names):\n- `Platform` \u2192 e.g. Tokopedia, Shopee, Lazada, Bukalapak\n- `Product_URL` \u2192 full product page URL\n- `Brand_Name` \u2192 your brand name (e.g. YourBrand)\n- `SKU_Code` \u2192 product identifier (e.g. SKU-001)\n- `Category` \u2192 e.g. Electronics, Fashion, Beauty\n- `Active` \u2192 Y or N (only Y rows are processed)\n\nAdd one row per product you want to monitor.\n\n**B) MrScraper Setup (required)**\nCreate TWO scrapers in your MrScraper dashboard:\n\n1. **Review List Scraper** (Map Agent)\n   - Crawls product page and discovers review URLs or review sections\n   - Copy its `scraperId` \u2192 paste into the Map Agent node\n\n2. **Review Detail Scraper** (General Agent)\n   - Extracts: review_text, rating, reviewer_name, review_date, photo_count, helpful_votes, verified_purchase, seller_reply\n   - Copy its `scraperId` \u2192 paste into the General Agent node\n\n3. **Enable AI Scraper API access** in MrScraper account settings.\n\n**C) Credentials Needed in n8n**\n- `MrScraper` API key \u2192 both MrScraper nodes\n- `OpenAI` API key \u2192 OpenAI Chat Model node\n- `Slack` OAuth \u2192 both Slack nodes\n- `Notion` OAuth \u2192 Notion node\n- `Google Sheets` OAuth2 \u2192 Google Sheets node\n\n**D) Config Values to Update (inside Filter & Enrich node)**\n- `slackChannel`: e.g. #brand-monitoring\n- `slackAlertChannel`: e.g. #brand-alerts\n- `notionDatabaseId`: from your Notion DB URL\n- `competitorKeywords`: comma-separated competitor names\n- `alertThreshold`: default 2.5 stars\n\n**E) Notion Database Properties**\nCreate a Notion DB with these properties:\n- Title (title)\n- SKU Code (text) | Platform (select) | Category (text)\n- Product URL (URL) | Star Rating (number)\n- Sentiment (select: positive/neutral/negative)\n- Sentiment Score (number) | Emotion Tags (text)\n- Product Dimension (select) | Key Phrase (text)\n- Summary (text) | CX Score (number)\n- Competitor Mentions (text) | Urgency Level (select)\n- Response Suggestion (text)\n- Viral Risk (checkbox) | Viral Reason (text)\n- Brand Loyalty Signal (checkbox) | Awareness Source (select)\n- Action Required (checkbox) | Action Reason (text)\n- Reviewer Name (text) | Verified Purchase (checkbox)\n- Photo Count (number) | Helpful Votes (number)\n- Review Excerpt (text) | Review Date (text) | Processed At (text)"
      },
      "typeVersion": 1
    },
    {
      "id": "c9fe4e5f-0585-4bc3-96b3-8e0c8c9c25e4",
      "name": "Sticky Note Phase Header 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        800,
        112
      ],
      "parameters": {
        "color": 4,
        "width": 432,
        "height": 256,
        "content": "## Phase 1 \u2014 Trigger & Config"
      },
      "typeVersion": 1
    },
    {
      "id": "5649c41b-cdd1-42e3-bf7e-585713f56a72",
      "name": "Sticky Note Phase 1 Notes",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        800,
        384
      ],
      "parameters": {
        "color": 4,
        "width": 432,
        "height": 336,
        "content": "### Phase 1 \u2014 What To Do\n* Trigger fires daily at **6:00 AM** \u2014 adjust to your timezone.\n* Google Sheets provides the product list.\n  Each row = one product SKU to monitor.\n* Loop processes each SKU independently.\n\n### Key Settings\n* **Trigger time** \u2192 edit the Schedule node\n* **Product list** \u2192 update Google Sheet ID\n* **Brand config** \u2192 edit slackChannel, notionDatabaseId, competitorKeywords inside the _Filter & Enrich Review Data_ node\n* Mark rows `Active = N` in Sheets to pause monitoring for a product without deleting it."
      },
      "typeVersion": 1
    },
    {
      "id": "63da5a7a-da6b-45be-9e3f-b6a6cc9d6ce9",
      "name": "Sticky Note Phase Header 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1248,
        16
      ],
      "parameters": {
        "color": 2,
        "width": 880,
        "height": 336,
        "content": "##  Phase 2 \u2014 Review URL Discovery"
      },
      "typeVersion": 1
    },
    {
      "id": "64ae885b-00d4-4aa5-ac99-9c03d112347c",
      "name": "Sticky Note Phase 2 Notes",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1248,
        368
      ],
      "parameters": {
        "color": 2,
        "width": 880,
        "height": 400,
        "content": "###  Phase 2 \u2014 What To Do\n1. **Map Agent** crawls the product page URL and discovers review section URLs or pagination links.\n2. **Extract Review URLs** node filters to only review-pattern URLs (e.g. /review, /ulasan).\n   If no review-specific URLs found, falls back to the product page itself (handles platforms where reviews are embedded).\n3. Each URL is passed individually into the review extraction loop.\n\n### Tips\n* For Tokopedia/Shopee, reviews are often on the same product page (no separate URL needed).\n* For Amazon, the review section has its own paginated URL \u2014 Map Agent handles this well.\n* Set `limit` on the Map Agent node to control max review pages per product.\n* Adjust `includePatterns` in the Map Agent to target review-specific page sections."
      },
      "typeVersion": 1
    },
    {
      "id": "e8a96139-1c5f-4e71-b023-ec48c8379d3a",
      "name": "Sticky Note Phase Header 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2160,
        16
      ],
      "parameters": {
        "color": 5,
        "width": 640,
        "height": 336,
        "content": "## Phase 3 \u2014 Review Extraction & Filtering"
      },
      "typeVersion": 1
    },
    {
      "id": "6a52db12-cb3d-4ea2-bf2d-2b9d19e48536",
      "name": "Sticky Note Phase 3 Notes",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2160,
        368
      ],
      "parameters": {
        "color": 5,
        "width": 640,
        "height": 560,
        "content": "### Phase 3 \u2014 What To Do\n1. **General Agent** extracts full review data from each URL.\n   Target fields in your MrScraper setup:\n   - `review_text` (or review / ulasan / content)\n   - `rating` (or star / stars / score)\n   - `reviewer_name` (or user / username)\n   - `review_date` (or date / created_at)\n   - `photo_count` (or images_count)\n   - `helpful_votes` (or likes)\n   - `verified_purchase` (boolean)\n   - `seller_reply` (or shop_reply / response)\n2. **Filter & Enrich** node:\n   - Skips reviews under 10 words (unless rating \u2264 2 stars \u2014 short bad reviews are still valuable signals)\n   - Generates a deduplication hash to prevent processing the same review twice\n   - Pre-flags viral risk based on photo count, helpful votes, and review length\n   - Passes product metadata (SKU, platform, brand) from Google Sheets into each review item\n3. **Keep Only Valid Reviews** passes only items where `skip = false`.\n\n### Expected Output Per Review\n- review_text, rating, reviewerName, reviewDate\n- photoCount, helpfulVotes, isVerified, sellerReply\n- productName, skuCode, platform, category, brandName\n- viralRiskPreflag, reviewHash"
      },
      "typeVersion": 1
    },
    {
      "id": "70162d1b-7fe4-4064-9db0-0b7afd740b20",
      "name": "Sticky Note Phase Header 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2816,
        16
      ],
      "parameters": {
        "color": 6,
        "width": 580,
        "height": 336,
        "content": "## Phase 4 \u2014 AI Brand Sentiment Analysis"
      },
      "typeVersion": 1
    },
    {
      "id": "03704708-7416-4b2a-898b-b15856d0c551",
      "name": "Sticky Note Phase 4 Notes",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2816,
        368
      ],
      "parameters": {
        "color": 6,
        "width": 580,
        "height": 720,
        "content": "### Phase 4 \u2014 What To Do\n1. **GPT-4o-mini** analyzes each review with a structured prompt.\n   Returns a JSON with all 15 brand intelligence fields:\n\n   **Sentiment & Emotion:**\n   - `sentiment_label` \u2192 positive / neutral / negative\n   - `sentiment_score` \u2192 -1.0 to +1.0\n   - `emotion_tags` \u2192 frustration / delight / anger / loyalty / confusion / trust...\n\n   **Product Quality Signals:**\n   - `product_dimension` \u2192 quality / delivery / packaging / pricing / authenticity / customer_service / after_sales\n\n   **Brand Reputation:**\n   - `key_phrase` \u2192 most impactful sentence from review\n   - `summary` \u2192 2-sentence plain-English summary\n   - `cx_score` \u2192 Customer Experience score 1\u201310\n\n   **Competitive Intelligence:**\n   - `competitor_mentions` \u2192 array of competitor names detected\n\n   **Viral Risk Detection:**\n   - `viral_risk` \u2192 true/false\n   - `viral_reason` \u2192 explanation\n\n   **Action & Routing:**\n   - `urgency_level` \u2192 low / medium / high / critical\n   - `action_required` \u2192 true/false\n   - `action_reason` \u2192 why CS needs to respond\n   - `response_suggestion` \u2192 ready-to-use CS reply\n   - `brand_loyalty_signal` \u2192 repeat buyer praise detection\n   - `awareness_source` \u2192 first_time / repeat / referral_mention\n\n2. **Parse AI Response** node sanitizes output, builds display formats, and prepares Slack alert message.\n\n### Cost Tip\n* gpt-4o-mini \u2248 $0.0001 per review (very low cost)\n* Switch to gpt-4o only for high-value product lines requiring deeper analysis"
      },
      "typeVersion": 1
    },
    {
      "id": "849ecd84-987a-40d0-8d3d-ab30d3a4a84a",
      "name": "Sticky Note Phase Header 5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3408,
        16
      ],
      "parameters": {
        "color": 3,
        "width": 804,
        "height": 336,
        "content": "## Phase 5 \u2014 Storage, Alerts & Daily Digest"
      },
      "typeVersion": 1
    },
    {
      "id": "8311b054-8516-42f7-9cb5-8effb8bf1942",
      "name": "Sticky Note Phase 5 Notes",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3408,
        368
      ],
      "parameters": {
        "color": 3,
        "width": 804,
        "height": 720,
        "content": "### Phase 5 \u2014 What To Do\n1. **Needs Urgent Alert?** routes reviews where `action_required = true` to an immediate Slack alert.\n   Urgent conditions: urgency = critical or high, viral risk detected, competitor switch mentioned.\n\n2. **Slack - Urgent Brand Alert** fires instantly to your #brand-alerts channel.\n   Message includes: platform, product, rating, emotion, dimension, key phrase, viral reason, competitor mention, and AI-suggested CS response.\n\n3. **Notion - Save Review Analysis** stores every processed review with all 27 metadata fields.\n   Enables: filtering by SKU/platform/sentiment, trend tracking, quarterly brand audits, CS ticket creation.\n\n4. **Build Daily Brand Health Digest** aggregates the full run and calculates:\n   - **Brand Awareness Score (BAS)** out of 100 (weighted formula combining rating, sentiment trend, loyalty signals, viral penalty)\n   - Sentiment breakdown (positive/neutral/negative counts)\n   - Avg rating, avg sentiment score, avg CX score\n   - Platform breakdown\n   - Top 3 praises + top 3 complaints (with platform & rating)\n   - Top emotion tags detected\n   - Most discussed product dimension\n   - Competitor mention summary\n   - Viral risk review list\n   - Action items needing CS follow-up\n\n5. **Slack - Daily Brand Digest** posts the full report to #brand-monitoring every morning.\n\n### Slack Alert Types\n* \ud83d\udea8 **Urgent Alert** \u2192 fires immediately when action_required = true\n* \ud83d\udcca **Daily Digest** \u2192 posted once at end of run with full brand health summary\n\n### Optional Extensions\n* Add Gmail node after Slack Digest to email the report to brand team\n* Connect Notion DB to Google Looker Studio for visual BAS trend charts\n* Add Jira/Asana node to auto-create CS tickets for critical reviews"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "7c5fc293-34c5-4f36-9eb2-bfb686836a25",
  "connections": {
    "Loop Each Review": {
      "main": [
        [
          {
            "node": "Loop Each Product SKU",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "MrScraper - Extract Review Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Brand Sentiment AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Extract Review URLs": {
      "main": [
        [
          {
            "node": "Loop Each Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs Urgent Alert?": {
      "main": [
        [
          {
            "node": "Slack - Urgent Brand Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Notion - Save Review Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Each Product SKU": {
      "main": [
        [
          {
            "node": "Build Daily Brand Health Digest",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "MrScraper - Discover Review URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Target Product List": {
      "main": [
        [
          {
            "node": "Loop Each Product SKU",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Keep Only Valid Reviews": {
      "main": [
        [
          {
            "node": "Brand Sentiment AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Brand Sentiment AI Agent": {
      "main": [
        [
          {
            "node": "Parse AI Response & Format Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack - Urgent Brand Alert": {
      "main": [
        [
          {
            "node": "Notion - Save Review Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter & Enrich Review Data": {
      "main": [
        [
          {
            "node": "Keep Only Valid Reviews",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger (Daily 6AM)": {
      "main": [
        [
          {
            "node": "Get Target Product List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notion - Save Review Analysis": {
      "main": [
        [
          {
            "node": "Loop Each Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Daily Brand Health Digest": {
      "main": [
        [
          {
            "node": "Slack - Daily Brand Digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "MrScraper - Discover Review URLs": {
      "main": [
        [
          {
            "node": "Extract Review URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Response & Format Output": {
      "main": [
        [
          {
            "node": "Needs Urgent Alert?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "MrScraper - Extract Review Content": {
      "main": [
        [
          {
            "node": "Filter & Enrich Review Data",
            "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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

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. It uses MrScraper to collect reviews from Tokopedia, Shopee, Lazada,…

Source: https://n8n.io/workflows/13819/ — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

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 Mr

N8N Nodes Mrscraper, OpenAI Chat, Chain Llm +3
Data & Sheets

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

HTTP Request, Google Sheets, Google Gemini Chat +4
Data & Sheets

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

Google Sheets, RSS Feed Read, Text Classifier +3
Data & Sheets

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

Google Sheets, Chain Llm, Google Gemini Chat +2
Data & Sheets

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

Notion, Slack, Google Sheets +3