{
  "name": "04b — Outreach Touch 2 (Day +4)",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "5 9 * * 1-5"
            }
          ]
        }
      },
      "id": "wf04b-0001",
      "name": "Weekdays 09:05",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT l.*, lc.id as contact_id, lc.full_name, lc.role_title, lc.email,\n       es.body as touch1_body, es.subject as touch1_subject\nFROM leads.leads l\nJOIN leads.lead_contacts lc ON lc.lead_id = l.id AND lc.is_primary = true\nJOIN leads.email_sends es ON es.lead_id = l.id AND es.touch_number = 1\nWHERE l.state = 'contacted'\n  AND es.sent_at <= NOW() - INTERVAL '4 days'\n  AND lc.email IS NOT NULL\n  AND NOT EXISTS (\n    SELECT 1 FROM leads.email_sends es2\n    WHERE es2.lead_id = l.id AND es2.touch_number = 2\n  )\nORDER BY es.sent_at ASC LIMIT 10",
        "additionalFields": {}
      },
      "id": "wf04b-0002",
      "name": "Fetch Touch 2 Ready Leads",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        460,
        300
      ]
    },
    {
      "parameters": {
        "batchSize": 1,
        "options": {}
      },
      "id": "wf04b-0003",
      "name": "Split Leads",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        680,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const l = $json;\nconst ra = l.research_artifact || {};\nconst senderName = $env.SENDER_NAME || 'Artenis';\n\nconst systemPrompt = `You are ${senderName}. Write a brief, natural follow-up — NOT a sales pitch.\n\nRecipient: ${l.full_name}, ${l.role_title} at ${l.company_name}\n4 days ago you asked them about: ${ra.the_specific_workflow}\nThey have not replied.\n\nRULES:\n- Under 40 words total\n- One paragraph\n- Reference your previous question naturally — you're genuinely curious if they solved it or just live with it\n- Light, human tone — not pushy\n- ZERO mention of your services, automation, or anything you sell\n- NO link, NO call to action\n- Sign off: just your first name\n\nSUBJECT: Start with \"Re: \" followed by 3-5 words about their workflow topic\n\nBANNED: I noticed, streamline, automate, solution, I help, I build, I work with, following up to see if\n\nOUTPUT: valid JSON only — {\"subject\":\"...\",\"body\":\"...\"}\nUse \\\\n for line breaks.`;\n\nreturn [{ json: { ...l, groq_system: systemPrompt, groq_user: 'Write the follow-up email. JSON only. Use \\\\n not real newlines.', _regen_count: 0 } }];"
      },
      "id": "wf04b-0004",
      "name": "Build Touch 2 Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        900,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.groq.com/openai/v1/chat/completions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: 'llama-3.3-70b-versatile', messages: [{ role: 'system', content: $json.groq_system }, { role: 'user', content: $json.groq_user }], temperature: 0.4, max_tokens: 400 }) }}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "wf04b-0005",
      "name": "Generate Touch 2",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1120,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const response = $json;\nconst prev = $('Build Touch 2 Prompt').item.json;\nconst raw = response.choices?.[0]?.message?.content || '';\n\nfunction fixAndParse(s) {\n  let inStr = false, esc = false, out = [];\n  for (const c of s) {\n    const code = c.charCodeAt(0);\n    if (esc) { out.push(c); esc = false; }\n    else if (code === 92) { out.push(c); esc = true; }\n    else if (code === 34) { inStr = !inStr; out.push(c); }\n    else if (code === 10 && inStr) { out.push('\\\\n'); }\n    else { out.push(c); }\n  }\n  return JSON.parse(out.join(''));\n}\n\nlet subject = '', body = '';\ntry {\n  const m = raw.match(/\\{[\\s\\S]*?\"subject\"[\\s\\S]*?\"body\"[\\s\\S]*?\\}/);\n  if (m) { const p = fixAndParse(m[0]); subject = p.subject.trim(); body = p.body.replace(/\\\\n/g,'\\n').trim(); }\n} catch(e) {}\n\nif (!subject || !body) {\n  const lines = raw.trim().split('\\n');\n  subject = lines[0].replace(/^subject:\\s*/i,'').trim();\n  body = lines.slice(1).join('\\n').trim();\n}\n\nreturn [{ json: { ...prev, generated_subject: subject, generated_body: body, raw_output: raw } }];"
      },
      "id": "wf04b-0006",
      "name": "Parse Output",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1340,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "conditions": [
            {
              "id": "nh",
              "leftValue": "={{ $json.generated_body }}",
              "rightValue": "NEEDS_HUMAN",
              "operator": {
                "type": "string",
                "operation": "equal"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "wf04b-0007",
      "name": "NEEDS_HUMAN?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1560,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const body = $json.generated_body || '';\nconst subject = $json.generated_subject || '';\nconst combined = subject + ' ' + body;\nconst moneyRegex = /\\b(price|pricing|cost|costs|budget|rate|rates|fee|fees|hour|hourly|package|affordable|cheap)\\b|[€$£]|\\b\\d{2,}\\s*(eur|usd|gbp|lek)\\b/i;\nconst aiTellsRegex = /\\b(I hope this (email|message) finds you well|just (touching|checking|circling) (in|back)|leverage|synergy|streamline your|revolutionize|game[- ]?changer|cutting[- ]?edge|harness the power|unlock the potential|in today's (fast-paced|digital|competitive)|delve into|tapestry)\\b/i;\nconst failed = moneyRegex.test(combined) || aiTellsRegex.test(combined);\nreturn [{ json: { ...$json, validation_passed: !failed } }];"
      },
      "id": "wf04b-0008",
      "name": "Validate",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1780,
        420
      ]
    },
    {
      "parameters": {
        "conditions": {
          "conditions": [
            {
              "id": "v",
              "leftValue": "={{ $json.validation_passed }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equal"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "wf04b-0009",
      "name": "Validation Passed?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2000,
        420
      ]
    },
    {
      "parameters": {
        "conditions": {
          "conditions": [
            {
              "id": "regen",
              "leftValue": "={{ $json._regen_count }}",
              "rightValue": 1,
              "operator": {
                "type": "number",
                "operation": "lt"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "wf04b-0010",
      "name": "Can Regen?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2220,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "return [{ json: { ...$json, _regen_count: ($json._regen_count || 0) + 1 } }];"
      },
      "id": "wf04b-0011",
      "name": "Increment Regen",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2440,
        200
      ]
    },
    {
      "parameters": {
        "jsCode": "const senderName = $env.SENDER_NAME || 'Your Name';\nconst senderEmail = $env.SENDER_EMAIL || 'you@example.com';\nconst body = $json.generated_body;\nconst signature = `\n\n${senderName}\n${senderEmail}`;\nreturn [{ json: { ...$json, final_body: body + signature } }];"
      },
      "id": "wf04b-0012",
      "name": "Append Signature",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2220,
        560
      ]
    },
    {
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "subject": "={{ $json.generated_subject }}",
        "emailType": "text",
        "message": "={{ $json.final_body }}",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "wf04b-0013",
      "name": "Send via Gmail",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        2440,
        560
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO leads.email_sends (lead_id, contact_id, touch_number, subject, body, sent_at) VALUES ($1, $2, 2, $3, $4, NOW())",
        "additionalFields": {
          "queryParams": "={{ JSON.stringify([$json.id, $json.contact_id, $json.generated_subject, $json.final_body]) }}"
        }
      },
      "id": "wf04b-0014",
      "name": "Insert email_sends",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        2660,
        560
      ]
    }
  ],
  "connections": {
    "Weekdays 09:05": {
      "main": [
        [
          {
            "node": "Fetch Touch 2 Ready Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Touch 2 Ready Leads": {
      "main": [
        [
          {
            "node": "Split Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Leads": {
      "main": [
        [],
        [
          {
            "node": "Build Touch 2 Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Touch 2 Prompt": {
      "main": [
        [
          {
            "node": "Generate Touch 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Touch 2": {
      "main": [
        [
          {
            "node": "Parse Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Output": {
      "main": [
        [
          {
            "node": "NEEDS_HUMAN?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "NEEDS_HUMAN?": {
      "main": [
        [
          {
            "node": "Split Leads",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Validate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate": {
      "main": [
        [
          {
            "node": "Validation Passed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validation Passed?": {
      "main": [
        [
          {
            "node": "Can Regen?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Append Signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Can Regen?": {
      "main": [
        [
          {
            "node": "Increment Regen",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Split Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Increment Regen": {
      "main": [
        [
          {
            "node": "Generate Touch 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append Signature": {
      "main": [
        [
          {
            "node": "Send via Gmail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send via Gmail": {
      "main": [
        [
          {
            "node": "Insert email_sends",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert email_sends": {
      "main": [
        [
          {
            "node": "Split Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
