AutomationFlowsAI & RAG › Scrape Linkedin B2b Leads with Apify and Gpt-4 and Approve Emails in Sheets

Scrape Linkedin B2b Leads with Apify and Gpt-4 and Approve Emails in Sheets

Bymoosa @moosa on n8n.io

Fully production-ready B2B lead outreach pipeline that: Takes industry keywords from a form trigger (or you can manually add rows to Google Sheets) Scrapes targeted LinkedIn leads using Apify (peakydev~leads-scraper-ppe actor) Filters for valid emails Automatically creates…

Webhook trigger★★★★☆ complexityAI-powered27 nodesHTTP RequestAgentGmailHubSpotGoogle SheetsForm TriggerOpenAI Chat
AI & RAG Trigger: Webhook Nodes: 27 Complexity: ★★★★☆ AI nodes: yes Added:

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

This workflow follows the Agent → Form Trigger 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
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "a686b9b4-09c6-4f5e-9917-6d590648e4f9",
      "name": "post Apify data scrap",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        304,
        0
      ],
      "parameters": {
        "url": "https://api.apify.com/v2/acts/peakydev~leads-scraper-ppe/runs?token=YOUR_TOKEN_HERE",
        "method": "POST",
        "options": {
          "redirect": {
            "redirect": {
              "maxRedirects": 5
            }
          }
        },
        "jsonBody": "{\n    \"includeEmails\": true,\n    \"industry\": [\n        \"Primary and Secondary Education\"\n    ],\n    \"totalResults\": 10000\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "Application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "0f495a64-b00e-46cf-b3f9-69beb2a32dbf",
      "name": "Get Apify recent run data",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        592,
        0
      ],
      "parameters": {
        "url": "https://api.apify.com/v2/acts/peakydev~leads-scraper-ppe/runs/last/dataset/items?token=YOUR_TOKEN_HERE",
        "options": {
          "redirect": {
            "redirect": {
              "maxRedirects": 5
            }
          }
        },
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "Application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "bde68ef5-7b9f-48c8-b3e3-5215c50290ba",
      "name": "generate Ai email",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2224,
        0
      ],
      "parameters": {
        "text": "=User Input:  {{ $item(\"0\").$node[\"JSON Stringifier\"].json[\"userInput\"] }}",
        "options": {
          "systemMessage": "=You are an expert Automation Engineer & B2B Outreach Specialist.\n\nYou will receive USER INPUT as a JSON array of lead objects.\nEach object represents one lead with personal and company details.\n\nYour task:\n- Read the USER INPUT carefully\n- Generate ONE personalized, professional cold email per lead\n- Use the lead\u2019s:\n  - fullName\n  - position\n  - organizationName\n  - organizationIndustry\n  - organizationDescription\n  - organizationWebsite (if helpful)\n- The email must feel human, relevant, and non-salesy\n- Focus on how automation can save time, reduce manual work, and improve efficiency\n- Tailor the value proposition based on the company\u2019s industry (e.g. Travel, SaaS, E-commerce, Agency)\n\nEmail requirements:\n- Friendly and professional tone\n- Short, clear paragraphs\n- No hype, no buzzwords\n- One clear CTA (call to action)\n- Do NOT mention scraping, APIs, or data sources\n- Do NOT say \u201cI found you on LinkedIn\u201d\n- Do NOT invent facts beyond the provided data\n\nEmail structure:\n1. Subject line (short & relevant)\n2. Personalized greeting using fullName\n3. One sentence showing you understand their business\n4. How automation can help their specific type of company\n5. What you do as an automation engineer (brief)\n6. Soft CTA (call, reply, or quick chat)\n7. Professional sign-off\n\nOutput format (VERY IMPORTANT):\nReturn ONLY the email content in plain text like this:\n\nSubject: <subject line>\n\nHi <Full Name>,\n\n<email body>\n\nBest regards,  \n<Your Name>  \nAutomation Engineer\n\nIf multiple users are provided, generate a separate email for EACH user.\n"
        },
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "0b0623d0-f30e-48db-ab9f-29d1ac069b69",
      "name": "make separate json of each keyword",
      "type": "n8n-nodes-base.code",
      "position": [
        -304,
        -16
      ],
      "parameters": {
        "jsCode": "// Input from previous node\nconst input = $input.item.json;\n\n// Step 1: Get the keyword string\nconst keywordString = input.Keyword || \"\";\n\n// Step 2: Split string into individual keywords\nconst keywordsArray = keywordString\n  .split(/\\s{2,}/)       // split by 2 or more spaces\n  .map(k => k.trim())    // remove extra spaces\n  .filter(k => k !== ''); // remove empty strings\n\n// Step 3: Convert into n8n items format\nreturn keywordsArray.map(k => ({\n  json: { keyword: k }\n}));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "45214227-bb86-476d-9cfc-bcc68c5e426f",
      "name": "If email exist",
      "type": "n8n-nodes-base.if",
      "position": [
        1168,
        16
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "8060c190-0a72-4c9b-a5c7-451ec8fe6a9c",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.email }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "f02a9fd2-897d-4c60-a34a-9582a47b4556",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -608,
        448
      ],
      "parameters": {
        "color": 4,
        "width": 1184,
        "height": 384,
        "content": "# Approved Leads Email Workflow"
      },
      "typeVersion": 1
    },
    {
      "id": "9de6efad-ecb0-4c4f-a29c-40b0eba54055",
      "name": "\ud83d\udce5 Approved Leads Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -464,
        576
      ],
      "parameters": {
        "path": "webhook/approved",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "lastNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "70ae0f09-b164-44fa-8ae1-c4f7c518a767",
      "name": "\ud83d\udd27 Normalize Lead Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -176,
        576
      ],
      "parameters": {
        "jsCode": "const webhookData = $input.item.json;\n\n// Extract body data\nconst data = webhookData.body;\n\n// Normalize data according to your sheet columns\nconst normalizedData = {\n  // Your sheet columns in exact order:\n  \n  // 1. organizationIndustry\n  organizationIndustry: data.organizationIndustry || \"\",\n  \n  // 2. email\n  email: data.email || \"\",\n  \n  // 3. city\n  city: data.city || \"\",\n  \n  // 4. state\n  state: data.state || \"\",\n  \n  // 5. country\n  country: data.country || \"\",\n  \n  // 6. organizationSize\n  organizationSize: data.organizationSize || \"\",\n  \n  // 7. organizationLinkedinUrl\n  organizationLinkedinUrl: data.organizationLinkedinUrl || \"\",\n  \n  // 8. organizationWebsiteUrl\n  organizationWebsiteUrl: data.organizationWebsiteUrl || \"\",\n  \n  // 9. organizationFoundedYear\n  organizationFoundedYear: data.organizationFoundedYear || \"\",\n  \n  // 10. organizationDescription\n  organizationDescription: data.organizationDescription || \"\",\n  \n  // 11. emailContent\n  emailContent: data.emailContent || \"\",\n  \n  // 12. Status (from webhook)\n  Status: data.status || \"\",\n  \n  // Additional useful data for email sending:\n  fullName: data.fullName || \"\",\n  position: data.position || \"\",\n  organizationName: data.organizationName || \"\",\n  \n  // Tracking\n  sheetRow: data.sheetRow,\n  eventId: data.eventId,\n  timestamp: data.timestamp\n};\n\n// Log for debugging\nconsole.log(\"Normalized Data:\", JSON.stringify(normalizedData, null, 2));\n\nreturn [{ json: normalizedData }];"
      },
      "typeVersion": 2
    },
    {
      "id": "b1dbaf04-48d2-437e-9966-ef4d6b08b8d7",
      "name": "\u2702\ufe0f Split Email Content",
      "type": "n8n-nodes-base.code",
      "position": [
        64,
        576
      ],
      "parameters": {
        "jsCode": "// Get the input data safely\nlet emailContent = \"\";\n\ntry {\n  // Method 1: Try to get data from common structures\n  const inputData = $input.item.json;\n  \n  if (inputData.body && inputData.body.emailContent) {\n    emailContent = inputData.body.emailContent;\n  } else if (inputData.emailContent) {\n    emailContent = inputData.emailContent;\n  } else if (inputData[0] && inputData[0].json && inputData[0].json.body && inputData[0].json.body.emailContent) {\n    emailContent = inputData[0].json.body.emailContent;\n  } else {\n    // Try to find email content in any format\n    const jsonStr = JSON.stringify(inputData);\n    const match = jsonStr.match(/\"emailContent\":\"([^\"]+)\"/);\n    if (match) {\n      emailContent = match[1].replace(/\\\\n/g, '\\n');\n    }\n  }\n} catch (error) {\n  console.log(\"Error getting data:\", error);\n  emailContent = \"\";\n}\n\nconsole.log(\"Email content length:\", emailContent.length);\n\n// Extract subject and body\nlet subject = \"Automation Proposal\";\nlet body = \"\";\n\nif (emailContent && emailContent.trim() !== \"\") {\n  // Find subject\n  const subjectMatch = emailContent.match(/Subject:\\s*(.+?)(?:\\n\\n|\\r\\n\\r\\n|$)/im);\n  if (subjectMatch) {\n    subject = subjectMatch[1].trim();\n    // Remove subject line\n    body = emailContent.replace(/Subject:\\s*.+?(\\n\\n|\\r\\n\\r\\n|$)/im, '').trim();\n  } else {\n    body = emailContent.trim();\n  }\n}\n\nconsole.log(\"Subject:\", subject);\nconsole.log(\"Body length:\", body.length);\n\n// Return the result\nreturn [{\n  json: {\n    subject: subject,\n    body: body\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "98c5b879-f320-4256-a412-36469668e49f",
      "name": "\ud83d\udce4 Send Approved Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        304,
        576
      ],
      "parameters": {
        "sendTo": "=muhammadmoosa.abc1@gmail.com",
        "message": "={{ $json.body }}",
        "options": {},
        "subject": "={{ $json.subject }}",
        "emailType": "text"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "6b865333-8ab3-46fd-a2d5-2f0b92d9ae7e",
      "name": "Data Batcher",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        0,
        -16
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "0dbaa915-0aeb-4b29-92db-29f3ca5aef4d",
      "name": "Lead Data Batcher",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        880,
        0
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "17642f70-ca69-49ac-a08d-683b788b2e09",
      "name": "JSON Stringifier",
      "type": "n8n-nodes-base.code",
      "position": [
        1456,
        0
      ],
      "parameters": {
        "jsCode": "// Get all incoming items (n8n compatible)\nconst items = $input.all();\n\n// Convert incoming JSON to string\nconst userInput = JSON.stringify(\n  items.map(item => item.json)\n);\n\n// Return as output\nreturn [\n  {\n    json: {\n      userInput\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "a1677126-07b7-4eed-8ddd-1accef15be52",
      "name": "HubSpot Company Creator",
      "type": "n8n-nodes-base.hubspot",
      "position": [
        1744,
        0
      ],
      "parameters": {
        "name": "={{ $item(\"0\").$node[\"If email exist\"].json[\"organizationName\"] }}",
        "resource": "company",
        "authentication": "appToken",
        "additionalFields": {
          "websiteUrl": "={{ $item(\"0\").$node[\"If email exist\"].json[\"organizationWebsite\"] }}",
          "description": "={{ $item(\"0\").$node[\"If email exist\"].json[\"organizationDescription\"] }}",
          "yearFounded": "={{ $item(\"0\").$node[\"If email exist\"].json[\"organizationFoundedYear\"] }}",
          "linkedInCompanyPage": "={{ $item(\"0\").$node[\"If email exist\"].json[\"organizationLinkedinUrl\"] }}"
        }
      },
      "credentials": {
        "hubspotAppToken": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "e83d869f-c9be-41c4-ac56-e4f082afd13c",
      "name": "HubSpot Contact Sync",
      "type": "n8n-nodes-base.hubspot",
      "position": [
        1968,
        0
      ],
      "parameters": {
        "email": "={{ $item(\"0\").$node[\"If email exist\"].json[\"email\"] }}",
        "options": {},
        "authentication": "appToken",
        "additionalFields": {
          "city": "={{ $item(\"0\").$node[\"If email exist\"].json[\"city\"] }}",
          "country": "={{ $item(\"0\").$node[\"If email exist\"].json[\"country\"] }}",
          "jobTitle": "={{ $item(\"0\").$node[\"If email exist\"].json[\"position\"] }}",
          "lastName": "={{ $item(\"0\").$node[\"If email exist\"].json[\"lastName\"] }}",
          "firstName": "={{ $item(\"0\").$node[\"If email exist\"].json[\"firstName\"] }}",
          "companySize": "={{ $item(\"0\").$node[\"If email exist\"].json[\"organizationSize\"] }}",
          "linkedinUrl": "={{ $item(\"0\").$node[\"If email exist\"].json[\"linkedinUrl\"] }}",
          "stateRegion": "={{ $item(\"0\").$node[\"If email exist\"].json[\"state\"] }}",
          "associatedCompanyId": "={{ $json.companyId }}"
        }
      },
      "credentials": {
        "hubspotAppToken": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "5a61af7b-a3ee-464f-a579-7212564b3018",
      "name": "Leads Log",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2608,
        0
      ],
      "parameters": {
        "columns": {
          "value": {
            "city": "={{ $item(\"0\").$node[\"If email exist\"].json[\"city\"] }}",
            "email": "={{ $item(\"0\").$node[\"If email exist\"].json[\"email\"] }}",
            "state": "={{ $item(\"0\").$node[\"If email exist\"].json[\"state\"] }}",
            "Status": "Pending",
            "country": "={{ $item(\"0\").$node[\"If email exist\"].json[\"country\"] }}",
            "emailContent": "={{ $json.output }}",
            "organizationSize": "={{ $item(\"0\").$node[\"If email exist\"].json[\"organizationSize\"] }}",
            "organizationIndustry": "={{ $('If email exist').item.json.organizationIndustry }}",
            "organizationWebsiteUrl": "={{ $item(\"0\").$node[\"If email exist\"].json[\"organizationWebsite\"] }}",
            "organizationDescription": "={{ $item(\"0\").$node[\"If email exist\"].json[\"organizationDescription\"] }}",
            "organizationFoundedYear": "={{ $item(\"0\").$node[\"If email exist\"].json[\"organizationFoundedYear\"] }}",
            "organizationLinkedinUrl": "={{ $item(\"0\").$node[\"If email exist\"].json[\"organizationLinkedinUrl\"] }}"
          },
          "schema": [
            {
              "id": "organizationIndustry",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "organizationIndustry",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "city",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "city",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "state",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "state",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "country",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "country",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "organizationSize",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "organizationSize",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "organizationLinkedinUrl",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "organizationLinkedinUrl",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "organizationWebsiteUrl",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "organizationWebsiteUrl",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "organizationFoundedYear",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "organizationFoundedYear",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "organizationDescription",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "organizationDescription",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "emailContent",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "emailContent",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "email"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1B5gPF93zBySVC9h2JzbCZXbVZvf5tEcLaoc6Xuyt3aQ/edit#gid=0",
          "cachedResultName": "Leads"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1B5gPF93zBySVC9h2JzbCZXbVZvf5tEcLaoc6Xuyt3aQ",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1B5gPF93zBySVC9h2JzbCZXbVZvf5tEcLaoc6Xuyt3aQ/edit?usp=drivesdk",
          "cachedResultName": "Linkdin Leads Scrapping"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "93a3faf9-d003-441b-901d-1bc3d8f1011b",
      "name": "Lead Campaign Setup",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -544,
        -16
      ],
      "parameters": {
        "options": {},
        "formTitle": "LinkedIn Leads Scraping Setup",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Keyword",
              "placeholder": "Keyword",
              "requiredField": true
            }
          ]
        },
        "formDescription": "This form configures automated LinkedIn lead extraction for targeted B2B marketing campaigns. By providing specific criteria below, our system will scrape high-quality leads from LinkedIn based on your business needs."
      },
      "typeVersion": 2.3
    },
    {
      "id": "1ca64694-06c4-471b-aadb-0ed0bdb20be7",
      "name": "Rejection Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -560,
        1088
      ],
      "parameters": {
        "path": "webhook/rejected",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "lastNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "a1e44d3a-983d-4f24-9cb4-be151ded07d3",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -624,
        944
      ],
      "parameters": {
        "color": 2,
        "width": 1280,
        "height": 464,
        "content": "# Rejection Follow-up Workflow\n"
      },
      "typeVersion": 1
    },
    {
      "id": "800b3f0f-1531-417d-b9ad-9a6ff4f3b7a5",
      "name": "Webhook Data Normalizer",
      "type": "n8n-nodes-base.code",
      "position": [
        -320,
        1088
      ],
      "parameters": {
        "jsCode": "const webhookData = $input.item.json;\n\n// Extract body data\nconst data = webhookData.body;\n\n// Normalize data according to your sheet columns\nconst normalizedData = {\n  // Your sheet columns in exact order:\n  \n  // 1. organizationIndustry\n  organizationIndustry: data.organizationIndustry || \"\",\n  \n  // 2. email\n  email: data.email || \"\",\n  \n  // 3. city\n  city: data.city || \"\",\n  \n  // 4. state\n  state: data.state || \"\",\n  \n  // 5. country\n  country: data.country || \"\",\n  \n  // 6. organizationSize\n  organizationSize: data.organizationSize || \"\",\n  \n  // 7. organizationLinkedinUrl\n  organizationLinkedinUrl: data.organizationLinkedinUrl || \"\",\n  \n  // 8. organizationWebsiteUrl\n  organizationWebsiteUrl: data.organizationWebsiteUrl || \"\",\n  \n  // 9. organizationFoundedYear\n  organizationFoundedYear: data.organizationFoundedYear || \"\",\n  \n  // 10. organizationDescription\n  organizationDescription: data.organizationDescription || \"\",\n  \n  // 11. emailContent\n  emailContent: data.emailContent || \"\",\n  \n  // 12. Status (from webhook)\n  Status: data.status || \"\",\n  \n  // Additional useful data for email sending:\n  fullName: data.fullName || \"\",\n  position: data.position || \"\",\n  organizationName: data.organizationName || \"\",\n  \n  // Tracking\n  sheetRow: data.sheetRow,\n  eventId: data.eventId,\n  timestamp: data.timestamp\n};\n\n// Log for debugging\nconsole.log(\"Normalized Data:\", JSON.stringify(normalizedData, null, 2));\n\nreturn [{ json: normalizedData }];"
      },
      "typeVersion": 2
    },
    {
      "id": "d19b6e66-09b6-4df1-ae4d-d9909cedcfe0",
      "name": "Rejection Data Stringifier",
      "type": "n8n-nodes-base.code",
      "position": [
        -80,
        1088
      ],
      "parameters": {
        "jsCode": "// Get all incoming items (n8n compatible)\nconst items = $input.all();\n\n// Convert incoming JSON to string\nconst userInput = JSON.stringify(\n  items.map(item => item.json)\n);\n\n// Return as output\nreturn [\n  {\n    json: {\n      userInput\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "d3ba5d1e-d1c7-4291-8c40-1abc4baa299b",
      "name": "Rejection Email Rewriter",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        112,
        1088
      ],
      "parameters": {
        "text": "=User input:  {{ $json.userInput }}",
        "options": {
          "systemMessage": "You are an expert Automation Engineer and B2B Outreach Copywriter.\n\nYou will receive USER INPUT as a JSON string.\nThe JSON may include:\n- previous lead data\n- current lead data\n- an emailContent field\n- a Status field that can be \"Rejected\"\n\nIMPORTANT CONTEXT:\nThe existing email was REJECTED.\nYour task is to REWRITE and IMPROVE the email with a different angle.\nDo NOT repeat the same phrasing, structure, or opening.\n\nYour goals:\n- Generate a NEW outreach email that feels more:\n  - Helpful\n  - Softer\n  - More value-focused\n  - Less salesy\n- Adapt tone to the lead\u2019s industry (Education, SaaS, Agency, etc.)\n- Emphasize assistance, clarity, and practical benefits\n- Avoid pressure, avoid aggressive CTAs\n\nRules:\n- Use ONLY data present in the USER INPUT\n- Do NOT mention rejection or status\n- Do NOT mention LinkedIn, scraping, or data sources\n- Do NOT invent personal details\n- Keep it short, polite, and respectful\n- One clear but low-friction CTA\n\nPersonalization:\n- If name is available, use it\n- Reference the organization\u2019s industry and context\n- Frame automation as support, not disruption\n\nEmail structure:\n1. New subject line (different from previous)\n2. Polite greeting\n3. Acknowledge their work context subtly\n4. Practical automation examples relevant to their industry\n5. Brief intro of yourself as an automation engineer\n6. Soft CTA (reply, short chat, or simple question)\n7. Professional sign-off\n\nOutput format (STRICT):\nReturn ONLY the updated email in plain text:\n\nSubject: <new subject>\n\nHi <Name>,\n\n<email body>\n\nBest regards,  \n<Your Name>  \nAutomation Engineer\n\nIf multiple users exist in the input, generate ONE updated email PER user.\nDo not include explanations, JSON, or commentary \u2014 only the email text.\n"
        },
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "ea6196c1-c0e2-48ac-b8fa-f33458a4e479",
      "name": "Update: Improved Email",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        464,
        1088
      ],
      "parameters": {
        "columns": {
          "value": {
            "email": "={{ $item(\"0\").$node[\"Webhook Data Normalizer\"].json[\"email\"] }}",
            "emailContent": "={{ $json.output }}"
          },
          "schema": [
            {
              "id": "organizationIndustry",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "organizationIndustry",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "city",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "city",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "state",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "state",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "country",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "country",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "organizationSize",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "organizationSize",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "organizationLinkedinUrl",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "organizationLinkedinUrl",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "organizationWebsiteUrl",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "organizationWebsiteUrl",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "organizationFoundedYear",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "organizationFoundedYear",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "organizationDescription",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "organizationDescription",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "emailContent",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "emailContent",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "email"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1B5gPF93zBySVC9h2JzbCZXbVZvf5tEcLaoc6Xuyt3aQ/edit#gid=0",
          "cachedResultName": "Leads"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1B5gPF93zBySVC9h2JzbCZXbVZvf5tEcLaoc6Xuyt3aQ",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1B5gPF93zBySVC9h2JzbCZXbVZvf5tEcLaoc6Xuyt3aQ/edit?usp=drivesdk",
          "cachedResultName": "Linkdin Leads Scrapping"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "f48b7143-491e-43c8-81d6-f9afd69bf6ed",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -624,
        -144
      ],
      "parameters": {
        "width": 3520,
        "height": 464,
        "content": "# LinkedIn Lead Generation Pipeline"
      },
      "typeVersion": 1
    },
    {
      "id": "fe109bb7-1ff3-4435-95ad-e1a26d5d56a9",
      "name": "LLM",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        2224,
        192
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5-mini",
          "cachedResultName": "gpt-5-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "b54d2535-c0c1-4df1-b784-c670dd2f4b91",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        112,
        1280
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5-mini",
          "cachedResultName": "gpt-5-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "c8f60827-fe7d-426b-b101-00cb5c392fc4",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1200,
        80
      ],
      "parameters": {
        "width": 400,
        "height": 1024,
        "content": "\n## How it works\n\n1. You submit lead keywords (industry, role, location) using the form trigger or by adding rows in Google Sheets.\n2. The workflow scrapes targeted LinkedIn leads via Apify and processes them in batches.\n3. Leads are filtered to keep only contacts with valid email addresses.\n4. Company and contact records are automatically created in HubSpot CRM.\n5. AI generates a personalized, non-salesy cold email for each lead, tailored to the company\u2019s industry.\n6. All leads and email drafts are logged in Google Sheets with a **Pending** status.\n7. You review leads directly inside Google Sheets:\n\n   * **Approve** \u2192 the email is sent automatically via Gmail.\n   * **Reject** \u2192 the email is rewritten with a softer, value-focused angle and updated in the same sheet row.\n8. This creates a full human-in-the-loop outreach system with traceable decisions and clean CRM data.\n\n---\n\n## Setup steps\n\n1. Import the workflow into n8n.\n2. Connect required credentials:\n\n   * Apify API token\n   * HubSpot App Token\n   * Google Sheets OAuth2\n   * Gmail OAuth2\n   * OpenAI API key\n3. Update placeholders:\n\n   * Google Sheet ID in the \u201cLeads Log\u201d node\n   * Your name and signature inside the AI prompts\n4. Activate the **Lead Campaign Setup** form trigger to start collecting leads.\n5. Review leads in Google Sheets and use approval or rejection actions to control email sending and rewriting.\n"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "LLM": {
      "ai_languageModel": [
        [
          {
            "node": "generate Ai email",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Leads Log": {
      "main": [
        [
          {
            "node": "Lead Data Batcher",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Data Batcher": {
      "main": [
        [],
        [
          {
            "node": "post Apify data scrap",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If email exist": {
      "main": [
        [
          {
            "node": "JSON Stringifier",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Lead Data Batcher",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JSON Stringifier": {
      "main": [
        [
          {
            "node": "HubSpot Company Creator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lead Data Batcher": {
      "main": [
        [
          {
            "node": "Data Batcher",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "If email exist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Rejection Email Rewriter",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Rejection Webhook": {
      "main": [
        [
          {
            "node": "Webhook Data Normalizer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "generate Ai email": {
      "main": [
        [
          {
            "node": "Leads Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lead Campaign Setup": {
      "main": [
        [
          {
            "node": "make separate json of each keyword",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HubSpot Contact Sync": {
      "main": [
        [
          {
            "node": "generate Ai email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "post Apify data scrap": {
      "main": [
        [
          {
            "node": "Get Apify recent run data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HubSpot Company Creator": {
      "main": [
        [
          {
            "node": "HubSpot Contact Sync",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook Data Normalizer": {
      "main": [
        [
          {
            "node": "Rejection Data Stringifier",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rejection Email Rewriter": {
      "main": [
        [
          {
            "node": "Update: Improved Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udce4 Send Approved Email": {
      "main": [
        []
      ]
    },
    "\ud83d\udd27 Normalize Lead Data": {
      "main": [
        [
          {
            "node": "\u2702\ufe0f Split Email Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Apify recent run data": {
      "main": [
        [
          {
            "node": "Lead Data Batcher",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rejection Data Stringifier": {
      "main": [
        [
          {
            "node": "Rejection Email Rewriter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u2702\ufe0f Split Email Content": {
      "main": [
        [
          {
            "node": "\ud83d\udce4 Send Approved Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udce5 Approved Leads Webhook": {
      "main": [
        [
          {
            "node": "\ud83d\udd27 Normalize Lead Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "make separate json of each keyword": {
      "main": [
        [
          {
            "node": "Data Batcher",
            "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

Fully production-ready B2B lead outreach pipeline that: Takes industry keywords from a form trigger (or you can manually add rows to Google Sheets) Scrapes targeted LinkedIn leads using Apify (peakydev~leads-scraper-ppe actor) Filters for valid emails Automatically creates…

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

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

This workflow enriches and personalizes your lead profiles by integrating HubSpot contact data, scraping social media information, and using AI to generate tailored outreach emails. It streamlines the

Google Sheets, HubSpot, HTTP Request +3
AI & RAG

Marketing, growth, and automation teams that need to ship polished bilingual newsletters—complete with images, optional video, and multi-channel distribution—without writing a line of code. A Webhook

Agent, Microsoft SharePoint, HTTP Request +4
AI & RAG

The workflow runs every hour with a randomized delay of 5–20 minutes to help distribute load. It records the exact date and time a lead is emailed so you can track outreach. Follow-ups are automatical

Google Sheets, Agent, OpenAI Chat +5
AI & RAG

This n8n workflow automates turning short user ideas into production-ready real-estate marketing assets (photorealistic images and optional 360° videos). A form submission seeds a prompt board → an LL

Form Trigger, Google Sheets, Agent +6
AI & RAG

Universal Expense tracker. Uses telegram, httpRequest, openAi, googleSheets. Webhook trigger; 33 nodes.

Telegram, HTTP Request, OpenAI +7