AutomationFlowsWeb Scraping › Generate Shopify Review Outreach Drafts with Apify, Hunter, Gpt-4o and Gmail

Generate Shopify Review Outreach Drafts with Apify, Hunter, Gpt-4o and Gmail

ByJitesh Dugar @jiteshdugar on n8n.io

This workflow runs daily to scrape competitor Shopify app reviews via Apify, logs and deduplicates new low-rated reviews in Google Sheets, looks up the reviewer’s website with Serper, enriches contacts with Hunter, generates a personalized outreach email with OpenAI, saves it as…

Cron / scheduled trigger★★★★★ complexityAI-powered36 nodesOpenAI ChatGoogle SheetsHunterChain Llm@Apify/N8N Nodes ApifyHTTP RequestGmailSlack
Web Scraping Trigger: Cron / scheduled Nodes: 36 Complexity: ★★★★★ AI nodes: yes Added:

This workflow corresponds to n8n.io template #16209 — we link there as the canonical source.

This workflow follows the Apifyn8N Nodes Apify → 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": "GmyyDpPNB015JRlq",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Review Scraping & AI Outreach Workflow",
  "tags": [],
  "nodes": [
    {
      "id": "62f2881f-043e-4fd3-aafe-7cbebdaacabb",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        3680,
        192
      ],
      "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": "0ff05c14-d184-4680-91af-9872bc611c33",
      "name": "Process Each Review",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1344,
        -16
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "c2c8b4d2-8119-4fa8-8c9d-a44acd956476",
      "name": "Check Existing Reviews",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1680,
        0
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $('Process Each Review').item.json.id }}",
              "lookupColumn": "Review ID"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1c_HPrB7PZdZJRSjIiG5GIk2b9UoLjfSyt4BUP2FKNCI/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1c_HPrB7PZdZJRSjIiG5GIk2b9UoLjfSyt4BUP2FKNCI",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1c_HPrB7PZdZJRSjIiG5GIk2b9UoLjfSyt4BUP2FKNCI/edit?usp=drivesdk",
          "cachedResultName": "Sathi's Review Data"
        },
        "combineFilters": "OR"
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "0cf83690-edcb-44fd-9e52-09fff9ec443d",
      "name": "Log New Review",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2208,
        -16
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $('Process Each Review').item.json.date }}",
            "Rating": "={{ $('Process Each Review').item.json.rating }}",
            "Review ID": "={{ $('Process Each Review').item.json.id }}",
            "Review Text": "={{ $('Process Each Review').item.json.review }}",
            "Competitor URL": "={{ $('Process Each Review').item.json.appReviewUrl }}",
            "Reviewer Domain": "={{ $('Process Each Review').item.json.reviewer }}"
          },
          "schema": [
            {
              "id": "Review ID",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Review ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Competitor URL",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Competitor URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Rating",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Rating",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Reviewer Domain",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Reviewer Domain",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Website",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Website",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Review Text",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Review Text",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Contact Name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Contact Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Contact Title",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Contact Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Contact Email",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Contact Email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Draft-Email",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Draft-Email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1c_HPrB7PZdZJRSjIiG5GIk2b9UoLjfSyt4BUP2FKNCI/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1c_HPrB7PZdZJRSjIiG5GIk2b9UoLjfSyt4BUP2FKNCI",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1c_HPrB7PZdZJRSjIiG5GIk2b9UoLjfSyt4BUP2FKNCI/edit?usp=drivesdk",
          "cachedResultName": "Sathi's Review Data"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "81aa74dd-1903-4366-a0f9-21a98a37b678",
      "name": "Extract Clean Domain",
      "type": "n8n-nodes-base.code",
      "position": [
        3072,
        -16
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Grab the #1 organic link from our Google Search\nlet rawUrl = $input.item.json.organic[0].link;\n\n// Use regex to strip http://, https://, www. and any sub-pages\nlet cleanDomain = rawUrl.replace(/^(?:https?:\\/\\/)?(?:www\\.)?/i, \"\").split('/')[0];\n\n// Add the clean domain to your existing data so you don't lose the review info!\n$input.item.json.domain = cleanDomain;\n\nreturn $input.item;"
      },
      "typeVersion": 2
    },
    {
      "id": "482533f9-83d5-4efe-8b2a-bb28aa5c5e20",
      "name": "Hunter: Find Contact Info",
      "type": "n8n-nodes-base.hunter",
      "position": [
        3392,
        -16
      ],
      "parameters": {
        "limit": 1,
        "domain": "={{ $json.domain }}",
        "filters": {}
      },
      "credentials": {
        "hunterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "0d55243f-ad7e-47d7-9b18-156285358f9c",
      "name": "Generate Outreach Draft",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        3680,
        -16
      ],
      "parameters": {
        "text": "=Write a highly personalized, brief cold email to {{ $('Hunter: Find Contact Info').item.json.first_name || 'the team' }} at {{ $('Extract Clean Domain').item.json.domain }}. \n\nThey just left this review on a competitor's app: \"{{ $('Process Each Review').item.json.review }}\"\n\nAcknowledge their exact frustration with the competitor and pitch SATHI as the reliable, professional solution to their problem. Keep it under 100 words.",
        "batching": {},
        "promptType": "define"
      },
      "typeVersion": 1.9
    },
    {
      "id": "99ee23d4-ba4f-4f81-9f35-6bd551c4341e",
      "name": "Filter 4 Stars or Less",
      "type": "n8n-nodes-base.filter",
      "position": [
        1008,
        -16
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "b2200cbc-6c0c-438f-82a4-86b35dbe3b42",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.reviewer }}",
              "rightValue": ""
            },
            {
              "id": "eba39924-c644-476f-88a2-56dbfadfbe03",
              "operator": {
                "type": "number",
                "operation": "lte"
              },
              "leftValue": "={{ $json.rating }}",
              "rightValue": 4
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "05ae7244-250d-4a12-a562-865efb117a5a",
      "name": "Trigger Daily",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -192,
        -16
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 10,
              "triggerAtMinute": 6
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "ebcc19ea-9d6c-4b4a-8de0-df52fe92d9e7",
      "name": "Set Target App URLs",
      "type": "n8n-nodes-base.code",
      "position": [
        80,
        -16
      ],
      "parameters": {
        "jsCode": "return [\n  { url: \"https://apps.shopify.com/goaffpro/reviews\" },\n  { url: \"https://apps.shopify.com/collabs/reviews\" },\n  { url: \"https://apps.shopify.com/refersion/reviews\" }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "263f8642-18e4-4eeb-a247-422546ec1bd4",
      "name": "Scrape Shopify Reviews",
      "type": "@apify/n8n-nodes-apify.apify",
      "position": [
        400,
        -16
      ],
      "parameters": {
        "actorId": {
          "__rl": true,
          "mode": "list",
          "value": "0PFewmVCG49Vuvg1k",
          "cachedResultUrl": "https://console.apify.com/actors/0PFewmVCG49Vuvg1k/input",
          "cachedResultName": "Shopify App Reviews Scraper (powerai/shopify-app-reviews-scraper)"
        },
        "operation": "Run actor and get dataset",
        "customBody": "={\n  \"appReviewUrl\": \"{{ $json.url }}\",\n  \"maxItems\": 2\n}"
      },
      "credentials": {
        "apifyApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "730f1aef-8732-4078-99cd-4da8b0a19707",
      "name": "Remove Duplicate Reviews",
      "type": "n8n-nodes-base.removeDuplicates",
      "position": [
        736,
        -16
      ],
      "parameters": {
        "compare": "selectedFields",
        "options": {},
        "fieldsToCompare": "id"
      },
      "typeVersion": 2
    },
    {
      "id": "2f82fd49-7052-4193-b702-346a74018d81",
      "name": "If Review is New",
      "type": "n8n-nodes-base.if",
      "position": [
        1968,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "6bb41ab3-53db-4494-9455-cc24be1fba02",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              },
              "leftValue": "={{ $json['Review ID'] }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "ddf89555-e0fc-4c8c-8364-20ab5c2d1c7b",
      "name": "Search Google for Website",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2800,
        -16
      ],
      "parameters": {
        "url": "https://google.serper.dev/search",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"q\": \"{{ $('Process Each Review').item.json.reviewer }} official website\"\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "7fe47769-e3bf-4ec0-8597-35d5439abbf1",
      "name": "Save Email as Draft",
      "type": "n8n-nodes-base.gmail",
      "position": [
        4032,
        -16
      ],
      "parameters": {
        "message": "={{$json.text.substring($json.text.indexOf('\\n')).trim() }}",
        "options": {
          "sendTo": "={{ $('Hunter: Find Contact Info').item.json.value }}"
        },
        "subject": "={{ $json.text.split('\\n')[0].replace(/Subject:\\s*/i, '').trim() }}",
        "resource": "draft"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "e34f8227-4172-40ce-9967-27d306bdbe98",
      "name": "Update Contact Info",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        4320,
        -16
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "=Draft Created",
            "Website": "={{ $('Extract Clean Domain').item.json.domain }}",
            "Review ID": "={{ $('Process Each Review').item.json.id }}",
            "Draft-Email": "={{ $('Generate Outreach Draft').item.json.text }}",
            "Contact Name": "={{ $('Hunter: Find Contact Info').item.json.first_name || 'The Team' }}",
            "Contact Email": "={{ $('Hunter: Find Contact Info').item.json.value }}",
            "Contact Title": "={{ $('Hunter: Find Contact Info').item.json.position_raw || 'Store Owner' }}"
          },
          "schema": [
            {
              "id": "Review ID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Review ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Competitor URL",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Competitor URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Rating",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Rating",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Reviewer Domain",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Reviewer Domain",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Website",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Website",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Review Text",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Review Text",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Contact Name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Contact Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Contact Title",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Contact Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Contact Email",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Contact Email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Draft-Email",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Draft-Email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Review ID"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1c_HPrB7PZdZJRSjIiG5GIk2b9UoLjfSyt4BUP2FKNCI/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1c_HPrB7PZdZJRSjIiG5GIk2b9UoLjfSyt4BUP2FKNCI",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1c_HPrB7PZdZJRSjIiG5GIk2b9UoLjfSyt4BUP2FKNCI/edit?usp=drivesdk",
          "cachedResultName": "Sathi's Review Data"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "611a96c3-635b-4b85-a1df-bb4e55ca3596",
      "name": "Draft Ready Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        4592,
        -16
      ],
      "parameters": {
        "text": "=\u2705 Draft ready for {{ $json[\"Contact Name\"] || \"The Team\" }} at {{ $json[\"Website\"] || \"Unknown Website\" }}",
        "select": "channel",
        "blocksUi": "={{\nJSON.stringify({\n  \"blocks\": [\n    {\n      \"type\": \"header\",\n      \"text\": {\n        \"type\": \"plain_text\",\n        \"text\": \"\u2705 Outreach Draft Ready\",\n        \"emoji\": true\n      }\n    },\n    {\n      \"type\": \"section\",\n      \"text\": {\n        \"type\": \"mrkdwn\",\n        \"text\": \"*Contact enrichment and personalized email generation are complete.*\"\n      }\n    },\n    {\n      \"type\": \"section\",\n      \"fields\": [\n        {\n          \"type\": \"mrkdwn\",\n          \"text\": \"*Website:*\\n\" + ($json[\"Website\"] || \"N/A\")\n        },\n        {\n          \"type\": \"mrkdwn\",\n          \"text\": \"*Contact Name:*\\n\" + ($json[\"Contact Name\"] || \"The Team\")\n        },\n        {\n          \"type\": \"mrkdwn\",\n          \"text\": \"*Contact Title:*\\n\" + ($json[\"Contact Title\"] || \"N/A\")\n        },\n        {\n          \"type\": \"mrkdwn\",\n          \"text\": \"*Contact Email:*\\n\" + ($json[\"Contact Email\"] || \"N/A\")\n        }\n      ]\n    },\n    {\n      \"type\": \"section\",\n      \"text\": {\n        \"type\": \"mrkdwn\",\n        \"text\": \"*Generated Outreach Draft:*\\n```\" + ($json[\"Draft-Email\"] || \"Draft not found\") + \"```\"\n      }\n    },\n    {\n      \"type\": \"context\",\n      \"elements\": [\n        {\n          \"type\": \"mrkdwn\",\n          \"text\": \"Status: `\" + ($json[\"Status\"] || \"Draft Created\") + \"` \u2022 Review ID: `\" + ($json[\"Review ID\"] || \"N/A\") + \"`\"\n        }\n      ]\n    }\n  ]\n})\n}}",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C091U351YLU",
          "cachedResultName": "customer-support"
        },
        "messageType": "block",
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.4
    },
    {
      "id": "db652eba-ec16-41d1-815d-09e4d9147d38",
      "name": "Alert New Review Detected",
      "type": "n8n-nodes-base.slack",
      "position": [
        2496,
        -16
      ],
      "parameters": {
        "text": "=\ud83d\udea8 New review detected: {{ $json[\"Reviewer Domain\"] || \"Unknown Brand\" }} - {{ $json[\"Rating\"] || \"N/A\" }}\u2b50",
        "select": "channel",
        "blocksUi": "={{\nJSON.stringify({\n  \"blocks\": [\n    {\n      \"type\": \"header\",\n      \"text\": {\n        \"type\": \"plain_text\",\n        \"text\": \"\ud83d\udea8 New Review Detected\",\n        \"emoji\": true\n      }\n    },\n    {\n      \"type\": \"section\",\n      \"fields\": [\n        {\n          \"type\": \"mrkdwn\",\n          \"text\": \"*Brand / Reviewer:*\\n\" + ($json[\"Reviewer Domain\"] || \"N/A\")\n        },\n        {\n          \"type\": \"mrkdwn\",\n          \"text\": \"*Rating:*\\n\" + ($json[\"Rating\"] || \"N/A\") + \" \u2b50\"\n        },\n        {\n          \"type\": \"mrkdwn\",\n          \"text\": \"*Review Date:*\\n\" + ($json[\"Date\"] || \"N/A\")\n        },\n        {\n          \"type\": \"mrkdwn\",\n          \"text\": \"*Status:*\\nNew Review Detected\"\n        }\n      ]\n    },\n    {\n      \"type\": \"section\",\n      \"text\": {\n        \"type\": \"mrkdwn\",\n        \"text\": \"*Competitor Review Page:*\\n\" + ($json[\"Competitor URL\"] || \"N/A\")\n      }\n    },\n    {\n      \"type\": \"section\",\n      \"text\": {\n        \"type\": \"mrkdwn\",\n        \"text\": \"*Review Text:*\\n> \" + ($json[\"Review Text\"] || \"N/A\")\n      }\n    },\n    {\n      \"type\": \"context\",\n      \"elements\": [\n        {\n          \"type\": \"mrkdwn\",\n          \"text\": \"Review ID: `\" + ($json[\"Review ID\"] || \"N/A\") + \"`\"\n        }\n      ]\n    }\n  ]\n})\n}}",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C091U351YLU",
          "cachedResultName": "customer-support"
        },
        "messageType": "block",
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.4
    },
    {
      "id": "0e627184-1d8d-4326-ac48-e8ff4ca394c8",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -240,
        -160
      ],
      "parameters": {
        "color": "#E5C2C2",
        "width": 208,
        "height": 288,
        "content": "## 1. Trigger Daily\n\n### Purpose\nThis node starts the workflow automatically every day.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "c395f113-62cf-4ddf-a173-3c5f4071a8d9",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -16,
        -288
      ],
      "parameters": {
        "color": "#ECD0DE",
        "width": 288,
        "height": 416,
        "content": "## 2. Set Target App URLs\n\n### Purpose\nThis node defines the competitor Shopify app review pages that should be monitored.\n\n### How It Works\nThe Code node returns a list of Shopify review URLs.\n\nEach URL is passed as a separate item to the next node.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "8e5777fe-5110-47b0-b47b-8e3fccab3644",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        288,
        -400
      ],
      "parameters": {
        "color": "#DAC7E6",
        "width": 336,
        "height": 528,
        "content": "## 3. Scrape Shopify Reviews\n\n### Purpose\nThis node collects recent reviews from each competitor Shopify app review page.\n\n### How It Works\nThe Apify actor receives the review page URL and scrapes review data from that page.  \nIt returns details such as:\n\n- Review ID\n- Rating\n- Review date\n- Reviewer / brand name\n- Review text\n- Review permalink\n- Competitor app URL\n"
      },
      "typeVersion": 1
    },
    {
      "id": "d3b1b06f-dd72-4661-adfb-dc44a485bcb2",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        -288
      ],
      "parameters": {
        "color": "#C9D7E9",
        "width": 272,
        "height": 416,
        "content": "## 4. Remove Duplicate Reviews\n\n### Purpose\nThis node removes duplicate review records from the scraped data.\n\n### How It Works\nIt compares incoming items using the `id` field.  \nIf the same review appears more than once, only one copy is kept.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "aeb169f7-ba11-4884-b88a-f9d233ea1a46",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        928,
        -464
      ],
      "parameters": {
        "color": "#D2ECD0",
        "width": 304,
        "height": 592,
        "content": "## 5. Filter 4 Stars or Less\n\n### Purpose\nThis node keeps only negative or neutral reviews.\n\n### How It Works\nThe Filter node checks two conditions:\n\n- The reviewer field is not empty\n- The review rating is less than or equal to 4\n\nOnly reviews that match both conditions continue.\n\n### Why It Is Used\nThe workflow is focused on finding merchants who may be unhappy with competitor platforms.  \nLow-rated reviews are more likely to contain pain points and sales opportunities."
      },
      "typeVersion": 1
    },
    {
      "id": "21ab330e-2476-4652-bc88-493038a9259c",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1248,
        -240
      ],
      "parameters": {
        "color": "#ECDEC5",
        "width": 304,
        "height": 368,
        "content": "## 6. Process Each Review\n\n### Purpose\nThis node processes reviews one by one.\n\n### How It Works\nThe Split In Batches node takes the filtered review list and sends one review at a time through the workflow."
      },
      "typeVersion": 1
    },
    {
      "id": "dbdb9d76-7634-4e64-96a4-fabf8328e7ff",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1568,
        -320
      ],
      "parameters": {
        "color": "#ECC5C5",
        "width": 320,
        "height": 448,
        "content": "## 7. Check Existing Reviews\n\n### Purpose\nThis node checks whether the review already exists in the Google Sheet.\n\n### How It Works\nThe Google Sheets node searches the master sheet using the current review\u2019s `Review ID`.\n\nIf the review ID already exists, it means the review was already processed earlier.  \nIf no matching row is found, the review is treated as new."
      },
      "typeVersion": 1
    },
    {
      "id": "ceeafbf1-431f-4c20-8d70-a7dd4621c0ba",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1904,
        -256
      ],
      "parameters": {
        "color": "#C9A9CB",
        "height": 384,
        "content": "## 8. If Review is New\n\n### How It Works\nThe IF node checks whether the `Review ID` returned from Google Sheets is empty.\n\n- If empty: the review is new and continues forward\n- If not empty: the review already exists and is skipped"
      },
      "typeVersion": 1
    },
    {
      "id": "5b8a2b09-7549-45d1-a83d-d17302041c34",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2160,
        -176
      ],
      "parameters": {
        "color": "#ECD0DE",
        "height": 304,
        "content": "## 9. Log New Review\n\n### Purpose\nThis node saves the new review into the Google Sheet.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "4ffd36af-af0e-4d13-92ae-947ddbca0de8",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2416,
        -448
      ],
      "parameters": {
        "color": "#DAC7E6",
        "width": 288,
        "height": 576,
        "content": "## 10. Alert New Review Detected\n\n### Purpose\nThis node sends a Slack alert when a new review is found.\n\n### How It Works\nThe Slack node sends a formatted block message to the selected Slack channel.  \nThe message includes:\n\n- Brand / reviewer name\n- Rating\n- Review date\n- Competitor review page\n- Review text\n- Review ID"
      },
      "typeVersion": 1
    },
    {
      "id": "e7cc426d-6b52-4247-b108-f2821c49edfb",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2720,
        -480
      ],
      "parameters": {
        "color": "#C9D7E9",
        "width": 272,
        "height": 608,
        "content": "## 11. Search Google for Website\n\n### Purpose\nThis node finds the official website of the reviewer or brand.\n\n### How It Works\nThe HTTP Request node sends a search request to Serper using this query format:\n\nreviewer name + official website\n\nThe search results return possible official websites related to the reviewer.\n\n### Why It Is Used\nThe workflow needs the merchant\u2019s website before it can find contact details and generate personalized outreach."
      },
      "typeVersion": 1
    },
    {
      "id": "6559c268-e2c4-488c-8652-be9da7b5208c",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3008,
        -416
      ],
      "parameters": {
        "color": "#D2ECD0",
        "width": 304,
        "height": 544,
        "content": "## 12. Extract Clean Domain\n\n### Purpose\nThis node extracts a clean domain from the Google search result.\n\n### How It Works\nThe Code node takes the first organic search result URL and removes:\n\n- https://\n- http://\n- www.\n- Extra page paths\n\n### Why It Is Used\nHunter needs a clean domain to search for contact information accurately.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "30b261e2-4f4d-4840-a135-b694720b52ae",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3328,
        -368
      ],
      "parameters": {
        "color": "#ECDEC5",
        "width": 304,
        "height": 496,
        "content": "## 13. Hunter: Find Contact Info\n\n### Purpose\nThis node finds a contact email for the merchant\u2019s website.\n\n### How It Works\nThe Hunter node receives the clean domain and searches for available email addresses connected to that domain.\n\n### Why It Is Used\nThe workflow needs a valid contact email before it can create a sales outreach draft."
      },
      "typeVersion": 1
    },
    {
      "id": "dc5ccb3b-e4ac-4f96-bb5c-a9983b227bd0",
      "name": "Sticky Note13",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3648,
        -240
      ],
      "parameters": {
        "color": "#E5C2C2",
        "width": 288,
        "height": 368,
        "content": "## 14. Generate Outreach Draft\n\n### Purpose\nThis node creates a personalized cold email based on the review.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "a076373e-8910-494f-9ffe-bc19983e4d28",
      "name": "Sticky Note14",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3952,
        -272
      ],
      "parameters": {
        "color": "#C9A9CB",
        "width": 288,
        "height": 400,
        "content": "## 15. Save Email as Draft\n\n### Purpose\nThis node saves the AI-generated outreach email as a Gmail draft.\n\n### Why It Is Used\nThis keeps a human approval step in the process.  \nThe sales team can review, edit, and send the email manually."
      },
      "typeVersion": 1
    },
    {
      "id": "8b085122-065e-47d4-926d-2aa97deb5761",
      "name": "Sticky Note15",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4256,
        -368
      ],
      "parameters": {
        "color": "#D2ECD0",
        "height": 496,
        "content": "## 16. Update Contact Info\n\n### Purpose\nThis node updates the Google Sheet with contact and outreach details.\n\nIt adds:\n\n- Website\n- Contact name\n- Contact title\n- Contact email\n- Status\n- Draft email text\n"
      },
      "typeVersion": 1
    },
    {
      "id": "5bcfa315-cb6e-4866-98d0-6777d59b51a1",
      "name": "Sticky Note16",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4512,
        -400
      ],
      "parameters": {
        "color": "#ECDEC5",
        "width": 256,
        "height": 528,
        "content": "## 18. Draft Ready Alert\n\n### Purpose\nThis node notifies the team when the outreach draft is ready.\n\n### How It Works\nThe Slack node sends a formatted message with:\n\n- Website\n- Contact name\n- Contact title\n- Contact email\n- Generated outreach draft\n- Review ID\n- Current status"
      },
      "typeVersion": 1
    },
    {
      "id": "8268e87c-3099-45c3-8cca-47b20183102b",
      "name": "Sticky Note17",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -224,
        -688
      ],
      "parameters": {
        "color": "#E8DFA6",
        "width": 848,
        "height": 272,
        "content": "# Workflow Overview\n\n### This workflow automatically monitors competitor Shopify app reviews every day.  \n### It filters low-rated reviews, removes duplicates, and checks Google Sheets to process only new opportunities.  \n### For each new review, it logs the review, alerts the team in Slack, finds the merchant\u2019s website, enriches contact details using Hunter, and generates a personalized AI outreach email.  \n### The email is saved as a Gmail draft, the Google Sheet is updated, and the team receives a final Slack alert when the draft is ready."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "0e2f06f7-b971-470f-bd5a-db242d67ef5e",
  "connections": {
    "Trigger Daily": {
      "main": [
        [
          {
            "node": "Set Target App URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log New Review": {
      "main": [
        [
          {
            "node": "Alert New Review Detected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Review is New": {
      "main": [
        [
          {
            "node": "Log New Review",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Process Each Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Draft Ready Alert": {
      "main": [
        [
          {
            "node": "Process Each Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Generate Outreach Draft",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Process Each Review": {
      "main": [
        [],
        [
          {
            "node": "Check Existing Reviews",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Email as Draft": {
      "main": [
        [
          {
            "node": "Update Contact Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Target App URLs": {
      "main": [
        [
          {
            "node": "Scrape Shopify Reviews",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Contact Info": {
      "main": [
        [
          {
            "node": "Draft Ready Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Clean Domain": {
      "main": [
        [
          {
            "node": "Hunter: Find Contact Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Existing Reviews": {
      "main": [
        [
          {
            "node": "If Review is New",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter 4 Stars or Less": {
      "main": [
        [
          {
            "node": "Process Each Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Shopify Reviews": {
      "main": [
        [
          {
            "node": "Remove Duplicate Reviews",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Outreach Draft": {
      "main": [
        [
          {
            "node": "Save Email as Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Duplicate Reviews": {
      "main": [
        [
          {
            "node": "Filter 4 Stars or Less",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Alert New Review Detected": {
      "main": [
        [
          {
            "node": "Search Google for Website",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hunter: Find Contact Info": {
      "main": [
        [
          {
            "node": "Generate Outreach Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Google for Website": {
      "main": [
        [
          {
            "node": "Extract Clean Domain",
            "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 workflow runs daily to scrape competitor Shopify app reviews via Apify, logs and deduplicates new low-rated reviews in Google Sheets, looks up the reviewer’s website with Serper, enriches contacts with Hunter, generates a personalized outreach email with OpenAI, saves it as…

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

More Web Scraping workflows → · Browse all categories →

Related workflows

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

Web Scraping

This n8n workflow automates the process of collecting job and decision-maker data, crafting AI-generated referral messages, and drafting them in Gmail—all using a combination of Apify, Google Sheets,

Google Sheets, Chain Llm, Google Gemini Chat +3
Web Scraping

[VICTOR] Google Maps Extractor. Uses formTrigger, outputParserStructured, lmChatOpenAi, httpRequest. Event-driven trigger; 21 nodes.

Form Trigger, Output Parser Structured, OpenAI Chat +5
Web Scraping

Automate Sales Meeting Prep With Ai & Apify Sent To Whatsapp. Uses gmail, googleCalendar, lmChatOpenAi, informationExtractor. Event-driven trigger; 61 nodes.

Gmail, Google Calendar, OpenAI Chat +5
Web Scraping

This n8n template builds a meeting assistant that compiles timely reminders of upcoming meetings filled with email history and recent LinkedIn activity of other people on the invite. This is then disc

Gmail, Google Calendar, OpenAI Chat +5
Web Scraping

Community nodes are used in this workflow. B2B Sales Teams & SDRs Recruitment Agencies & Tech Recruiters Startup Founders Growth Marketing Teams Scrape Hiring Signals: The workflow starts by using an

Google Sheets, Google Gemini Chat, Output Parser Structured +3