{
  "name": "Lead Workflow B - Daily Follow-up Sequence",
  "nodes": [
    {
      "id": "ea0a9ac3-f42f-4222-bf75-2142a3883a85",
      "name": "Daily 09:00 Follow-up Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -1100,
        260
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * *"
            }
          ]
        }
      }
    },
    {
      "id": "4edc91e7-384e-4c9d-94a6-944334b012d0",
      "name": "Select Leads Ready for Follow-up",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        -840,
        260
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT\n  id,\n  name,\n  email,\n  phone,\n  raw_message,\n  source,\n  qualified_data,\n  score,\n  classification,\n  status,\n  followup_count,\n  last_followup_at,\n  meeting_booked,\n  created_at\nFROM leads\nWHERE status IN ('new', 'contacted', 'warm')\n  AND followup_count < 3\n  AND meeting_booked = FALSE\n  AND (last_followup_at IS NULL OR last_followup_at < NOW() - INTERVAL '3 days')\nORDER BY last_followup_at NULLS FIRST, created_at ASC;",
        "options": {}
      },
      "alwaysOutputData": true
    },
    {
      "id": "f4c8fe7c-c7a2-43e8-a7d1-da2b0e2fcd83",
      "name": "Leads Found for Follow-up",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -580,
        260
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "3c866a08-4941-4c79-a9f9-a56b437a1861",
              "leftValue": "={{ $json.id }}",
              "rightValue": "",
              "operator": {
                "type": "number",
                "operation": "exists",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "2737f32d-77ce-4e98-9f39-6effd83edf1f",
      "name": "Process One Lead at a Time",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 1,
      "position": [
        -320,
        260
      ],
      "parameters": {
        "batchSize": 1,
        "options": {}
      }
    },
    {
      "id": "bdc969b6-5cb7-4778-8208-20719429216e",
      "name": "Build Follow-up Email Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -60,
        260
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const config = {\\n  businessName:       \"Prime Properties Agency\",\\n  vertical:           \"real estate agency\",\\n  ownerName:          \"Marco\",\\n  calendarLink:       \"https://calendly.com/prime-properties/intro\",\\n  hotThreshold:       7,\\n  warmThreshold:      4,\\n\\n  qualificationFields: [\\n    \"budget\",\\n    \"buy_or_rent\",\\n    \"location_preference\",\\n    \"bedrooms\",\\n    \"timeline_months\",\\n    \"currently_renting_or_owning\"\\n  ],\\n\\n  scoringRules: `\\n    Score higher if: budget is confirmed, timeline is under 3 months,\\n    location is specific, they mention a reason for moving.\\n    Score lower if: no budget mentioned, timeline is vague or over 12 months,\\n    message is very short with no specifics.\\n  `,\\n\\n  followupAngles: [\\n    \"gentle check-in, ask if they had a chance to think it over, mention 2 new listings\",\\n    \"add value \\u2014 share one market insight relevant to their search criteria\",\\n    \"soft close \\u2014 this is the last message, leave the door open with no pressure\"\\n  ],\\n\\n  proofElement: \"We helped 14 families find their home in Tirana this quarter.\",\\n};\n\n// This Code node picks the configured follow-up angle and builds the Groq follow-up prompt for one lead.\nconst env = (typeof $env !== \"undefined\") ? $env : ((typeof process !== \"undefined\" && process.env) ? process.env : {});\nconst lead = $json;\nconst qualified_data = typeof lead.qualified_data === \"string\" ? JSON.parse(lead.qualified_data) : (lead.qualified_data || {});\nconst followupCount = Number(lead.followup_count || 0);\nconst name = lead.name;\nconst airtableBaseUrl = \"https://api.airtable.com/v0/\" + encodeURIComponent(env.AIRTABLE_BASE_ID || \"REPLACE_AIRTABLE_BASE_ID\") + \"/\" + encodeURIComponent(env.AIRTABLE_TABLE_NAME || \"Leads\");\n\nconst followupPrompt = `\nWrite follow-up email number ${followupCount + 1} from ${config.ownerName}\nat ${config.businessName} to ${name}.\n\nTheir original need: ${qualified_data.summary}\nFollow-up angle: ${config.followupAngles[followupCount]}\nSocial proof to use if relevant: ${config.proofElement}\n\nRules:\n- Under 80 words\n- Do not repeat what previous emails said\n- End with a simple yes/no question\nReturn plain text only. No subject line. No sign-off.\n`;\n\nreturn {\n  ...lead,\n  qualified_data,\n  followupCount,\n  prompt: followupPrompt,\n  airtableBaseUrl,\n};"
      }
    },
    {
      "id": "5e49bdba-98e0-42a0-bf00-42b3ac451630",
      "name": "Groq Generate Follow-up Email",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        200,
        260
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.groq.com/openai/v1/chat/completions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: \"llama-3.3-70b-versatile\", temperature: 0.3, max_tokens: 600, messages: [{ role: \"system\", content: \"You are a lead qualification and email writing AI. Follow instructions exactly. Return only what is asked.\" }, { role: \"user\", content: $json.prompt }] }) }}",
        "options": {}
      }
    },
    {
      "id": "192f03b4-778a-43a8-b3aa-b848ae3ddd2c",
      "name": "Parse Follow-up Email",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        260
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const config = {\\n  businessName:       \"Prime Properties Agency\",\\n  vertical:           \"real estate agency\",\\n  ownerName:          \"Marco\",\\n  calendarLink:       \"https://calendly.com/prime-properties/intro\",\\n  hotThreshold:       7,\\n  warmThreshold:      4,\\n\\n  qualificationFields: [\\n    \"budget\",\\n    \"buy_or_rent\",\\n    \"location_preference\",\\n    \"bedrooms\",\\n    \"timeline_months\",\\n    \"currently_renting_or_owning\"\\n  ],\\n\\n  scoringRules: `\\n    Score higher if: budget is confirmed, timeline is under 3 months,\\n    location is specific, they mention a reason for moving.\\n    Score lower if: no budget mentioned, timeline is vague or over 12 months,\\n    message is very short with no specifics.\\n  `,\\n\\n  followupAngles: [\\n    \"gentle check-in, ask if they had a chance to think it over, mention 2 new listings\",\\n    \"add value \\u2014 share one market insight relevant to their search criteria\",\\n    \"soft close \\u2014 this is the last message, leave the door open with no pressure\"\\n  ],\\n\\n  proofElement: \"We helped 14 families find their home in Tirana this quarter.\",\\n};\n\n// This Code node parses Groq's follow-up email text and keeps the current lead item intact.\nconst lead = $(\"Build Follow-up Email Prompt\").first().json;\nconst raw = $json.choices[0].message.content;\nconst emailBody = raw.replace(/```[a-z]*\\n?/gi, \"\").trim();\nreturn {\n  ...lead,\n  emailBody,\n  gmailSubject: \"Just checking in\",\n};"
      }
    },
    {
      "id": "17b67c04-f325-43b4-8da0-7efac755d001",
      "name": "Send Follow-up via Gmail OAuth",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        720,
        260
      ],
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "subject": "={{ $json.gmailSubject }}",
        "message": "={{ $json.emailBody }}",
        "emailType": "text",
        "options": {
          "appendAttribution": false
        }
      }
    },
    {
      "id": "9e7ad78d-d538-42e0-89d0-38b0ee290e0c",
      "name": "Update Follow-up State in Postgres",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        980,
        260
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE leads\nSET followup_count = followup_count + 1,\n    last_followup_at = NOW(),\n    status = CASE WHEN followup_count + 1 >= 3 THEN 'exhausted' ELSE status END\nWHERE id = $1\nRETURNING id, email, followup_count, status;",
        "options": {
          "queryReplacement": "={{ [$(\"Parse Follow-up Email\").item.json.id] }}"
        }
      }
    },
    {
      "id": "66b29e1f-b4b9-43a1-a229-8d7188c0000f",
      "name": "Patch Airtable Follow-up State",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1240,
        260
      ],
      "parameters": {
        "method": "PATCH",
        "url": "={{ $(\"Build Follow-up Email Prompt\").item.json.airtableBaseUrl }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ performUpsert: { fieldsToMergeOn: [\"Email\"] }, records: [{ fields: { Email: $(\"Parse Follow-up Email\").item.json.email, \"Follow-up Count\": $json.followup_count, Status: $json.status } }] }) }}",
        "options": {}
      }
    }
  ],
  "connections": {
    "Daily 09:00 Follow-up Trigger": {
      "main": [
        [
          {
            "node": "Select Leads Ready for Follow-up",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select Leads Ready for Follow-up": {
      "main": [
        [
          {
            "node": "Leads Found for Follow-up",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Leads Found for Follow-up": {
      "main": [
        [
          {
            "node": "Process One Lead at a Time",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process One Lead at a Time": {
      "main": [
        [
          {
            "node": "Build Follow-up Email Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Follow-up Email Prompt": {
      "main": [
        [
          {
            "node": "Groq Generate Follow-up Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Groq Generate Follow-up Email": {
      "main": [
        [
          {
            "node": "Parse Follow-up Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Follow-up Email": {
      "main": [
        [
          {
            "node": "Send Follow-up via Gmail OAuth",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Follow-up via Gmail OAuth": {
      "main": [
        [
          {
            "node": "Update Follow-up State in Postgres",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Follow-up State in Postgres": {
      "main": [
        [
          {
            "node": "Patch Airtable Follow-up State",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Patch Airtable Follow-up State": {
      "main": [
        [
          {
            "node": "Process One Lead at a Time",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Tirane"
  }
}
