{
  "name": "Agent 7: Follow-up Sequencer — drafts & sends follow-ups 2x/day",
  "description": null,
  "active": false,
  "nodes": [
    {
      "parameters": {},
      "id": "t_manual",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -1408,
        -48
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "30 9 * * 1-5"
            },
            {
              "field": "cronExpression",
              "expression": "0 19 * * 1-5"
            }
          ]
        }
      },
      "id": "t_sched",
      "name": "Twice Daily — 9:30 AM & 7 PM IST",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        -1408,
        144
      ]
    },
    {
      "parameters": {
        "operation": "getAll",
        "tableId": "outreach_log",
        "matchType": "allFilters",
        "filters": {
          "conditions": [
            {
              "keyName": "reply_received",
              "condition": "eq",
              "keyValue": "false"
            },
            {
              "keyName": "follow_up_count",
              "condition": "lt",
              "keyValue": "3"
            },
            {
              "keyName": "bounced",
              "condition": "eq",
              "keyValue": "false"
            },
            {
              "keyName": "sent_date",
              "condition": "gte",
              "keyValue": "1970-01-01"
            }
          ]
        },
        "returnAll": true
      },
      "id": "n_fetch",
      "name": "Fetch Outreach Log",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        -1184,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "\nconst items=$input.all();const today=new Date();\nreturn items.map(item=>{const f=item.json;\n const sentDate=new Date(f.sent_date||today);\n const daysSince=Math.floor((today-sentDate)/(1000*60*60*24));\n let followupType=null;const count=parseInt(f.follow_up_count||0);\n if(count===0&&daysSince>=3)followupType='day3';\n else if(count===1&&daysSince>=7)followupType='day7';\n else if(count===2&&daysSince>=14)followupType='day14';\n return {json:{log_id:item.json.id,prospect_id:f.prospect_id||'',\n  original_subject:f.email_subject||'',original_body:f.email_body||'',\n  follow_up_count:count,followup_type:followupType,days_since:daysSince,\n  pain_points:f.pain_points||'',founder_name:f.founder_name||'',company:f.company||'',to_email:f.to_email||''}};\n}).filter(i=>i.json.followup_type!==null)\n .sort((a,b)=>b.json.days_since-a.json.days_since)\n .slice(0,30); // cap per run (2 runs/day = 60/day) — oldest first, controls Kimi cost + approval size\n"
      },
      "id": "n_classify",
      "name": "Classify Follow-up Type",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -960,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "return $input.all().map(item=>{const d=item.json;const num=d.follow_up_count+1;\nconst seedStr=String(d.company||'')+String(d.followup_type||'');\nlet seed=0;for(let i=0;i<seedStr.length;i++){seed=(seed*31+seedStr.charCodeAt(i))>>>0;}\nconst OPENERS=[\n 'a one-line reference to your earlier note tied to a SPECIFIC detail from it (never \"just following up\")',\n 'lead with a NEW small observation about their market or website, then connect it back to the earlier note',\n 'a candid, human one-liner acknowledging you are following up, in completely fresh words (never \"following up briefly\" or \"bumping this\")'\n];\nconst opener=OPENERS[seed%OPENERS.length];\nconst CTA3=[\n 'Would Tuesday or Thursday work for a quick call?',\n 'Would a 15-minute call next week be a bad time?',\n 'I will send the findings either way. Want to walk through them together?'];\nconst CTA7=[\n 'One call. If it is not useful, you keep the findings and never hear from me again.',\n 'Who should I walk through this with? You, or whoever runs marketing?',\n 'There are three things I would fix first. Fastest way to show you is a short call. Open to it?'];\nconst CTA14=[\n 'If search visibility becomes a priority, one 15-minute call is all I would need to show you the gap.',\n 'Before I close the file on this, worth one short call?'];\nconst fuCta=(d.followup_type==='day3'?CTA3[seed%CTA3.length]:(d.followup_type==='day7'?CTA7[seed%CTA7.length]:CTA14[seed%CTA14.length]));\n\nlet style;\nif(d.followup_type==='day3') style=`Follow-up #1 (3 days later), 60-110 words. Open with: ${opener}. Then take ONE specific point from the original email and deepen or reframe it — do not repeat it verbatim. Optional single proof sentence only if it flows naturally (never more than one statistic). End with this exact ask (or a close natural variant): \"${fuCta}\" Plain paragraphs preferred; at most one short <ul> of 2 items.`;\nelse if(d.followup_type==='day7') style=`Follow-up #2 (a week later), 60-120 words. Give them something NEW: one concrete idea, observation or quick win for ${d.company} related to the original angle — something they could act on even without you. Then a soft offer: send the fuller notes, or a 15-minute walkthrough, their choice — in your own natural words. No guilt-tripping.`;\nelse style=`Final follow-up (2 weeks later), 45-80 words, plain-text feel, zero pressure. In fresh words: this is the last note; restate in ONE sentence the single biggest opportunity you saw for ${d.company}; close with this exact line (or a close natural variant): \"${fuCta}\" Then wish them well. No bullets, no statistics.`;\nconst prompt=`You are Manvendra Singh, founder of Meraku Media (SEO & growth for premium high-ticket service businesses). Write follow-up #${num} to ${d.company} in the SAME thread as the original email below. It must read like a human typed it in two minutes — specific, warm, brief.\n\nTHE ORIGINAL EMAIL (build on ITS angle — never introduce a generic pitch):\n${(d.original_body||'').replace(/<[^>]+>/g,' ').replace(/\\s+/g,' ').slice(0,1200)}\n\nTHEIR GAPS (source material): ${d.pain_points||'(general search-visibility gaps for their market)'}\n\nFORMAT: ${style}\n\nRULES:\n- ONE SENTENCE PER PARAGRAPH: every single sentence goes in its own <p>. Never put two sentences in the same paragraph.\n- NEVER use an em dash or en dash anywhere (no \\u2014 or \\u2013). Use commas or periods instead.\n- Greeting: recipient name is \"${String(d.founder_name||'').replace(/^(dr|mr|mrs|ms|prof|shri|smt)\\.?\\s+/i,'')}\". Real first name -> \"Hi <first>,\"; else \"Hello,\". Never placeholders.\n- BANNED (used in past emails — never use these or close variants): \"Following up briefly on my note\", \"The short version of what I found\", \"What's still on the table\", \"Resurfacing this before it sinks\", \"Last note from me — I know inboxes are brutal\", \"closing this gap grew qualified inquiries 3x within six months\".\n- Vary sentence rhythm. No marketing voice, no emojis, never name a past client (no ClientA/ClientB).\n- Clean HTML: short <p> paragraphs (one short <ul> only where the format allows).\n- Subject exactly: \"Re: ${d.original_subject||'following up'}\".\n- Signoff exactly: <p>Warm regards,<br>Manvendra</p> and write NOTHING after that line (the full signature block is appended automatically)\nReturn ONLY valid JSON: {\"subject\":\"Re: ...\",\"body_html\":\"...\"}`;\nreturn {json:{...d,prompt}};});"
      },
      "id": "n_fu_prompt",
      "name": "Build Follow-up Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -736,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://moviesbar1-resource.openai.azure.com/openai/v1/chat/completions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"model\":\"gpt-5.4-2\",\"messages\":[{\"role\":\"user\",\"content\":$json.prompt}],\"max_completion_tokens\":4096} }}",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 1,
              "batchInterval": 2000
            }
          },
          "timeout": 280000
        }
      },
      "id": "n_fu_kimi",
      "name": "GPT — Write Follow-up",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -512,
        0
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const splitSentences=(html)=>String(html||'').replace(/<p([^>]*)>([\\s\\S]*?)<\\/p>/gi,(m,attrs,inner)=>{\n  if(/<(table|img|ul|ol|br)/i.test(inner)) return m;\n  const parts=inner.split(/(?<=[.?!])\\s+(?=[A-Z0-9\"'])/g).map(s=>s.trim()).filter(Boolean);\n  if(parts.length<2) return m;\n  return parts.map(s=>'<p'+attrs+'>'+s+'</p>').join('');\n});\nconst MERAKU_SIG='<p style=\"margin:16px 0 2px 0\">Warm regards,</p>'\n +'<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"font-family:Arial,Helvetica,sans-serif;border-collapse:collapse;margin-top:6px\"><tr>'\n +'<td style=\"vertical-align:middle;padding-right:12px\"><img src=\"https://yourdomain.com/your-headshot.jpg\" alt=\"Manvendra Singh\" width=\"56\" height=\"56\" style=\"display:block;width:56px;height:56px;border-radius:50%\"></td>'\n +'<td style=\"vertical-align:middle;border-left:3px solid #2e7d5b;padding-left:12px\">'\n +'<div style=\"font-size:14px;font-weight:bold;color:#1a1a1a;line-height:1.4\">Manvendra Singh</div>'\n +'<div style=\"font-size:12px;color:#5f6368;line-height:1.5\">Founder, Meraku Media</div>'\n +'<div style=\"font-size:12px;color:#5f6368;line-height:1.5;padding-top:3px\">[YOUR PHONE NUMBER]</div>'\n +'</td></tr></table>';\nconst stripOldSig=(b)=>String(b||'')\n .replace(/<p[^>]*>\\s*(warm\\s+regards|best\\s+regards|kind\\s+regards|regards|best|thanks|thank\\s+you|cheers)[\\s\\S]{0,500}?manvendra[\\s\\S]*$/i,'')\n .replace(/(warm\\s+regards|best\\s+regards|kind\\s+regards|thanks|thank\\s+you|cheers)[,!]?[\\s\\S]{0,500}?manvendra[\\s\\S]*$/i,'')\n .trim();\nconst src=$('Build Follow-up Prompt').item.json;\nlet c=$json.choices?.[0]?.message?.content||'';\nconst f=c.match(/```(?:json)?\\s*([\\s\\S]*?)```/);if(f)c=f[1].trim();\nlet p={};try{p=JSON.parse(c);}catch(e){\n try{p=JSON.parse(c.replace(/\\r?\\n/g,'\\\\n'));}catch(e2){\n  const ms=c.match(/\"subject\"\\s*:\\s*\"([^\"]*)\"/);\n  const mb=c.match(/\"body_html\"\\s*:\\s*\"([\\s\\S]*?)\"\\s*}?\\s*$/);\n  if(ms||mb) p={subject:ms?ms[1]:undefined, body_html:mb?mb[1].replace(/\\\\n/g,'\\n').replace(/\\\\\"/g,'\"'):undefined};\n }\n}\nlet body=p.body_html||p.body||c||'';\n// un-escape if the model double-escaped the HTML\nif(/&lt;|&gt;|&amp;lt;/.test(body)){\n  body=body.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g,'\"').replace(/&#39;/g,\"'\");\n}\n// if the model returned plain text (no tags), turn it into clean paragraphs\nif(!/<p|<br|<ul|<div/i.test(body)){\n  body=body.split(/\\n{2,}/).map(s=>s.trim()).filter(Boolean).map(s=>'<p>'+s.replace(/\\n/g,'<br>')+'</p>').join('');\n}\n// canonical signature: strip whatever signoff the model wrote, append the real block\n  body = body.replace(/\\s*[\\u2014\\u2013]\\s*/g, ', ');\nbody = splitSentences(body);\nbody = stripOldSig(body) + MERAKU_SIG;\nreturn {json:{...src, followup_subject:p.subject||('Re: '+(src.original_subject||'following up')), followup_body:body}};\n"
      },
      "id": "n_parse_fu",
      "name": "Parse Follow-up",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -288,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "\nconst items = $input.all();\nconst WEBHOOK = 'https://your-n8n-instance.com/webhook/yourbrand-followup-approval';\nreturn items.map(item => {\n  const d = item.json;\n  const approveUrl = `${WEBHOOK}?action=approve&log_id=${d.log_id}&prospect_id=${d.prospect_id}`;\n  const skipUrl = `${WEBHOOK}?action=skip&log_id=${d.log_id}`;\n  const typeLabel = {day3: 'Day 3', day7: 'Day 7', day14: 'Day 14 (Break-up)'}[d.followup_type] || 'Follow-up';\n  const html = `<html><body style=\"font-family:Arial,sans-serif;max-width:700px;margin:auto;padding:20px\">\n<h2 style=\"color:#2d3748\">📬 Follow-up Approval — ${typeLabel}</h2>\n<p><strong>Subject:</strong> ${d.followup_subject}</p>\n<div style=\"background:#f7fafc;border:1px solid #e2e8f0;padding:15px;border-radius:4px;white-space:pre-line\">${d.followup_body}</div>\n<div style=\"margin:20px 0;text-align:center\">\n  <a href=\"${approveUrl}\" style=\"display:inline-block;padding:10px 20px;background:#48bb78;color:white;border-radius:6px;text-decoration:none;font-weight:bold;margin:5px\">✅ SEND</a>\n  <a href=\"${skipUrl}\" style=\"display:inline-block;padding:10px 20px;background:#a0aec0;color:white;border-radius:6px;text-decoration:none;font-weight:bold;margin:5px\">⏭ SKIP</a>\n</div>\n</body></html>`;\n  return { json: { ...d, approval_html: html, approval_subject: `[Follow-up] ${typeLabel} approval` } };\n});\n"
      },
      "id": "n_build_approval",
      "name": "Build Approval Email",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1056,
        144
      ]
    },
    {
      "parameters": {
        "sendTo": "you@yourdomain.com",
        "subject": "={{ $json.subject }}",
        "message": "={{ $json.combined_html }}",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "n_gmail_approval",
      "name": "Gmail — Follow-up Approval",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1504,
        48
      ]
    },
    {
      "parameters": {
        "path": "yourbrand-followup-approval",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "n_webhook",
      "name": "Follow-up Approval Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -1408,
        656
      ]
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\nreturn items.map(item => ({\n  json: { action: item.json.query?.action || '', log_id: item.json.query?.log_id || '',\n    prospect_id: item.json.query?.prospect_id || '', confirmed: item.json.query?.confirm === '1' }\n}));"
      },
      "id": "n_wh_parse",
      "name": "Parse Follow-up Webhook",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1184,
        656
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.action }}",
              "rightValue": "approve",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "n_action_check",
      "name": "Approve or Skip?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -736,
        752
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "tableId": "outreach_log",
        "filters": {
          "conditions": [
            {
              "keyName": "id",
              "condition": "eq",
              "keyValue": "={{ $('Parse Follow-up Webhook').first().json.log_id }}"
            }
          ]
        },
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "follow_up_count",
              "fieldValue": "={{ ($('Get Log Record').first().json.follow_up_count || 0) + 1 }}"
            },
            {
              "fieldId": "followup_pending",
              "fieldValue": "={{ false }}"
            }
          ]
        }
      },
      "id": "n_at_log_update",
      "name": "Airtable — Increment Follow-up",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        608,
        560
      ],
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 2000,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "operation": "update",
        "tableId": "prospects",
        "filters": {
          "conditions": [
            {
              "keyName": "id",
              "condition": "eq",
              "keyValue": "={{ $('Parse Follow-up Webhook').first().json.prospect_id }}"
            }
          ]
        },
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "status",
              "fieldValue": "followed_up"
            },
            {
              "fieldId": "last_contacted",
              "fieldValue": "={{ new Date().toISOString().split('T')[0] }}"
            }
          ]
        }
      },
      "id": "n_at_prospect_date",
      "name": "Airtable — Update Last Contacted",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        832,
        560
      ],
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 2000,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "✅ Follow-up sent!",
        "options": {
          "responseCode": 200
        }
      },
      "id": "n_resp_ok",
      "name": "Response: Sent",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1056,
        560
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "⏭ Follow-up skipped.",
        "options": {
          "responseCode": 200
        }
      },
      "id": "n_resp_skip",
      "name": "Response: Skipped",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        -288,
        848
      ]
    },
    {
      "parameters": {
        "jsCode": "\nconst items=$input.all();\nif(!items.length) return [];\nconst WEBHOOK='https://your-n8n-instance.com/webhook/yourbrand-followup-approval';\nconst SEND_ALL='https://your-n8n-instance.com/webhook/yourbrand-followup-send-all';\nconst labels={day3:'Day 3',day7:'Day 7',day14:'Day 14 (Break-up)'};\nconst cards=items.map(it=>{\n  const d=it.json;\n  const a=`${WEBHOOK}?action=approve&log_id=${d.log_id}&prospect_id=${d.prospect_id}`;\n  const s=`${WEBHOOK}?action=skip&log_id=${d.log_id}`;\n  const t=labels[d.followup_type]||'Follow-up';\n  return `<div style=\"border:1px solid #e2e8f0;border-radius:8px;padding:16px;margin:0 0 16px\">\n   <div style=\"font-size:12px;color:#718096;text-transform:uppercase;letter-spacing:.04em\">${t}</div>\n   <p style=\"margin:6px 0\"><strong>${d.followup_subject||d.original_subject||''}</strong></p>\n   <div style=\"background:#f7fafc;border:1px solid #edf2f7;padding:12px;border-radius:6px;white-space:pre-line;font-size:14px\">${d.followup_body||''}</div>\n   <div style=\"margin-top:12px\">\n     <a href=\"${a}\" style=\"display:inline-block;padding:9px 18px;background:#48bb78;color:#fff;border-radius:6px;text-decoration:none;font-weight:bold;margin-right:8px\">SEND</a>\n     <a href=\"${s}\" style=\"display:inline-block;padding:9px 18px;background:#a0aec0;color:#fff;border-radius:6px;text-decoration:none;font-weight:bold\">SKIP</a>\n   </div></div>`;\n}).join('');\nconst count=items.length;\nconst html=`<!doctype html><html><body style=\"font-family:-apple-system,Segoe UI,Arial,sans-serif;max-width:700px;margin:auto;padding:8px;color:#1a202c\">\n<h2 style=\"margin:0 0 4px\">${count} follow-up${count>1?'s':''} ready to approve</h2>\n<p style=\"color:#4a5568;margin:0 0 14px\">Click SEND or SKIP on each, or send everything at once:</p>\n<div style=\"margin:0 0 20px\"><a href=\"${SEND_ALL}\" style=\"display:inline-block;padding:12px 24px;background:#2b6cb0;color:#fff;border-radius:6px;text-decoration:none;font-weight:bold\">✅ APPROVE ALL &amp; SEND</a></div>\n${cards}\n<p style=\"color:#a0aec0;font-size:12px;margin-top:18px\">Meraku · follow-up approvals · 9:30 AM & 7 PM IST</p>\n</body></html>`;\nreturn [{json:{count, combined_html:html, subject:`[ACTION] ${count} follow-up${count>1?'s':''} to approve`}}];\n"
      },
      "id": "fu_combine",
      "name": "Combine Follow-ups",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1280,
        144
      ]
    },
    {
      "parameters": {
        "tableId": "activity_log",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "summary",
              "fieldValue": "=Follow-ups drafted: {{ $json.count }}"
            },
            {
              "fieldId": "workflow",
              "fieldValue": "Follow-up Sequencer"
            },
            {
              "fieldId": "action",
              "fieldValue": "followup"
            },
            {
              "fieldId": "count",
              "fieldValue": "={{ $json.count }}"
            },
            {
              "fieldId": "details",
              "fieldValue": "={{ $json.count }} follow-up(s) drafted and sent for approval"
            }
          ]
        }
      },
      "id": "fu_log",
      "name": "Log → Activity_Log",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        1504,
        240
      ],
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 2000,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "\nconst NAME_PH=/\\{\\{?\\s*(first[_ ]?name|name|firstname|fname)\\s*\\}?\\}|\\[\\s*(first[_ ]?name|name|firstname|company)\\s*\\]/gi;\nconst ANY_PH=/\\{\\{[^}]*\\}\\}|\\[[A-Za-z _]{2,30}\\]|\\b(first_?name|firstname|lorem ipsum|x{4,})\\b/i;\nconst unesc=s=>(s||'').replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g,'\"').replace(/&#39;/g,\"'\").replace(/&amp;/g,'&');\nreturn $input.all().map(item=>{const d=item.json;\n let subj=(d.followup_subject||'').replace(/[\\u{1F000}-\\u{1FFFF}\\u{2600}-\\u{27BF}\\u{2190}-\\u{21FF}\\u{2B00}-\\u{2BFF}]/gu,'').trim();\n let body=d.followup_body||'';\n if(/&lt;|&gt;/.test(body)) body=unesc(unesc(body));\n const fn=(d.founder_name||'').trim();\n const first=(fn&&/^[A-Za-z][A-Za-z.'’-]+$/.test(fn.split(/\\s+/)[0]))?fn.split(/\\s+/)[0]:'';\n body=body.replace(NAME_PH, first||'there'); subj=subj.replace(NAME_PH, first||'').trim();\n const txt=(body||'').replace(/<[^>]+>/g,' ').replace(/\\s+/g,' ').trim();\n const issues=[];\n if(ANY_PH.test(subj+' '+body)) issues.push('placeholder');\n if(/\"body_html\"\\s*:|^\\s*\\{/.test(txt)) issues.push('raw JSON leaked');\n if(/\\baurum\\b|\\bgracias\\b/i.test(body)) issues.push('names past client');\n if(!subj) issues.push('empty subject');\n if(subj.length>90) issues.push('subject too long');\n const words=txt?txt.split(' ').filter(Boolean).length:0;\n if(words<40) issues.push('body too short');\n if(words>170) issues.push('body too long');\n if(!/manvendra|meraku/i.test(body)) issues.push('missing signoff');\n if(/&lt;|&gt;/.test(body)) issues.push('escaped HTML');\n return {json:{...d, followup_subject:subj, followup_body:body, gate_pass:issues.length===0, gate_issues:issues.join('; ')}};\n});\n"
      },
      "id": "fu_gate",
      "name": "Hard Gate (Follow-up)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -64,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "g1",
              "leftValue": "={{ $json.gate_pass }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ]
        },
        "options": {}
      },
      "id": "fu_gate_if",
      "name": "Gate Pass (FU)?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        160,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "return $input.all().map(item=>{const d=item.json;\nconst prompt=`Fix this follow-up email to ${d.company}. It FAILED a mechanical quality gate for: ${d.gate_issues}. Follow-up #${d.follow_up_count+1}.\nRepair only what was flagged; keep the email's existing angle and natural voice. Do NOT turn it into a template; never use \"Following up briefly\", \"The short version of what I found\", or a fixed 3-bullet recap format.\nREQUIREMENTS: greeting (\"Hi <first>,\" from \"${String(d.founder_name||'').replace(/^(dr|mr|mrs|ms|prof|shri|smt)\\.?\\s+/i,'')}\", else \"Hello,\"); 50-140 words; plain human sentences; at most one statistic; clean HTML (<p>, optional one short <ul>); no placeholders; no escaped HTML; never name a past client; subject \"Re: ${d.original_subject||'following up'}\"; no emojis; signoff <p>Warm regards,<br>Manvendra</p> and write NOTHING after that line (the full signature block is appended automatically).\nCurrent subject: ${d.followup_subject}\nCurrent body: ${d.followup_body}\nReturn ONLY JSON: {\"subject\":\"Re: ...\",\"body_html\":\"...\"}`;\nreturn {json:{...d,prompt}};});"
      },
      "id": "fu_grw_p",
      "name": "Build FU Rewrite Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        384,
        216
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://moviesbar1-resource.openai.azure.com/openai/v1/chat/completions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\"model\":\"gpt-5.4-2\",\"messages\":[{\"role\":\"user\",\"content\":$json.prompt}],\"max_completion_tokens\":4096} }}",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 1,
              "batchInterval": 2000
            }
          },
          "timeout": 280000
        }
      },
      "id": "fu_grw_k",
      "name": "GPT — FU Gate Rewrite",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        608,
        216
      ],
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 3000,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const splitSentences=(html)=>String(html||'').replace(/<p([^>]*)>([\\s\\S]*?)<\\/p>/gi,(m,attrs,inner)=>{\n  if(/<(table|img|ul|ol|br)/i.test(inner)) return m;\n  const parts=inner.split(/(?<=[.?!])\\s+(?=[A-Z0-9\"'])/g).map(s=>s.trim()).filter(Boolean);\n  if(parts.length<2) return m;\n  return parts.map(s=>'<p'+attrs+'>'+s+'</p>').join('');\n});\nconst MERAKU_SIG='<p style=\"margin:16px 0 2px 0\">Warm regards,</p>'\n +'<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"font-family:Arial,Helvetica,sans-serif;border-collapse:collapse;margin-top:6px\"><tr>'\n +'<td style=\"vertical-align:middle;padding-right:12px\"><img src=\"https://yourdomain.com/your-headshot.jpg\" alt=\"Manvendra Singh\" width=\"56\" height=\"56\" style=\"display:block;width:56px;height:56px;border-radius:50%\"></td>'\n +'<td style=\"vertical-align:middle;border-left:3px solid #2e7d5b;padding-left:12px\">'\n +'<div style=\"font-size:14px;font-weight:bold;color:#1a1a1a;line-height:1.4\">Manvendra Singh</div>'\n +'<div style=\"font-size:12px;color:#5f6368;line-height:1.5\">Founder, Meraku Media</div>'\n +'<div style=\"font-size:12px;color:#5f6368;line-height:1.5;padding-top:3px\">[YOUR PHONE NUMBER]</div>'\n +'</td></tr></table>';\nconst stripOldSig=(b)=>String(b||'')\n .replace(/<p[^>]*>\\s*(warm\\s+regards|best\\s+regards|kind\\s+regards|regards|best|thanks|thank\\s+you|cheers)[\\s\\S]{0,500}?manvendra[\\s\\S]*$/i,'')\n .replace(/(warm\\s+regards|best\\s+regards|kind\\s+regards|thanks|thank\\s+you|cheers)[,!]?[\\s\\S]{0,500}?manvendra[\\s\\S]*$/i,'')\n .trim();\n\nconst src=$('Build FU Rewrite Prompt').item.json;\nlet c=$json.choices?.[0]?.message?.content||'';\nconst f=c.match(/```(?:json)?\\s*([\\s\\S]*?)```/);if(f)c=f[1].trim();\nlet p={};try{p=JSON.parse(c);}catch(e){}\nconst unesc=s=>(s||'').replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g,'\"').replace(/&#39;/g,\"'\").replace(/&amp;/g,'&');\nconst NAME_PH=/\\{\\{?\\s*(first[_ ]?name|name|firstname|fname)\\s*\\}?\\}|\\[\\s*(first[_ ]?name|name|firstname|company)\\s*\\]/gi;\nlet subj=(p.subject||src.followup_subject||'').replace(/[\\u{1F000}-\\u{1FFFF}\\u{2600}-\\u{27BF}]/gu,'').trim();\nlet body=p.body_html||p.body||src.followup_body||'';\nif(/&lt;|&gt;/.test(body)) body=unesc(unesc(body));\nconst fn=(src.founder_name||'').trim().replace(/^(dr|mr|mrs|ms|prof|shri|smt)\\.?\\s+/i,'');const first=(fn&&/^[A-Za-z][A-Za-z.'’-]+$/.test(fn.split(/\\s+/)[0]))?fn.split(/\\s+/)[0]:'';\nbody=body.replace(NAME_PH,first||'there').replace(/\\{\\{[^}]*\\}\\}/g,first||'there'); subj=subj.replace(NAME_PH,first||'').trim();\nbody = body.replace(/\\s*[\\u2014\\u2013]\\s*/g, ', ');\nbody = splitSentences(body);\nbody = stripOldSig(body) + MERAKU_SIG;\nreturn {json:{...src, followup_subject:subj, followup_body:body, gate_pass:true}};\n"
      },
      "id": "fu_grw_parse",
      "name": "Parse FU Rewrite",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        832,
        216
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "tableId": "outreach_log",
        "filters": {
          "conditions": [
            {
              "keyName": "id",
              "condition": "eq",
              "keyValue": "={{ $json.log_id }}"
            }
          ]
        },
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "followup_subject",
              "fieldValue": "={{ $json.followup_subject }}"
            },
            {
              "fieldId": "followup_body",
              "fieldValue": "={{ $json.followup_body }}"
            },
            {
              "fieldId": "followup_pending",
              "fieldValue": "={{ true }}"
            }
          ]
        }
      },
      "id": "fu_store",
      "name": "Store Draft → Outreach_Log",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        1056,
        -48
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "operation": "get",
        "tableId": "outreach_log",
        "filters": {
          "conditions": [
            {
              "keyName": "id",
              "keyValue": "={{ $('Parse Follow-up Webhook').item.json.log_id }}"
            }
          ]
        }
      },
      "id": "fu_get",
      "name": "Get Log Record",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        -512,
        656
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "operation": "update",
        "tableId": "outreach_log",
        "filters": {
          "conditions": [
            {
              "keyName": "id",
              "condition": "eq",
              "keyValue": "={{ $('Parse Follow-up Webhook').item.json.log_id }}"
            }
          ]
        },
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "followup_pending",
              "fieldValue": "={{ false }}"
            }
          ]
        }
      },
      "id": "fu_clear",
      "name": "Clear Pending (skip)",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        -512,
        848
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "path": "yourbrand-followup-send-all",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "fua_wh",
      "name": "Send-All Followups Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -1408,
        1192
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "Sending all follow-ups now with 1-3 min gaps. You can close this tab.",
        "options": {}
      },
      "id": "fua_resp",
      "name": "Resp All Followups",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        -736,
        1288
      ]
    },
    {
      "parameters": {
        "operation": "getAll",
        "tableId": "outreach_log",
        "returnAll": true,
        "filters": {
          "conditions": [
            {
              "keyName": "followup_pending",
              "condition": "eq",
              "keyValue": "true"
            },
            {
              "keyName": "reply_received",
              "condition": "eq",
              "keyValue": "false"
            },
            {
              "keyName": "bounced",
              "condition": "eq",
              "keyValue": "false"
            }
          ]
        },
        "matchType": "allFilters"
      },
      "id": "fua_fetch",
      "name": "Fetch All Pending Followups",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        -512,
        1288
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "fua_loop",
      "name": "Loop Followups",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        -288,
        1288
      ]
    },
    {
      "parameters": {
        "jsCode": "return $input.all().map(i=>({json:{...i.json, today:new Date().toISOString().slice(0,10)}}));"
      },
      "id": "fua_stamp",
      "name": "Stamp Today FU",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -64,
        1216
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "tableId": "outreach_log",
        "filters": {
          "conditions": [
            {
              "keyName": "id",
              "condition": "eq",
              "keyValue": "={{ $('Stamp Today FU').first().json.id }}"
            }
          ]
        },
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "follow_up_count",
              "fieldValue": "={{ ($('Stamp Today FU').first().json.follow_up_count || 0) + 1 }}"
            },
            {
              "fieldId": "followup_pending",
              "fieldValue": "={{ false }}"
            }
          ]
        }
      },
      "id": "fua_inc",
      "name": "Increment FU Count Each",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        1056,
        1120
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "operation": "update",
        "tableId": "prospects",
        "filters": {
          "conditions": [
            {
              "keyName": "id",
              "condition": "eq",
              "keyValue": "={{ $('Stamp Today FU').first().json.prospect_id }}"
            }
          ]
        },
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "status",
              "fieldValue": "followed_up"
            },
            {
              "fieldId": "last_contacted",
              "fieldValue": "={{ $('Stamp Today FU').first().json.today }}"
            }
          ]
        }
      },
      "id": "fua_upd_pros",
      "name": "Update Prospect Each",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        1280,
        1120
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "amount": "={{ Math.floor(Math.random()*3)+1 }}",
        "unit": "minutes"
      },
      "id": "fua_wait",
      "name": "Random 1-3 min FU",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1504,
        1288
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.action }}",
              "rightValue": "approve",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "c2",
              "leftValue": "={{ $json.confirmed }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "false",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "fuc_if",
      "name": "Needs Confirm (FU)?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -960,
        656
      ]
    },
    {
      "parameters": {
        "jsCode": "const url='https://your-n8n-instance.com/webhook/yourbrand-followup-approval?action=approve&log_id='+$json.log_id+'&prospect_id='+$json.prospect_id+'&confirm=1';\nconst html='<html><body style=\"font-family:Arial,sans-serif;max-width:480px;margin:40px auto;text-align:center\">'+\n'<h2 style=\"color:#2d3748\">Send this follow-up?</h2><p style=\"color:#4a5568\">This will email the follow-up to the prospect immediately.</p>'+\n'<a href=\"'+url+'\" style=\"display:inline-block;background:#48bb78;color:#fff;padding:14px 32px;border-radius:8px;text-decoration:none;font-weight:bold;font-size:16px\">YES — PROCEED</a>'+\n'<p style=\"color:#a0aec0;margin-top:18px\">Close this tab to cancel. Nothing has been sent yet.</p></body></html>';\nreturn [{json:{confirm_html:html}}];"
      },
      "id": "fuc_pg",
      "name": "Build FU Confirm",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -736,
        464
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "={{ $json.confirm_html }}",
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/html; charset=utf-8"
              }
            ]
          }
        }
      },
      "id": "fuc_rs",
      "name": "Serve FU Confirm",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        -512,
        464
      ]
    },
    {
      "parameters": {
        "jsCode": "const q=$input.all()[0].json.query||{};return [{json:{confirmed:q.confirm==='1'}}];"
      },
      "id": "fuc2_ps",
      "name": "Parse FU SendAll Confirm",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1184,
        1192
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.confirmed }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "fuc2_if",
      "name": "Confirmed FU All?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -960,
        1192
      ]
    },
    {
      "parameters": {
        "jsCode": "const url='https://your-n8n-instance.com/webhook/yourbrand-followup-send-all?confirm=1';\nconst html='<html><body style=\"font-family:Arial,sans-serif;max-width:480px;margin:40px auto;text-align:center\">'+\n'<h2 style=\"color:#2d3748\">Send ALL pending follow-ups?</h2><p style=\"color:#4a5568\">Sends every drafted follow-up to prospects, spaced 1-3 min apart.</p>'+\n'<a href=\"'+url+'\" style=\"display:inline-block;background:#48bb78;color:#fff;padding:14px 32px;border-radius:8px;text-decoration:none;font-weight:bold;font-size:16px\">YES — PROCEED</a>'+\n'<p style=\"color:#a0aec0;margin-top:18px\">Close this tab to cancel. Nothing has been sent yet.</p></body></html>';\nreturn [{json:{confirm_html:html}}];"
      },
      "id": "fuc2_pg",
      "name": "Build FU SendAll Confirm",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -736,
        1096
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "={{ $json.confirm_html }}",
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/html; charset=utf-8"
              }
            ]
          }
        }
      },
      "id": "fuc2_rs",
      "name": "Serve FU SendAll Confirm",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        -512,
        1096
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "tableId": "prospects",
        "filters": {
          "conditions": [
            {
              "keyName": "email",
              "condition": "ilike",
              "keyValue": "={{ $('Get Log Record').first().json.to_email }}"
            }
          ]
        },
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "email_valid",
              "fieldValue": "={{ false }}"
            },
            {
              "fieldId": "email_status",
              "fieldValue": "send_failed"
            }
          ]
        }
      },
      "id": "fu_sf1",
      "name": "Flag FU Invalid",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        608,
        752
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "operation": "update",
        "tableId": "prospects",
        "filters": {
          "conditions": [
            {
              "keyName": "email",
              "condition": "ilike",
              "keyValue": "={{ $('Stamp Today FU').first().json.to_email }}"
            }
          ]
        },
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "email_valid",
              "fieldValue": "={{ false }}"
            },
            {
              "fieldId": "email_status",
              "fieldValue": "send_failed"
            }
          ]
        }
      },
      "id": "fu_sf2",
      "name": "Flag FU Invalid (Each)",
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        1056,
        1312
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "resource": "label",
        "operation": "create",
        "name": "ACTION",
        "options": {}
      },
      "id": "al_ensure_FU",
      "name": "Ensure 'ACTION' Label FU",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1728,
        48
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "resource": "label",
        "returnAll": true
      },
      "id": "al_get_FU",
      "name": "Get Gmail Labels (FU)",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1952,
        48
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "operation": "addLabels",
        "messageId": "={{ $('Gmail — Follow-up Approval').item.json.id }}",
        "labelIds": [
          "={{ $('Get Gmail Labels (FU)').all().find(l => (l.json.name||'').toLowerCase()==='action')?.json.id }}"
        ]
      },
      "id": "al_apply_FU",
      "name": "Apply 'ACTION' Label FU",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        2176,
        48
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "rs1",
              "leftValue": "={{ $json.reply_received }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "notEquals"
              }
            },
            {
              "id": "rs2",
              "leftValue": "={{ $json.bounced }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "notEquals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "fu_replysafe",
      "name": "Still No Reply?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -332,
        656
      ]
    },
    {
      "name": "Build Threaded Raw (Reply Fol)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        564,
        1000
      ],
      "parameters": {
        "jsCode": "const d=$json;\nconst SENDER_B_WEIGHT=25;\nconst __sh=(s)=>{s=String(s||'');let h=0;for(let i=0;i<s.length;i++){h=(h*31+s.charCodeAt(i))>>>0;}return h;};\nconst senderB=(__sh(String(d.prospect_id||d.id))%100)<SENDER_B_WEIGHT;\nconst tid=String(d.gmail_thread_id||'').trim();\nlet subj=String(d.followup_subject||'').trim();\nif(!/^re:/i.test(subj)) subj='Re: '+subj;\nconst encSubj='=?UTF-8?B?'+Buffer.from(subj,'utf8').toString('base64')+'?=';\nconst mime=['To: '+String(d.to_email||'').trim(),'Subject: '+encSubj,\n 'MIME-Version: 1.0','Content-Type: text/html; charset=UTF-8','',String(d.followup_body||'')].join('\\r\\n');\nconst raw=Buffer.from(mime,'utf8').toString('base64').replace(/\\+/g,'-').replace(/\\//g,'_').replace(/=+$/,'');\nconst payload=tid?{raw:raw,threadId:tid}:{raw:raw};\nreturn {json:{...d, payload, sender_b:senderB, threaded:!!tid}};",
        "mode": "runOnceForEachItem"
      },
      "id": "6d3affe3-1a61-4b8c-ba6d-facd523ad891"
    },
    {
      "name": "Send Threaded (Reply Fol)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        744,
        1000
      ],
      "parameters": {
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "gmailOAuth2",
        "method": "POST",
        "url": "https://gmail.googleapis.com/gmail/v1/users/me/messages/send",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.payload }}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "a0e8544a-2ea4-432f-89e6-5f767dd0f27d",
      "onError": "continueRegularOutput"
    },
    {
      "name": "Build Threaded Raw (Reply Eac)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1012,
        1560
      ],
      "parameters": {
        "jsCode": "const d=$json;\nconst SENDER_B_WEIGHT=25;\nconst __sh=(s)=>{s=String(s||'');let h=0;for(let i=0;i<s.length;i++){h=(h*31+s.charCodeAt(i))>>>0;}return h;};\nconst senderB=(__sh(String(d.prospect_id||d.id))%100)<SENDER_B_WEIGHT;\nconst tid=String(d.gmail_thread_id||'').trim();\nlet subj=String(d.followup_subject||'').trim();\nif(!/^re:/i.test(subj)) subj='Re: '+subj;\nconst encSubj='=?UTF-8?B?'+Buffer.from(subj,'utf8').toString('base64')+'?=';\nconst mime=['To: '+String(d.to_email||'').trim(),'Subject: '+encSubj,\n 'MIME-Version: 1.0','Content-Type: text/html; charset=UTF-8','',String(d.followup_body||'')].join('\\r\\n');\nconst raw=Buffer.from(mime,'utf8').toString('base64').replace(/\\+/g,'-').replace(/\\//g,'_').replace(/=+$/,'');\nconst payload=tid?{raw:raw,threadId:tid}:{raw:raw};\nreturn {json:{...d, payload, sender_b:senderB, threaded:!!tid}};",
        "mode": "runOnceForEachItem"
      },
      "id": "7b03c4ac-9e76-45bc-aa07-a1b8c530d721"
    },
    {
      "name": "Send Threaded (Reply Eac)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1192,
        1560
      ],
      "parameters": {
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "gmailOAuth2",
        "method": "POST",
        "url": "https://gmail.googleapis.com/gmail/v1/users/me/messages/send",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.payload }}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "af3741c1-b126-4bdf-94a0-a8c742d2db09",
      "onError": "continueRegularOutput"
    },
    {
      "name": "Send Threaded B",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1004,
        1160
      ],
      "parameters": {
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "gmailOAuth2",
        "method": "POST",
        "url": "https://gmail.googleapis.com/gmail/v1/users/me/messages/send",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.payload }}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "a0e8544a-2ea4-432f-89e6-5f767dd0f27d",
      "onError": "continueRegularOutput"
    },
    {
      "name": "Send Threaded B (Each)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1452,
        1720
      ],
      "parameters": {
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "gmailOAuth2",
        "method": "POST",
        "url": "https://gmail.googleapis.com/gmail/v1/users/me/messages/send",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.payload }}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "af3741c1-b126-4bdf-94a0-a8c742d2db09",
      "onError": "continueRegularOutput"
    },
    {
      "name": "Which Sender Each?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1172,
        1560
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "sb",
              "leftValue": "={{ $json.sender_b === true }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "ee89a2b3-fc33-48aa-b669-7051cbd72345"
    },
    {
      "name": "Which Sender One?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        724,
        1000
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "sb",
              "leftValue": "={{ $json.sender_b === true }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "5c7c8556-ab86-4747-b445-64638d04fe3e"
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Fetch Outreach Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Outreach Log": {
      "main": [
        [
          {
            "node": "Classify Follow-up Type",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Follow-up Type": {
      "main": [
        [
          {
            "node": "Build Follow-up Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Follow-up Prompt": {
      "main": [
        [
          {
            "node": "GPT — Write Follow-up",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GPT — Write Follow-up": {
      "main": [
        [
          {
            "node": "Parse Follow-up",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Follow-up": {
      "main": [
        [
          {
            "node": "Hard Gate (Follow-up)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Approval Email": {
      "main": [
        [
          {
            "node": "Combine Follow-ups",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Follow-up Approval Webhook": {
      "main": [
        [
          {
            "node": "Parse Follow-up Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Follow-up Webhook": {
      "main": [
        [
          {
            "node": "Needs Confirm (FU)?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Approve or Skip?": {
      "main": [
        [
          {
            "node": "Get Log Record",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Clear Pending (skip)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable — Increment Follow-up": {
      "main": [
        [
          {
            "node": "Airtable — Update Last Contacted",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable — Update Last Contacted": {
      "main": [
        [
          {
            "node": "Response: Sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Follow-ups": {
      "main": [
        [
          {
            "node": "Gmail — Follow-up Approval",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log → Activity_Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hard Gate (Follow-up)": {
      "main": [
        [
          {
            "node": "Gate Pass (FU)?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gate Pass (FU)?": {
      "main": [
        [
          {
            "node": "Store Draft → Outreach_Log",
            "type": "main",
            "index": 0
          },
          {
            "node": "Build Approval Email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build FU Rewrite Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build FU Rewrite Prompt": {
      "main": [
        [
          {
            "node": "GPT — FU Gate Rewrite",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GPT — FU Gate Rewrite": {
      "main": [
        [
          {
            "node": "Parse FU Rewrite",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse FU Rewrite": {
      "main": [
        [
          {
            "node": "Store Draft → Outreach_Log",
            "type": "main",
            "index": 0
          },
          {
            "node": "Build Approval Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Draft → Outreach_Log": {
      "main": [
        []
      ]
    },
    "Get Log Record": {
      "main": [
        [
          {
            "node": "Still No Reply?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clear Pending (skip)": {
      "main": [
        [
          {
            "node": "Response: Skipped",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send-All Followups Webhook": {
      "main": [
        [
          {
            "node": "Parse FU SendAll Confirm",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Resp All Followups": {
      "main": [
        [
          {
            "node": "Fetch All Pending Followups",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch All Pending Followups": {
      "main": [
        [
          {
            "node": "Loop Followups",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Followups": {
      "main": [
        [],
        [
          {
            "node": "Stamp Today FU",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Stamp Today FU": {
      "main": [
        [
          {
            "node": "Build Threaded Raw (Reply Eac)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Increment FU Count Each": {
      "main": [
        [
          {
            "node": "Update Prospect Each",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Prospect Each": {
      "main": [
        [
          {
            "node": "Random 1-3 min FU",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Random 1-3 min FU": {
      "main": [
        [
          {
            "node": "Loop Followups",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Twice Daily — 9:30 AM & 7 PM IST": {
      "main": [
        [
          {
            "node": "Fetch Outreach Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs Confirm (FU)?": {
      "main": [
        [
          {
            "node": "Build FU Confirm",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Approve or Skip?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build FU Confirm": {
      "main": [
        [
          {
            "node": "Serve FU Confirm",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse FU SendAll Confirm": {
      "main": [
        [
          {
            "node": "Confirmed FU All?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Confirmed FU All?": {
      "main": [
        [
          {
            "node": "Resp All Followups",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build FU SendAll Confirm",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build FU SendAll Confirm": {
      "main": [
        [
          {
            "node": "Serve FU SendAll Confirm",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail — Follow-up Approval": {
      "main": [
        [
          {
            "node": "Ensure 'ACTION' Label FU",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ensure 'ACTION' Label FU": {
      "main": [
        [
          {
            "node": "Get Gmail Labels (FU)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Gmail Labels (FU)": {
      "main": [
        [
          {
            "node": "Apply 'ACTION' Label FU",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Still No Reply?": {
      "main": [
        [
          {
            "node": "Build Threaded Raw (Reply Fol)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Clear Pending (skip)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Threaded Raw (Reply Fol)": {
      "main": [
        [
          {
            "node": "Which Sender One?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Threaded (Reply Fol)": {
      "main": [
        [
          {
            "node": "Airtable — Increment Follow-up",
            "type": "main",
            "index": 0
          },
          {
            "node": "Flag FU Invalid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Threaded Raw (Reply Eac)": {
      "main": [
        [
          {
            "node": "Which Sender Each?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Threaded (Reply Eac)": {
      "main": [
        [
          {
            "node": "Increment FU Count Each",
            "type": "main",
            "index": 0
          },
          {
            "node": "Flag FU Invalid (Each)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Threaded B": {
      "main": [
        [
          {
            "node": "Airtable — Increment Follow-up",
            "type": "main",
            "index": 0
          },
          {
            "node": "Flag FU Invalid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Threaded B (Each)": {
      "main": [
        [
          {
            "node": "Increment FU Count Each",
            "type": "main",
            "index": 0
          },
          {
            "node": "Flag FU Invalid (Each)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Which Sender Each?": {
      "main": [
        [
          {
            "node": "Send Threaded B (Each)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Threaded (Reply Eac)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Which Sender One?": {
      "main": [
        [
          {
            "node": "Send Threaded B",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Threaded (Reply Fol)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false,
    "binaryMode": "separate"
  }
}