AutomationFlowsAI & RAG › Run Ai-scored Cold Email Outreach and Follow-ups with Ollama and Gmail

Run Ai-scored Cold Email Outreach and Follow-ups with Ollama and Gmail

ByTony Adijah @togo on n8n.io

This workflow is built for founders, sales teams, solopreneurs, and agencies who want to automate outbound sales without expensive tools. Perfect for anyone doing cold email outreach who wants AI-powered personalization at scale.

Cron / scheduled trigger★★★★★ complexityAI-powered44 nodesGoogle SheetsAgentGmailTelegramOllama Chat
AI & RAG Trigger: Cron / scheduled Nodes: 44 Complexity: ★★★★★ AI nodes: yes Added:

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

This workflow follows the Agent → Gmail 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": "SkOA5njFb0ZAvaSy",
  "name": "AI SDR \u2014 Fully Automated Cold Outreach with Scoring and Follow-ups",
  "tags": [],
  "nodes": [
    {
      "id": "ff288d70-ed51-4639-a4fc-cd9f84c57e73",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "width": 620,
        "height": 860,
        "content": "## AI SDR \u2014 Fully Automated Cold Outreach with Scoring and Follow-ups\nAutomate your entire cold email pipeline: lead research, AI scoring, personalized email generation, sending, follow-ups, and reply detection \u2014 all driven from a Google Sheet.\n\n### How it works\n1. **New Lead Processing (8 AM weekdays):** Reads new leads from Google Sheets, scrapes their website, builds a research dossier, scores them with AI, and generates 3 personalized emails.\n2. **Email 1 sent immediately** for leads scoring 40+. Low-fit leads are marked as skipped.\n3. **Follow-up engine (every 2hrs weekdays):** Sends Email 2 after 3 days, Email 3 after 7 days.\n4. **Reply detection (every 2hrs weekdays):** Searches Gmail for replies from active leads. If found, marks the lead as \"replied\" and stops the sequence.\n5. **Telegram notifications** sent at every step \u2014 emails sent, leads skipped, and replies detected.\n\n### Setup steps\n1. Connect your **Google Sheets** credential and point to your lead sheet (columns: Lead Name, Email, Company, Website, Role/Title, Status, Reply Date, Reply Subject, Reply Snippet).\n2. Connect your **Gmail** credential (OAuth2).\n3. Connect your **Telegram** credential and set `YOUR_TELEGRAM_CHAT_ID` in each Telegram node.\n4. Connect your **Ollama** credential (or swap for OpenAI/Anthropic).\n5. Edit the **AI Lead Scorer** system prompt with your product, ICP, and scoring criteria.\n6. Edit the **AI Email Writer** system prompt with your name, company, and value prop.\n7. Set your sender name in the **Extract Emails** code node (line 12) and **Find Follow-ups** code node (line 9).\n8. Set your Gmail address in the **Filter Active Leads** code node (line 10) and **Check Reply Results** code node (line 10).\n\n### Customization\n- Adjust the scoring threshold (default 40) in the **Extract Score** node.\n- Change follow-up timing (default 3 and 7 days) in the **Find Follow-ups** node.\n- Modify cron schedules on any trigger to match your timezone and preferences."
      },
      "typeVersion": 1
    },
    {
      "id": "e6bd6e84-b8d3-4233-a3ba-a9f1e84abae2",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1104,
        640
      ],
      "parameters": {
        "color": 6,
        "width": 2260,
        "height": 380,
        "content": "## \ud83d\udd2c Research, Score & Send\nScrapes lead websites, builds research dossiers, scores with AI, generates 3 personalized emails, and sends Email 1 immediately."
      },
      "typeVersion": 1
    },
    {
      "id": "90cd7117-dd95-4f9a-b230-d6f9ddef14c8",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        912,
        1104
      ],
      "parameters": {
        "color": 6,
        "width": 1260,
        "height": 420,
        "content": "## \ud83d\udce7 Follow-up Engine\nSends Email 2 after 3 days and Email 3 after 7 days. Skips leads that have already replied."
      },
      "typeVersion": 1
    },
    {
      "id": "3be24aab-7a78-45c3-b88f-c602bcbefa28",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        912,
        1584
      ],
      "parameters": {
        "color": 6,
        "width": 1304,
        "height": 420,
        "content": "## \ud83d\udcec Reply Detection\nSearches Gmail for replies from active leads. Marks them as \"replied\" and stops the email sequence automatically."
      },
      "typeVersion": 1
    },
    {
      "id": "0ec61a66-98cf-4a44-84d9-37b22611b70e",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2608,
        1904
      ],
      "parameters": {
        "color": 3,
        "width": 460,
        "content": "## \u26a0\ufe0f Set your Gmail address\nEdit line 10 in both **Filter Active Leads** and **Check Reply Results** code nodes \u2014 replace `your-email@gmail.com` with your actual sending Gmail address. This prevents your own outbound emails from being detected as replies."
      },
      "typeVersion": 1
    },
    {
      "id": "b43da6bf-e5a3-480e-953d-54d135e68bee",
      "name": "\u23f0 8 AM \u2014 New Leads",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        928,
        752
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1-5"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "00d418c1-2115-466b-9e5e-190b83333889",
      "name": "\ud83d\udccb Read Sheet (New)",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1152,
        752
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_SHEET_GID",
          "cachedResultName": "Leads"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "53f04d54-e0be-4437-8e4b-37bc17257e32",
      "name": "\ud83c\udd95 Filter New",
      "type": "n8n-nodes-base.code",
      "position": [
        1376,
        752
      ],
      "parameters": {
        "jsCode": "const allRows = $input.all();\nconst newLeads = [];\n\nfor (let i = 0; i < allRows.length; i++) {\n  const r = allRows[i].json;\n  const status = (r.Status || r.status || '').toLowerCase().trim();\n  const name = r['Lead Name'] || r.leadName || '';\n  const email = r.Email || r.email || '';\n  const website = r.Website || r.website || '';\n\n  if (status === 'new' && name && email) {\n    newLeads.push({\n      json: {\n        leadName: name,\n        company: r.Company || r.company || '',\n        website: website,\n        role: r['Role/Title'] || r.role || r.title || '',\n        email: email,\n        linkedinUrl: r['LinkedIn URL'] || r.linkedinUrl || '',\n        rowNumber: i + 2\n      }\n    });\n  }\n}\n\nif (newLeads.length === 0) {\n  return [{ json: { hasNewLeads: false } }];\n}\n\nreturn newLeads;"
      },
      "typeVersion": 2
    },
    {
      "id": "6d454456-c5c1-4060-8006-c1ec1daeb7a5",
      "name": "\ud83c\udd95 Has Leads?",
      "type": "n8n-nodes-base.if",
      "position": [
        1600,
        752
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-lead",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $json.leadName }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "77fc3456-e21c-4b0e-9434-d7a6a1e500cf",
      "name": "\ud83c\udf10 Scrape Website",
      "type": "n8n-nodes-base.code",
      "position": [
        1824,
        656
      ],
      "parameters": {
        "jsCode": "const lead = $json;\nlet research = { companyInfo: '', scrapedSuccessfully: false };\n\nif (!lead.website) {\n  research.companyInfo = `${lead.company} \u2014 no website provided. Role: ${lead.role}.`;\n  return { json: { ...lead, research } };\n}\n\ntry {\n  let url = lead.website;\n  if (!url.startsWith('http')) url = 'https://' + url;\n\n  const response = await this.helpers.httpRequest({\n    method: 'GET',\n    url: url,\n    timeout: 15000,\n    headers: { 'User-Agent': 'Mozilla/5.0 (compatible; research-bot)' },\n    returnFullResponse: true,\n    ignoreHttpStatusErrors: true\n  });\n\n  if (response && response.body) {\n    let html = typeof response.body === 'string' ? response.body : JSON.stringify(response.body);\n\n    const titleMatch = html.match(/<title[^>]*>(.*?)<\\/title>/i);\n    const title = titleMatch ? titleMatch[1].trim() : '';\n\n    const metaMatch = html.match(/<meta[^>]*name=[\"']description[\"'][^>]*content=[\"']([^\"']*)[\"']/i);\n    const metaDesc = metaMatch ? metaMatch[1].trim() : '';\n\n    html = html.replace(/<script[\\s\\S]*?<\\/script>/gi, ' ');\n    html = html.replace(/<style[\\s\\S]*?<\\/style>/gi, ' ');\n    html = html.replace(/<nav[\\s\\S]*?<\\/nav>/gi, ' ');\n    html = html.replace(/<footer[\\s\\S]*?<\\/footer>/gi, ' ');\n    html = html.replace(/<[^>]+>/g, ' ');\n    html = html.replace(/&nbsp;|&amp;|&lt;|&gt;/g, ' ');\n    html = html.replace(/\\s+/g, ' ').trim();\n\n    research.companyInfo = `Title: ${title}\\nDescription: ${metaDesc}\\nContent: ${html.substring(0, 1500)}`;\n    research.scrapedSuccessfully = true;\n  }\n} catch (e) {\n  research.companyInfo = `Could not scrape ${lead.website}. Company: ${lead.company}, Role: ${lead.role}.`;\n}\n\nreturn { json: { ...lead, research } };"
      },
      "typeVersion": 2
    },
    {
      "id": "a407e7ad-a48a-49f2-8c4c-b867a9dc2b51",
      "name": "\ud83d\udcc4 Build Dossier",
      "type": "n8n-nodes-base.code",
      "position": [
        2048,
        656
      ],
      "parameters": {
        "jsCode": "const lead = $json;\nconst r = lead.research || {};\n\nconst dossier = `=== LEAD DOSSIER ===\nPerson: ${lead.leadName}\nRole: ${lead.role}\nCompany: ${lead.company}\nWebsite: ${lead.website || 'N/A'}\nEmail: ${lead.email}\n\n--- Company Research ---\n${r.companyInfo || 'No info available.'}\n\nScraped: ${r.scrapedSuccessfully ? 'Yes' : 'No'}`;\n\nreturn { json: { ...lead, dossier } };"
      },
      "typeVersion": 2
    },
    {
      "id": "975ad2ef-3697-451e-b7cc-4b84795a5ae4",
      "name": "\ud83d\udcca AI Lead Scorer",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2272,
        656
      ],
      "parameters": {
        "text": "=Score this lead for sales outreach:\n\n{{ $json.dossier }}\n\nScore 0-100.",
        "options": {
          "systemMessage": "You are a sales intelligence analyst.\n\n=== YOUR PRODUCT (EDIT THIS) ===\nProduct: [YOUR PRODUCT NAME]\nWhat it does: [WHAT IT DOES]\nIdeal customer: [YOUR ICP]\nProblems solved: [KEY PROBLEMS]\n\n=== SCORING ===\n1. Company-Solution Fit (0-30): Do they have our problem?\n2. Role Fit (0-25): Can they buy? (C-level=high, IC=low)\n3. Timing Signals (0-25): Hiring? Funded? Growing?\n4. Engagement Potential (0-20): Will they respond?\n\nRAW JSON ONLY. No markdown fences.\n{\"totalScore\":0,\"companySolutionFit\":{\"score\":0,\"reason\":\"\"},\"roleFit\":{\"score\":0,\"reason\":\"\"},\"timingSignals\":{\"score\":0,\"reason\":\"\"},\"engagementPotential\":{\"score\":0,\"reason\":\"\"},\"overallAssessment\":\"\",\"recommendation\":\"pursue\",\"keyInsight\":\"\"}"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 2.1
    },
    {
      "id": "7454887b-5a92-4d21-b927-f8124a31eb27",
      "name": "\ud83c\udfaf Extract Score",
      "type": "n8n-nodes-base.code",
      "position": [
        2624,
        656
      ],
      "parameters": {
        "jsCode": "const lead = $('\ud83d\udcc4 Build Dossier').item.json;\nconst raw = $json;\nlet score = {};\n\nfunction tryParse(str) {\n  if (typeof str !== 'string') return null;\n  str = str.replace(/```json\\s*/gi, '').replace(/```\\s*/g, '').trim();\n  try { return JSON.parse(str); } catch(e) {\n    const m = str.match(/\\{[\\s\\S]*\"totalScore\"[\\s\\S]*\\}/);\n    if (m) try { return JSON.parse(m[0]); } catch(e2) {}\n    return null;\n  }\n}\n\nfunction dig(obj, d) {\n  if (d > 5 || !obj || typeof obj !== 'object') return null;\n  if (obj.totalScore !== undefined) return obj;\n  for (const k of ['output','text','message','content','response','result','data','kwargs','lc_kwargs']) {\n    if (obj[k]) {\n      if (typeof obj[k] === 'string') { const r = tryParse(obj[k]); if (r) return r; }\n      else { const r = dig(obj[k], d+1); if (r) return r; }\n    }\n  }\n  for (const k of Object.keys(obj)) {\n    if (typeof obj[k] === 'string' && obj[k].includes('totalScore')) {\n      const r = tryParse(obj[k]); if (r) return r;\n    } else if (typeof obj[k] === 'object' && obj[k]) {\n      const r = dig(obj[k], d+1); if (r) return r;\n    }\n  }\n  return null;\n}\n\ntry {\n  if (raw && raw.totalScore !== undefined) score = raw;\n  else { const f = dig(raw, 0); if (f) score = f; }\n  if (score.totalScore === undefined) {\n    const p = tryParse(JSON.stringify(raw));\n    if (p) score = p;\n  }\n} catch(e) {}\n\nif (score.totalScore === undefined) {\n  score = { totalScore: 50, recommendation: 'pursue', overallAssessment: 'AI parse failed \u2014 default score.', keyInsight: 'Review manually' };\n}\n\nreturn { json: {\n  ...lead,\n  score: score.totalScore,\n  recommendation: score.recommendation || (score.totalScore >= 40 ? 'pursue' : 'skip'),\n  keyInsight: score.keyInsight || '',\n  overallAssessment: score.overallAssessment || '',\n  shouldPursue: score.totalScore >= 40\n}};"
      },
      "typeVersion": 2
    },
    {
      "id": "268c6d25-e5fb-4157-bb26-6a13c235a14a",
      "name": "\ud83d\udcca Pursue?",
      "type": "n8n-nodes-base.if",
      "position": [
        2848,
        656
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "pursue",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.shouldPursue }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "e84960a8-3d0b-484d-a436-3d37174a8c5e",
      "name": "\u270d\ufe0f AI Email Writer",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        3072,
        400
      ],
      "parameters": {
        "text": "=Write 3 cold outreach emails for:\n\n{{ $json.dossier }}\n\nScore: {{ $json.score }}/100\nKey Insight: {{ $json.keyInsight }}\nAssessment: {{ $json.overallAssessment }}",
        "options": {
          "systemMessage": "You are writing cold outreach emails.\n\n=== SENDER INFO (EDIT THIS) ===\n[YOUR NAME]: Your full name\n[YOUR COMPANY]: Your company name\n[WHAT YOU SELL]: One-line description of your product\n[#1 BENEFIT]: The main benefit your customers get\n[ONE SPECIFIC RESULT]: A concrete result you've achieved for a customer\n\n=== STRICT FORMATTING RULES ===\n\nEVERY email must follow this EXACT structure:\n\nLine 1: Greeting (Hi [FirstName],)\nLine 2: (blank line)\nLines 3-8: Body (4-6 short sentences)\nLine N-1: (blank line)\nLine N: Sign-off (Best, [YOUR NAME])\n\nFORMATTING:\n- Use real line breaks between paragraphs\n- NO markdown (no **, no ##, no bullets)\n- NO \"Subject:\" in the body\n- Keep sentences SHORT (under 20 words each)\n- One idea per paragraph\n- Max 6 sentences in body\n- Sign off with: Best, [YOUR NAME]\n\nCONTENT RULES:\n- Reference something SPECIFIC from their company research\n- Mention their role naturally\n- Email 1: Lead with THEIR world, not yours\n- Email 2: Share a different angle or result (never say \"following up\")\n- Email 3: Be human, make it easy to say no\n- Sound like a PERSON, not a salesperson\n- NO buzzwords (leverage, synergy, revolutionary, game-changing)\n- NO \"I hope this email finds you well\"\n- NO \"I came across your company\"\n\n=== SUBJECT LINE RULES ===\n- 4-7 words only\n- Lowercase (except first word and proper nouns)\n- No clickbait, no ALL CAPS\n- Create curiosity, not hype\n\n=== OUTPUT FORMAT ===\nRAW JSON ONLY. No markdown fences. No explanation.\n\n{\"email1\":{\"subject\":\"subject here\",\"body\":\"Hi FirstName,\\n\\nFirst paragraph.\\n\\nSecond paragraph.\\n\\nCTA paragraph.\\n\\nBest,\\n[YOUR NAME]\"},\"email2\":{\"subject\":\"different subject\",\"body\":\"Hi FirstName,\\n\\nDifferent angle.\\n\\nProof or insight.\\n\\nSoft CTA.\\n\\nBest,\\n[YOUR NAME]\"},\"email3\":{\"subject\":\"last note subject\",\"body\":\"Hi FirstName,\\n\\nAcknowledge outreach.\\n\\nGenuine value.\\n\\nEasy to say no.\\n\\nCheers,\\n[YOUR NAME]\"},\"personalizationUsed\":[\"specific thing 1\",\"specific thing 2\"]}"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 2.1
    },
    {
      "id": "b51ecaf0-3369-41ed-b2b5-d1a8fbddf1cf",
      "name": "\ud83d\udcdd Extract Emails",
      "type": "n8n-nodes-base.code",
      "position": [
        3424,
        512
      ],
      "parameters": {
        "jsCode": "const lead = $('\ud83d\udcca Pursue?').item.json;\nconst raw = $json;\nlet emails = {};\n\n// \u26a0\ufe0f CHANGE THIS to your name\nconst senderName = 'YOUR_NAME';\nconst firstName = lead.leadName.split(' ')[0];\n\nfunction tryParse(str) {\n  if (typeof str !== 'string') return null;\n  str = str.replace(/```json\\s*/gi, '').replace(/```\\s*/g, '').trim();\n  try { return JSON.parse(str); } catch(e) {\n    const m = str.match(/\\{[\\s\\S]*\"email1\"[\\s\\S]*\\}/);\n    if (m) try { return JSON.parse(m[0]); } catch(e2) {}\n    return null;\n  }\n}\n\nfunction dig(obj, d) {\n  if (d > 5 || !obj || typeof obj !== 'object') return null;\n  if (obj.email1 && typeof obj.email1 === 'object') return obj;\n  for (const k of ['output','text','message','content','response','result','data','kwargs','lc_kwargs']) {\n    if (obj[k]) {\n      if (typeof obj[k] === 'string') { const r = tryParse(obj[k]); if (r && r.email1) return r; }\n      else { const r = dig(obj[k], d+1); if (r) return r; }\n    }\n  }\n  for (const k of Object.keys(obj)) {\n    if (typeof obj[k] === 'string' && obj[k].includes('email1')) { const r = tryParse(obj[k]); if (r && r.email1) return r; }\n    else if (typeof obj[k] === 'object' && obj[k]) { const r = dig(obj[k], d+1); if (r) return r; }\n  }\n  return null;\n}\n\ntry {\n  if (raw && raw.email1) emails = raw;\n  else { const f = dig(raw, 0); if (f) emails = f; }\n  if (!emails.email1) { const p = tryParse(JSON.stringify(raw)); if (p && p.email1) emails = p; }\n} catch(e) {}\n\nif (!emails.email1) {\n  emails = {\n    email1: { subject: `Quick question about ${lead.company}`, body: `Hi ${firstName},\\n\\nI noticed ${lead.company} and wanted to reach out.\\n\\nWould love to share something relevant to your work as ${lead.role}.\\n\\nWorth a quick chat?\\n\\nBest,\\n${senderName}` },\n    email2: { subject: `Thought of ${lead.company}`, body: `Hi ${firstName},\\n\\nI had another thought about how we might help ${lead.company}.\\n\\nWe recently helped a similar company solve a challenge you might relate to.\\n\\nWould it make sense to share the details?\\n\\nBest,\\n${senderName}` },\n    email3: { subject: `Last note from me`, body: `Hi ${firstName},\\n\\nI know you're busy, so I'll keep this brief.\\n\\nIf the timing isn't right, totally understand.\\n\\nWishing you and ${lead.company} all the best.\\n\\nCheers,\\n${senderName}` },\n    personalizationUsed: ['company name only \u2014 AI parse failed']\n  };\n}\n\nfunction formatEmailBody(rawBody) {\n  if (!rawBody) return '';\n  let body = rawBody;\n  body = body.replace(/\\\\n/g, '\\n');\n  body = body.replace(/\\\\\\\\n/g, '\\n');\n  body = body.replace(/^Subject:.*?\\n\\n?/i, '');\n  body = body.replace(/^Subject:.*?\\n/i, '');\n  body = body.replace(/\\*\\*/g, '');\n  body = body.replace(/^#+\\s/gm, '');\n  body = body.replace(/\\[YOUR NAME\\]/gi, senderName);\n  body = body.replace(/\\[Your Name\\]/g, senderName);\n  body = body.replace(/YourName/g, senderName);\n  body = body.replace(/\\n\\n?(Best|Cheers|Thanks|Thank you|Regards|Kind regards|Warm regards|Sincerely|Talk soon|All the best),?\\s*\\n\\s*[A-Z][a-zA-Z]*(\\s+[A-Z][a-zA-Z]*)?\\s*(\\n\\n?(Best|Cheers|Thanks|Thank you|Regards|Kind regards|Warm regards|Sincerely|Talk soon|All the best),?\\s*\\n\\s*(\\[YOUR NAME\\]|YourName|[A-Z][a-zA-Z]*(\\s+[A-Z][a-zA-Z]*)?))?$/i, '');\n  body = body.replace(/\\n\\n?(Best|Cheers|Thanks|Regards|Sincerely|Talk soon),?\\s*$/i, '');\n  body = body.trimEnd();\n  if (!/^(Hi|Hey|Hello|Dear|Good)\\s/i.test(body.trim())) {\n    body = `Hi ${firstName},\\n\\n${body}`;\n  }\n  body = body + `\\n\\nBest,\\n${senderName}`;\n  body = body.replace(/\\n{4,}/g, '\\n\\n\\n').trim();\n  return body;\n}\n\nfunction formatSubject(rawSubject, emailNumber) {\n  if (!rawSubject) return `Following up \u2014 ${lead.company}`;\n  let subject = rawSubject;\n  subject = subject.replace(/^[\"']|[\"']$/g, '');\n  subject = subject.replace(/^Subject:\\s*/i, '');\n  subject = subject.replace(/\\[YOUR NAME\\]/gi, senderName);\n  if (emailNumber > 1 && !/^Re:/i.test(subject)) subject = 'Re: ' + subject;\n  subject = subject.replace(/^(Re:\\s*)+/i, 'Re: ');\n  return subject.trim();\n}\n\nfunction toHTML(plainText) {\n  let html = plainText.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\\n\\n/g, '</p><p>').replace(/\\n/g, '<br>');\n  return '<div style=\"font-family: Arial, sans-serif; font-size: 14px; color: #333; line-height: 1.6;\"><p>' + html + '</p></div>';\n}\n\nconst email1Subject = formatSubject(emails.email1.subject, 1);\nconst email1Body = formatEmailBody(emails.email1.body);\nconst email2Subject = formatSubject(emails.email2.subject || emails.email1.subject, 2);\nconst email2Body = formatEmailBody(emails.email2.body);\nconst email3Subject = formatSubject(emails.email3.subject || emails.email1.subject, 3);\nconst email3Body = formatEmailBody(emails.email3.body);\n\nreturn { json: {\n  leadName: lead.leadName, company: lead.company, email: lead.email,\n  role: lead.role, website: lead.website, score: lead.score,\n  keyInsight: lead.keyInsight, overallAssessment: lead.overallAssessment,\n  rowNumber: lead.rowNumber,\n  email1Subject, email1Body, email1HTML: toHTML(email1Body),\n  email2Subject, email2Body, email2HTML: toHTML(email2Body),\n  email3Subject, email3Body, email3HTML: toHTML(email3Body),\n  email1Full: 'Subject: ' + email1Subject + '\\n\\n' + email1Body,\n  email2Full: 'Subject: ' + email2Subject + '\\n\\n' + email2Body,\n  email3Full: 'Subject: ' + email3Subject + '\\n\\n' + email3Body,\n  personalizationUsed: Array.isArray(emails.personalizationUsed) ? emails.personalizationUsed.join(', ') : ''\n}};"
      },
      "typeVersion": 2
    },
    {
      "id": "6f9bc1bd-d6da-4415-9326-130018f64386",
      "name": "\ud83d\udce7 Send Email 1",
      "type": "n8n-nodes-base.gmail",
      "position": [
        3648,
        512
      ],
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "message": "={{ $json.email1HTML }}",
        "options": {
          "appendAttribution": false
        },
        "subject": "={{ $json.email1Subject }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "7be872d9-8aef-43fe-b79c-aa848628fef9",
      "name": "\ud83d\udcbe Save + Mark Sent",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3872,
        608
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "email_1_sent",
            "Lead Name": "={{ $('\ud83d\udcdd Extract Emails').item.json.leadName }}",
            "email 3 sub": "={{ $('\ud83d\udcdd Extract Emails').item.json.email3Subject }}",
            "email 1 body": "={{ $('\ud83d\udcdd Extract Emails').item.json.email1Body }}",
            "email 2 body": "={{ $('\ud83d\udcdd Extract Emails').item.json.email2Body }}",
            "email 3 body": "={{ $('\ud83d\udcdd Extract Emails').item.json.email3Body }}",
            "email 1subject": "={{ $('\ud83d\udcdd Extract Emails').item.json.email1Subject }}",
            "email 2 subject": "={{ $('\ud83d\udcdd Extract Emails').item.json.email2Subject }}",
            "email 1 body html": "={{ $('\ud83d\udcdd Extract Emails').item.json.email1HTML }}",
            "email 1 sent date": "={{ new Date().toISOString() }}",
            "email 2 body html": "={{ $('\ud83d\udcdd Extract Emails').item.json.email2HTML }}",
            "email 3 body html": "={{ $('\ud83d\udcdd Extract Emails').item.json.email3HTML }}"
          },
          "schema": [
            {
              "id": "Lead Name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Lead Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 1subject",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 1subject",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 1 body",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 1 body",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 1 sent date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 1 sent date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 1 body html",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 1 body html",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 2 subject",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 2 subject",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 2 body",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 2 body",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 2 body html",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 2 body html",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 3 sub",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 3 sub",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 3 body",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 3 body",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 3 body html",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 3 body html",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Lead Name"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_SHEET_GID",
          "cachedResultName": "Leads"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "1441a932-a034-4196-b322-ac6272e7af10",
      "name": "\ud83d\udcf2 Email 1 Sent \u2705",
      "type": "n8n-nodes-base.telegram",
      "position": [
        3872,
        416
      ],
      "parameters": {
        "text": "=\ud83d\ude80 *Email 1 Sent \u2014 Auto-SDR*\n\n\ud83d\udc64 {{ $('\ud83d\udcdd Extract Emails').item.json.leadName }}\n\ud83c\udfe2 {{ $('\ud83d\udcdd Extract Emails').item.json.company }} ({{ $('\ud83d\udcdd Extract Emails').item.json.role }})\n\ud83d\udcca Score: {{ $('\ud83d\udcdd Extract Emails').item.json.score }}/100\n\ud83d\udce7 To: {{ $('\ud83d\udcdd Extract Emails').item.json.email }}\n\ud83d\udcdd Subject: {{ $('\ud83d\udcdd Extract Emails').item.json.email1Subject }}\n\n\ud83d\udca1 {{ $('\ud83d\udcdd Extract Emails').item.json.keyInsight }}\n\n_Email 2 in 3 days. Email 3 in 7 days._\n\u23f0 {{ new Date().toLocaleString() }}",
        "chatId": "YOUR_TELEGRAM_CHAT_ID",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "3b92637a-f2c2-432c-b957-03ecf5526c8a",
      "name": "\u23ed\ufe0f Mark Skipped",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3136,
        800
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "low_fit_skipped",
            "Lead Name": "={{ $json.leadName }}"
          },
          "schema": [
            {
              "id": "Lead Name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Lead Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Lead Name"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_SHEET_GID",
          "cachedResultName": "Leads"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "835b1e19-c429-4814-92e4-d15dd031ac76",
      "name": "\ud83d\udcf2 Skipped",
      "type": "n8n-nodes-base.telegram",
      "position": [
        3424,
        800
      ],
      "parameters": {
        "text": "=\u23ed\ufe0f *Lead Skipped*\n\ud83d\udc64 {{ $json.leadName }} @ {{ $json.company }}\n\ud83d\udcca Score: {{ $json.score }}/100\n\ud83d\udcac {{ $json.overallAssessment }}",
        "chatId": "YOUR_TELEGRAM_CHAT_ID",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "c4316023-e077-4889-ac3d-86ad9059d458",
      "name": "\ud83d\udca4 No New Leads",
      "type": "n8n-nodes-base.code",
      "position": [
        1824,
        848
      ],
      "parameters": {
        "jsCode": "return { json: { status: 'no_new_leads', checkedAt: new Date().toLocaleString() } };"
      },
      "typeVersion": 2
    },
    {
      "id": "9da28257-a906-4425-81f9-a11fde5a8864",
      "name": "\u23f0 Every 2hrs \u2014 Follow-ups",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        928,
        1264
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 10,12,14,16 * * 1-5"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "cffcf037-6615-4705-8a8f-49dbbcbe1e40",
      "name": "\ud83d\udccb Read Sheet (Follow-ups)",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1152,
        1264
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_SHEET_GID",
          "cachedResultName": "Leads"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "44cea630-cb7a-461f-9459-0b602f839d00",
      "name": "\ud83d\udd0d Find Follow-ups",
      "type": "n8n-nodes-base.code",
      "position": [
        1376,
        1264
      ],
      "parameters": {
        "jsCode": "const allRows = $input.all();\nconst now = new Date();\nconst actions = [];\n\n// \u26a0\ufe0f CHANGE THIS to your name\nconst senderName = 'YOUR_NAME';\n\nfunction formatBody(rawContent, firstName) {\n  if (!rawContent) return '';\n  let body = rawContent;\n  body = body.replace(/^Subject:.*?\\n\\n?/i, '');\n  body = body.replace(/^Subject:.*?\\n/i, '');\n  body = body.replace(/\\\\n/g, '\\n');\n  body = body.replace(/\\\\\\\\n/g, '\\n');\n  body = body.replace(/\\*\\*/g, '');\n  body = body.replace(/^#+\\s/gm, '');\n  body = body.replace(/\\[YOUR NAME\\]/gi, senderName);\n  body = body.replace(/\\[Your Name\\]/g, senderName);\n  body = body.replace(/YourName/g, senderName);\n  body = body.replace(/\\n\\n?(Best|Cheers|Thanks|Thank you|Regards|Kind regards|Warm regards|Sincerely|Talk soon|All the best),?\\s*\\n\\s*[A-Z][a-zA-Z]*(\\s+[A-Z][a-zA-Z]*)?\\s*(\\n\\n?(Best|Cheers|Thanks|Thank you|Regards|Kind regards|Warm regards|Sincerely|Talk soon|All the best),?\\s*\\n\\s*(\\[YOUR NAME\\]|YourName|[A-Z][a-zA-Z]*(\\s+[A-Z][a-zA-Z]*)?))?$/i, '');\n  body = body.replace(/\\n\\n?(Best|Cheers|Thanks|Regards|Sincerely|Talk soon),?\\s*$/i, '');\n  body = body.trimEnd();\n  if (!/^(Hi|Hey|Hello|Dear|Good)\\s/i.test(body.trim())) {\n    body = `Hi ${firstName},\\n\\n${body}`;\n  }\n  body = body + `\\n\\nBest,\\n${senderName}`;\n  body = body.replace(/\\n{4,}/g, '\\n\\n\\n').trim();\n  return body;\n}\n\nfunction toHTML(text) {\n  let html = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\\n\\n/g, '</p><p>').replace(/\\n/g, '<br>');\n  return '<div style=\"font-family: Arial, sans-serif; font-size: 14px; color: #333; line-height: 1.6;\"><p>' + html + '</p></div>';\n}\n\nfor (let i = 0; i < allRows.length; i++) {\n  const r = allRows[i].json;\n  const name = r['Lead Name'] || '';\n  const email = r['Email'] || r['email'] || '';\n  const company = r['Company'] || '';\n  const status = (r['Status'] || '').toLowerCase().trim();\n  const firstName = (name || '').split(' ')[0] || 'there';\n\n  if (!name || !email) continue;\n  if (['low_fit_skipped', 'sequence_complete', 'replied', 'new'].includes(status)) continue;\n\n  const email1SentStr = (r['email 1 sent date'] || '').toString().trim();\n  const email2SentStr = (r['email 2 sent date '] || r['email 2 sent date'] || '').toString().trim();\n  const email3SentStr = (r['email 3 sent date'] || '').toString().trim();\n\n  if (!email1SentStr) continue;\n\n  const email1SentDate = new Date(email1SentStr);\n  if (isNaN(email1SentDate.getTime())) continue;\n\n  const daysSinceEmail1 = (now - email1SentDate) / (1000 * 60 * 60 * 24);\n\n  if (!email2SentStr && daysSinceEmail1 >= 3) {\n    const subject = r['email 2 subject'] || ('Re: ' + (r['email 1subject'] || `Following up \u2014 ${company}`).replace(/^Re:\\s*/i, ''));\n    const body = formatBody(\n      r['email 2 body'] || `Hi ${firstName},\\n\\nI had another thought about how we might help ${company}.\\n\\nWould it make sense to connect briefly?`,\n      firstName\n    );\n    actions.push({ json: {\n      action: 'send_email_2', leadName: name, email: email, company: company,\n      subject: subject, body: body, htmlBody: toHTML(body), emailNumber: 2,\n      daysSinceEmail1: Math.round(daysSinceEmail1 * 10) / 10\n    }});\n  }\n  else if (email2SentStr && !email3SentStr && daysSinceEmail1 >= 7) {\n    const subject = r['email 3 sub'] || ('Re: ' + (r['email 1subject'] || `Following up \u2014 ${company}`).replace(/^Re:\\s*/i, ''));\n    const body = formatBody(\n      r['email 3 body'] || `Hi ${firstName},\\n\\nI know you're busy, so I'll keep this short.\\n\\nIf timing isn't right, no worries at all.\\n\\nWishing you and ${company} all the best.`,\n      firstName\n    );\n    actions.push({ json: {\n      action: 'send_email_3', leadName: name, email: email, company: company,\n      subject: subject, body: body, htmlBody: toHTML(body), emailNumber: 3,\n      daysSinceEmail1: Math.round(daysSinceEmail1 * 10) / 10\n    }});\n  }\n}\n\nif (actions.length === 0) {\n  return [{ json: { hasActions: false, message: 'No follow-ups needed right now.', totalRowsChecked: allRows.length, checkedAt: new Date().toLocaleString() }}];\n}\n\nreturn actions;"
      },
      "typeVersion": 2
    },
    {
      "id": "efbc486b-0d8f-4001-bbe4-63cf0ca2dbdd",
      "name": "\ud83d\udce7 Has Follow-ups?",
      "type": "n8n-nodes-base.if",
      "position": [
        1600,
        1264
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-action",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $json.action }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "f6c8fed2-d1bc-43d4-bb79-87517538c114",
      "name": "\ud83d\udce7 Send Follow-up",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1824,
        1168
      ],
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "message": "={{ $json.htmlBody }}",
        "options": {
          "appendAttribution": false
        },
        "subject": "={{ $json.subject }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "999b991c-87ad-402d-a9c3-2ded4af66698",
      "name": "\ud83d\udccb Prepare Update",
      "type": "n8n-nodes-base.code",
      "position": [
        2048,
        1168
      ],
      "parameters": {
        "jsCode": "const data = $('\ud83d\udce7 Has Follow-ups?').item.json;\nconst nowISO = new Date().toISOString();\n\nconst update = { 'Lead Name': data.leadName };\n\nif (data.emailNumber === 2) {\n  update['Status'] = 'email_2_sent';\n  update['email 2 sent date '] = nowISO;\n  update['email 2 subject'] = data.subject;\n  update['email 2 body'] = data.body;\n  update['email 2 body html'] = data.htmlBody;\n} else if (data.emailNumber === 3) {\n  update['Status'] = 'sequence_complete';\n  update['email 3 sent date'] = nowISO;\n  update['email 3 sub'] = data.subject;\n  update['email 3 body'] = data.body;\n  update['email 3 body html'] = data.htmlBody;\n}\n\nreturn { json: update };"
      },
      "typeVersion": 2
    },
    {
      "id": "852f7db1-c97c-4c6e-9cd6-1f4ff55b382d",
      "name": "\u2705 Mark Follow-up Sent",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2272,
        1168
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "={{ $json.Status }}",
            "Lead Name": "={{ $json['Lead Name'] }}",
            "email 3 sub": "={{ $json['email 3 sub'] || '' }}",
            "email 2 body": "={{ $json['email 2 body'] || '' }}",
            "email 3 body": "={{ $json['email 3 body'] || '' }}",
            "email 2 subject": "={{ $json['email 2 subject'] || '' }}",
            "email 2 body html": "={{ $json['email 2 body html'] || '' }}",
            "email 3 body html": "={{ $json['email 3 body html'] || '' }}",
            "email 3 sent date": "={{ $json['email 3 sent date'] || '' }}",
            "email 2 sent date ": "={{ $json['email 2 sent date '] || '' }}"
          },
          "schema": [
            {
              "id": "Lead Name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Lead Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 2 sent date ",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 2 sent date ",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 2 subject",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 2 subject",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 2 body",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 2 body",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 2 body html",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 2 body html",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 3 sent date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 3 sent date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 3 sub",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 3 sub",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 3 body",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 3 body",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email 3 body html",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email 3 body html",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Lead Name"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_SHEET_GID",
          "cachedResultName": "Leads"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "9149478f-d8fa-4c9a-8c9c-7f3095661c23",
      "name": "\ud83d\udcf2 Follow-up Sent",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2272,
        1008
      ],
      "parameters": {
        "text": "=\ud83d\udce7 *Follow-up Email {{ $('\ud83d\udce7 Has Follow-ups?').item.json.emailNumber }} Sent*\n\n\ud83d\udc64 {{ $('\ud83d\udce7 Has Follow-ups?').item.json.leadName }} @ {{ $('\ud83d\udce7 Has Follow-ups?').item.json.company }}\n\ud83d\udce8 {{ $('\ud83d\udce7 Has Follow-ups?').item.json.email }}\n\ud83d\udcdd {{ $('\ud83d\udce7 Has Follow-ups?').item.json.subject }}\n\n{{ $('\ud83d\udce7 Has Follow-ups?').item.json.emailNumber === 3 ? '\ud83c\udfc1 Sequence complete!' : '\u23f3 Next follow-up scheduled.' }}\n\u23f0 {{ new Date().toLocaleString() }}",
        "chatId": "YOUR_TELEGRAM_CHAT_ID",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "19415433-ff65-4a27-94e6-7c86cacf41b8",
      "name": "\ud83d\udca4 Nothing Pending",
      "type": "n8n-nodes-base.code",
      "position": [
        1824,
        1360
      ],
      "parameters": {
        "jsCode": "return { json: { status: 'no_followups', checkedAt: new Date().toLocaleString() } };"
      },
      "typeVersion": 2
    },
    {
      "id": "f5e432ab-8110-4c6b-a121-2aaa1d6d3826",
      "name": "Ollama Scorer",
      "type": "@n8n/n8n-nodes-langchain.lmChatOllama",
      "position": [
        2352,
        880
      ],
      "parameters": {
        "model": "YOUR_MODEL_NAME",
        "options": {
          "temperature": 0.3
        }
      },
      "typeVersion": 1
    },
    {
      "id": "227b7180-940f-496c-9762-9690f7782591",
      "name": "Ollama Writer",
      "type": "@n8n/n8n-nodes-langchain.lmChatOllama",
      "position": [
        3152,
        624
      ],
      "parameters": {
        "model": "YOUR_MODEL_NAME",
        "options": {
          "temperature": 0.7
        }
      },
      "typeVersion": 1
    },
    {
      "id": "4e08504a-2411-4ce8-a17f-9281ec0c34e6",
      "name": "\u23f0 Every 2hrs \u2014 Reply Check",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        928,
        1744
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "30 9,11,13,15,17 * * 1-5"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "6c1293fa-42f5-485c-9568-179bf8e55414",
      "name": "\ud83d\udccb Read Sheet (Replies)",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1152,
        1744
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_SHEET_GID",
          "cachedResultName": "Leads"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "2a290c73-5488-4b01-8bfa-93879cbe60e0",
      "name": "\ud83d\udd0d Filter Active Leads",
      "type": "n8n-nodes-base.code",
      "position": [
        1376,
        1744
      ],
      "parameters": {
        "jsCode": "const allRows = $input.all();\nconst activeLeads = [];\n\n// \u26a0\ufe0f REPLACE WITH YOUR GMAIL ADDRESS\nconst MY_GMAIL = 'user@example.com';\n\nfor (let i = 0; i < allRows.length; i++) {\n  const r = allRows[i].json;\n  const name = r['Lead Name'] || '';\n  const email = (r['Email'] || r['email'] || '').trim().toLowerCase();\n  const company = r['Company'] || '';\n  const status = (r['Status'] || '').toLowerCase().trim();\n\n  if (!name || !email) continue;\n\n  const activeStatuses = ['email_1_sent', 'email_2_sent', 'sequence_complete'];\n  if (!activeStatuses.includes(status)) continue;\n\n  const email1SentStr = (r['email 1 sent date'] || '').toString().trim();\n  if (!email1SentStr) continue;\n\n  activeLeads.push({\n    json: {\n      leadName: name,\n      email: email,\n      company: company,\n      status: status,\n      email1SentDate: email1SentStr,\n      myGmail: MY_GMAIL,\n      rowIndex: i + 2\n    }\n  });\n}\n\nif (activeLeads.length === 0) {\n  return [{ json: { hasActiveLeads: false, message: 'No active leads to check for replies.', checkedAt: new Date().toLocaleString() } }];\n}\n\nreturn activeLeads;"
      },
      "typeVersion": 2
    },
    {
      "id": "e9b61105-f943-4b5d-8edd-2837a0fec3d3",
      "name": "\ud83d\udcec Has Active Leads?",
      "type": "n8n-nodes-base.if",
      "position": [
        1600,
        1744
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-active",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $json.email }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "3a7dc4af-95a5-4434-990e-4e2fc37a4b84",
      "name": "\ud83d\udd0e Search Gmail Replies",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1824,
        1648
      ],
      "parameters": {
        "limit": 30,
        "simple": false,
        "filters": {
          "q": "=from:{{ $json.email }} in:inbox newer_than:30d -in:sent",
          "includeSpamTrash": false
        },
        "options": {},
        "operation": "getAll"
      },
      "typeVersion": 2.1
    },
    {
      "id": "a9320a6b-095c-442b-ae07-d7422554dc99",
      "name": "\ud83d\udce9 Check Reply Results",
      "type": "n8n-nodes-base.code",
      "position": [
        2048,
        1648
      ],
      "parameters": {
        "jsCode": "const lead = $('\ud83d\udcec Has Active Leads?').item.json;\nconst gmailResults = $input.all();\n\n// \u26a0\ufe0f REPLACE WITH YOUR GMAIL ADDRESS\nconst MY_GMAIL = (lead.myGmail || 'user@example.com').toLowerCase().trim();\n\nfunction safeParseDate(val) {\n  if (val === null || val === undefined || val === '') return null;\n  const str = String(val).trim();\n  if (!str || str === '0' || str === 'undefined' || str === 'null') return null;\n\n  if (/^\\d{10,13}$/.test(str)) {\n    const num = parseInt(str, 10);\n    const ms = str.length <= 10 ? num * 1000 : num;\n    const d = new Date(ms);\n    if (!isNaN(d.getTime()) && d.getFullYear() > 2000 && d.getFullYear() < 2100) return d;\n  }\n\n  try {\n    const d1 = new Date(str);\n    if (!isNaN(d1.getTime()) && d1.getFullYear() > 2000 && d1.getFullYear() < 2100) return d1;\n  } catch(e) {}\n\n  const num = Number(val);\n  if (!isNaN(num) && num > 1+1234567890) {\n    const ms = num > 1+1234567890 ? num : num * 1000;\n    try {\n      const d2 = new Date(ms);\n      if (!isNaN(d2.getTime()) && d2.getFullYear() > 2000 && d2.getFullYear() < 2100) return d2;\n    } catch(e) {}\n  }\n\n  return null;\n}\n\nfunction extractEmail(fromStr) {\n  if (!fromStr) return '';\n  const s = String(fromStr).toLowerCase().trim();\n  const match = s.match(/<([^>]+)>/);\n  if (match) return match[1].trim();\n  if (s.includes('@')) return s.trim();\n  return s;\n}\n\nconst email1SentDate = safeParseDate(lead.email1SentDate);\n\nif (!email1SentDate) {\n  return [{ json: {\n    hasReply: false, leadName: lead.leadName || '', email: lead.email || '',\n    company: lead.company || '', currentStatus: lead.status || '',\n    replyDate: '', replySubject: '', replySnippet: '',\n    debug: 'Could not parse email1SentDate: ' + String(lead.email1SentDate)\n  }}];\n}\n\nif (!gmailResults || gmailResults.length === 0) {\n  return [{ json: {\n    hasReply: false, leadName: lead.leadName || '', email: lead.email || '',\n    company: lead.company || '', currentStatus: lead.status || '',\n    replyDate: '', replySubject: '', replySnippet: ''\n  }}];\n}\n\nconst leadEmail = (lead.email || '').toLowerCase().trim();\nlet replyFound = false;\nlet replyDate = '';\nlet replySubject = '';\nlet replySnippet = '';\n\nfor (const msg of gmailResults) {\n  const m = msg.json || {};\n\n  const labels = m.labelIds || m.labels || [];\n  const labelArr = Array.isArray(labels) ? labels : [String(labels)];\n  const labelStr = labelArr.join(',').toUpperCase();\n  if (labelStr.includes('SENT') && !labelStr.includes('INBOX')) continue;\n\n  const fromRaw = m.from || m.From || m.sender || m.Sender || '';\n  const senderEmail = extractEmail(fromRaw);\n\n  if (senderEmail === MY_GMAIL) continue;\n  if (!senderEmail.includes(leadEmail) && !leadEmail.includes(senderEmail)) continue;\n\n  const msgDate = safeParseDate(m.internalDate)\n    || safeParseDate(m.date)\n    || safeParseDate(m.Date)\n    || safeParseDate(m.receivedDate)\n    || null;\n\n  if (!msgDate) continue;\n  if (msgDate <= email1SentDate) continue;\n\n  if (extractEmail(fromRaw) === MY_GMAIL) continue;\n\n  replyFound = true;\n  replyDate = msgDate.toISOString();\n  replySubject = m.subject || m.Subject || '(no subject)';\n  replySnippet = (m.snippet || m.textPlain || m.text || m.body || '').toString().substring(0, 300);\n  break;\n}\n\nreturn [{ json: {\n  hasReply: replyFound, leadName: lead.leadName || '', email: lead.email || '',\n  company: lead.company || '', currentStatus: lead.status || '',\n  replyDate: replyDate, replySubject: replySubject, replySnippet: replySnippet\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "83fe6612-0f68-486a-94fd-0277ddc815d1",
      "name": "\ud83d\udcac Reply Found?",
      "type": "n8n-nodes-base.if",
      "position": [
        2272,
        1648
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-reply",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.hasReply }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "91a4af5a-cdbd-42c6-bdc9-0eb1245acb04",
      "name": "\u2705 Mark Replied in Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2496,
        1536
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "replied",
            "Lead Name": "={{ $json.leadName }}",
            "Reply Date": "={{ $json.replyDate }}",
            "Reply Snippet": "={{ $json.replySnippet }}",
            "Reply Subject": "={{ $json.replySubject }}"
          },
          "schema": [
            {
              "id": "Lead Name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Lead Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Reply Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Reply Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Reply Subject",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Reply Subject",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Reply Snippet",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Reply Snippet",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Lead Name"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_SHEET_GID",
          "cachedResultName": "Leads"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "46f06680-5f22-4431-9a79-a934fc9846b8",
      "name": "\ud83d\udcf2 Reply Alert! \ud83c\udf89",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2496,
        1376
      ],
      "parameters": {
        "text": "=\ud83c\udf89 *REPLY DETECTED \u2014 Auto-SDR*\n\n\ud83d\udc64 {{ $('\ud83d\udcac Reply Found?').item.json.leadName }}\n\ud83c\udfe2 {{ $('\ud83d\udcac Reply Found?').item.json.company }}\n\ud83d\udce7 From: {{ $('\ud83d\udcac Reply Found?').item.json.email }}\n\ud83d\udcdd Subject: {{ $('\ud83d\udcac Reply Found?').item.json.replySubject }}\n\n\ud83d\udcac Preview:\n_{{ $('\ud83d\udcac Reply Found?').item.json.replySnippet }}_\n\n\ud83d\uded1 Sequence stopped. Follow up manually!\n\ud83d\udcc5 {{ $('\ud83d\udcac Reply Found?').item.json.replyDate }}\n\u23f0 Detected: {{ new Date().toLocaleString() }}",
        "chatId": "YOUR_TELEGRAM_CHAT_ID",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "6985015c-8601-4beb-86d1-7a5c2723231a",
      "name": "\ud83d\udca4 No Reply Yet",
      "type": "n8n-nodes-base.code",
      "position": [
        2496,
        1744
      ],
      "parameters": {
        "jsCode": "return { json: { status: 'no_reply', leadName: $json.leadName || '', email: $json.email || '', checkedAt: new Date().toLocaleString() } };"
      },
      "typeVersion": 2
    },
    {
      "id": "6f41120c-93b1-43a7-98cc-8e54bafa2a03",
      "name": "\ud83d\udca4 No Active Leads",
      "type": "n8n-nodes-base.code",
      "position": [
        1824,
        1840
      ],
      "parameters": {
        "jsCode": "return { json: { status: 'no_active_leads_to_check', checkedAt: new Date().toLocaleString() } };"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "5cffe5e9-b1db-48f0-b5bc-7ac32e366b44",
  "connections": {
    "\ud83d\udcca Pursue?": {
      "main": [
        [
          {
            "node": "\u270d\ufe0f AI Email Writer",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "\u23ed\ufe0f Mark Skipped",
            "type": "main",
     
Pro

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

About this workflow

This workflow is built for founders, sales teams, solopreneurs, and agencies who want to automate outbound sales without expensive tools. Perfect for anyone doing cold email outreach who wants AI-powered personalization at scale.

Source: https://n8n.io/workflows/13466/ — 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 is for solopreneurs, founders, creators, and marketers who want a consistent LinkedIn presence without spending hours writing posts. Ideal for anyone in tech, SaaS, or AI who wants trend

HTTP Request, Agent, Google Sheets +2
AI & RAG

This workflow automates the process of generating, reviewing, and publishing blog posts across multiple platforms, now enhanced with support for RSS Feeds as a content source. It streamlines the manag

HTTP Request, Html Extract, RSS Feed Read +9
AI & RAG

//ASMR AI Workflow

HTTP Request, Tool Think, OpenAI Chat +6
AI & RAG

Workflow Created By: Abdullah Dilshad 📧 iamabdullahdishad@gmail.com

HTTP Request, Agent, OpenAI Chat +4
AI & RAG

This workflow is designed for stock traders, financial analysts, investment enthusiasts, and anyone interested in automated stock market analysis. It's particularly useful for those who want to make d

N8N Nodes Rapiwa, Gmail, HTTP Request +6