{
  "name": "Workflow A \u2014 WhatsApp Lead Intake & Qualification",
  "nodes": [
    {
      "id": "wfa00001-0001-0001-0001-000000000001",
      "name": "Cron \u2014 Every 1 Min",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        200,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "* * * * *"
            }
          ]
        }
      }
    },
    {
      "id": "wfa00002-0002-0002-0002-000000000002",
      "name": "Claim Inbox Batch",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        440,
        300
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE lead_inbox SET status='CLAIMED', claimed_at=NOW() WHERE id IN (SELECT id FROM lead_inbox WHERE status='NEW' ORDER BY received_at LIMIT 5 FOR UPDATE SKIP LOCKED) RETURNING *;",
        "options": {}
      }
    },
    {
      "id": "wfa00003-0003-0003-0003-000000000003",
      "name": "Has Claimed Rows?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        680,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-has-rows",
              "leftValue": "={{ $json.id ?? '' }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "wfa00004-0004-0004-0004-000000000004",
      "name": "Process Each Row",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        920,
        200
      ],
      "parameters": {
        "batchSize": 1,
        "options": {}
      }
    },
    {
      "id": "wfa00005-0005-0005-0005-000000000005",
      "name": "Bake Row Context",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1160,
        200
      ],
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            {
              "id": "bake-inbox-id",
              "name": "inboxId",
              "value": "={{ $json.id }}",
              "type": "string"
            },
            {
              "id": "bake-phone",
              "name": "phoneE164",
              "value": "={{ $json.phone_e164 }}",
              "type": "string"
            },
            {
              "id": "bake-body",
              "name": "messageBody",
              "value": "={{ $json.body ?? '' }}",
              "type": "string"
            },
            {
              "id": "bake-wa-msg-id",
              "name": "waMsgId",
              "value": "={{ $json.wa_message_id }}",
              "type": "string"
            },
            {
              "id": "bake-lead-id",
              "name": "inboxLeadId",
              "value": "={{ $json.lead_id ?? '' }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "wfa00006-0006-0006-0006-000000000006",
      "name": "Resolve Lead by Phone",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "onError": "continueRegularOutput",
      "position": [
        1400,
        200
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "contentType": "json",
        "jsonBody": "={{ { query: '{ leads(filter: { phoneE164: { primaryPhoneNumber: { eq: \"' + $json.phoneE164 + '\" } } }) { edges { node { id name consentStatus qualifiedAt assignedAgent { id } } } } }' } }}",
        "options": {
          "timeout": 15000,
          "retry": {
            "enabled": true,
            "maxTries": 2,
            "waitBetweenTries": 2000
          }
        }
      }
    },
    {
      "id": "wfa00007-0007-0007-0007-000000000007",
      "name": "GQL Errors \u2014 Resolve Lead?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1640,
        200
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-gql-errors",
              "leftValue": "={{ ($json.errors?.length ?? 0) }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "wfa00008-0008-0008-0008-000000000008",
      "name": "Log GQL Resolve Error",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        1880,
        80
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO workflow_errors (workflow_name, execution_id, node_name, error_message, error_stack) VALUES ('workflow-a-lead-intake', $1, 'Resolve Lead by Phone', $2, $3)",
        "options": {
          "queryReplacement": "={{ [$execution.id, ('GQL error resolving lead: ' + ($json.errors?.[0]?.message ?? 'unknown')), ($json.errors?.[0]?.extensions?.code ?? 'GQL_ERROR')] }}"
        }
      }
    },
    {
      "id": "wfa00009-0009-0009-0009-000000000009",
      "name": "Mark Inbox Failed \u2014 GQL Error",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        2120,
        80
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE lead_inbox SET status='FAILED', error_message=$1 WHERE id=$2::bigint",
        "options": {
          "queryReplacement": "={{ ['GQL error on Resolve Lead by Phone', $('Bake Row Context').first()?.json?.inboxId] }}"
        }
      }
    },
    {
      "id": "wfa00010-0010-0010-0010-000000000010",
      "name": "Lead Exists?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1880,
        320
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-lead-exists",
              "leftValue": "={{ $json.data?.leads?.edges?.[0]?.node?.id ?? '' }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "wfa00011-0011-0011-0011-000000000011",
      "name": "Create Lead in Twenty",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "onError": "continueRegularOutput",
      "position": [
        2120,
        460
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "contentType": "json",
        "jsonBody": "={{ { query: 'mutation { createLead(data: { name: { firstName: \"Lead\", lastName: \"' + $('Bake Row Context').first()?.json?.phoneE164 + '\" }, phoneE164: { primaryPhoneNumber: \"' + $('Bake Row Context').first()?.json?.phoneE164 + '\", primaryPhoneCountryCode: \"GH\" }, consentStatus: \"PENDING\" }) { id name consentStatus } }' } }}",
        "options": {
          "timeout": 15000,
          "retry": {
            "enabled": true,
            "maxTries": 2,
            "waitBetweenTries": 2000
          }
        }
      }
    },
    {
      "id": "wfa00012-0012-0012-0012-000000000012",
      "name": "GQL Errors \u2014 Create Lead?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2360,
        460
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-create-gql-errors",
              "leftValue": "={{ ($json.errors?.length ?? 0) }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "wfa00013-0013-0013-0013-000000000013",
      "name": "Log Create Lead GQL Error",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        2600,
        360
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO workflow_errors (workflow_name, execution_id, node_name, error_message, error_stack) VALUES ('workflow-a-lead-intake', $1, 'Create Lead in Twenty', $2, $3)",
        "options": {
          "queryReplacement": "={{ [$execution.id, ('GQL error creating lead: ' + ($json.errors?.[0]?.message ?? 'unknown')), ($json.errors?.[0]?.extensions?.code ?? 'GQL_ERROR')] }}"
        }
      }
    },
    {
      "id": "wfa00014-0014-0014-0014-000000000014",
      "name": "Mark Inbox Failed \u2014 Create Lead",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        2840,
        360
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE lead_inbox SET status='FAILED', error_message=$1 WHERE id=$2::bigint",
        "options": {
          "queryReplacement": "={{ ['GQL error creating lead', $('Bake Row Context').first()?.json?.inboxId] }}"
        }
      }
    },
    {
      "id": "wfa00015-0015-0015-0015-000000000015",
      "name": "Insert lead_facts \u2014 New Lead",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        2600,
        560
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO lead_facts (lead_id, phone_e164, consent_status, last_inbound_at, updated_at) VALUES ($1, $2, 'PENDING', NOW(), NOW()) ON CONFLICT (lead_id) DO NOTHING",
        "options": {
          "queryReplacement": "={{ [$json.data?.createLead?.id, $('Bake Row Context').first()?.json?.phoneE164] }}"
        }
      }
    },
    {
      "id": "wfa00016-0016-0016-0016-000000000016",
      "name": "Get or Create Conversation \u2014 New Lead",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        2840,
        560
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO conversation (lead_id, kind) VALUES ($1, 'LEAD_QUALIFICATION') ON CONFLICT DO NOTHING; SELECT id FROM conversation WHERE lead_id=$1 AND kind='LEAD_QUALIFICATION' AND closed_at IS NULL ORDER BY started_at DESC LIMIT 1",
        "options": {
          "queryReplacement": "={{ [$('Insert lead_facts \u2014 New Lead').first()?.json?.lead_id ?? $('Create Lead in Twenty').first()?.json?.data?.createLead?.id] }}"
        }
      }
    },
    {
      "id": "wfa00017-0017-0017-0017-000000000017",
      "name": "Store Inbound Message \u2014 New Lead",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        3080,
        560
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO conversation_message (conversation_id, direction, wa_message_id, body, created_at) VALUES ($1::uuid, 'IN', $2, $3, NOW()) ON CONFLICT (wa_message_id) DO NOTHING",
        "options": {
          "queryReplacement": "={{ [$json.id, $('Bake Row Context').first()?.json?.waMsgId, $('Bake Row Context').first()?.json?.messageBody] }}"
        }
      }
    },
    {
      "id": "wfa00018-0018-0018-0018-000000000018",
      "name": "Send Consent Request",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.3,
      "position": [
        3320,
        560
      ],
      "parameters": {
        "workflowId": {
          "value": "SUBFLOW_WA_SEND_ID",
          "__rl": true,
          "mode": "id"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "to": "={{ $('Bake Row Context').first()?.json?.phoneE164 }}",
            "templateName": "consent_request",
            "templateVars": "={{ ['Lead'] }}",
            "conversationId": "={{ $('Get or Create Conversation \u2014 New Lead').first()?.json?.id ?? '' }}"
          },
          "schema": [
            {
              "id": "to",
              "displayName": "to",
              "type": "string",
              "required": true
            },
            {
              "id": "templateName",
              "displayName": "templateName",
              "type": "string",
              "required": false
            },
            {
              "id": "templateVars",
              "displayName": "templateVars",
              "type": "array",
              "required": false
            },
            {
              "id": "conversationId",
              "displayName": "conversationId",
              "type": "string",
              "required": false
            }
          ]
        }
      }
    },
    {
      "id": "wfa00019-0019-0019-0019-000000000019",
      "name": "Store Outbound Consent Request",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        3560,
        560
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO conversation_message (conversation_id, direction, body, template_name, created_at) VALUES ($1::uuid, 'OUT', $2, 'consent_request', NOW())",
        "options": {
          "queryReplacement": "={{ [$('Get or Create Conversation \u2014 New Lead').first()?.json?.id, 'Consent request template sent'] }}"
        }
      }
    },
    {
      "id": "wfa00020-0020-0020-0020-000000000020",
      "name": "Mark Inbox Processed \u2014 New Lead",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        3800,
        560
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE lead_inbox SET status='PROCESSED', processed_at=NOW() WHERE id=$1::bigint",
        "options": {
          "queryReplacement": "={{ [$('Bake Row Context').first()?.json?.inboxId] }}"
        }
      }
    },
    {
      "id": "wfa00021-0021-0021-0021-000000000021",
      "name": "Read lead_facts \u2014 Existing Lead",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        2120,
        200
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT lf.*, c.id AS conversation_id FROM lead_facts lf LEFT JOIN conversation c ON c.lead_id = lf.lead_id AND c.kind = 'LEAD_QUALIFICATION' AND c.closed_at IS NULL WHERE lf.phone_e164 = $1 ORDER BY c.started_at DESC LIMIT 1",
        "options": {
          "queryReplacement": "={{ [$('Bake Row Context').first()?.json?.phoneE164] }}"
        }
      }
    },
    {
      "id": "wfa00022-0022-0022-0022-000000000022",
      "name": "lead_facts Found?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2360,
        200
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-facts-found",
              "leftValue": "={{ $json.lead_id ?? '' }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "wfa00023-0023-0023-0023-000000000023",
      "name": "Upsert lead_facts from Twenty",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        2600,
        80
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO lead_facts (lead_id, phone_e164, consent_status, last_inbound_at, updated_at) VALUES ($1, $2, $3, NOW(), NOW()) ON CONFLICT (lead_id) DO UPDATE SET consent_status=EXCLUDED.consent_status, last_inbound_at=NOW(), updated_at=NOW(); SELECT lf.*, c.id AS conversation_id FROM lead_facts lf LEFT JOIN conversation c ON c.lead_id=lf.lead_id AND c.kind='LEAD_QUALIFICATION' AND c.closed_at IS NULL WHERE lf.lead_id=$1 ORDER BY c.started_at DESC LIMIT 1",
        "options": {
          "queryReplacement": "={{ [$('Resolve Lead by Phone').first()?.json?.data?.leads?.edges?.[0]?.node?.id, $('Bake Row Context').first()?.json?.phoneE164, ($('Resolve Lead by Phone').first()?.json?.data?.leads?.edges?.[0]?.node?.consentStatus ?? 'PENDING')] }}"
        }
      }
    },
    {
      "id": "wfa00024-0024-0024-0024-000000000024",
      "name": "Get or Create Conversation \u2014 Existing Lead",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        2840,
        200
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO conversation (lead_id, kind) VALUES ($1, 'LEAD_QUALIFICATION') ON CONFLICT DO NOTHING; SELECT id FROM conversation WHERE lead_id=$1 AND kind='LEAD_QUALIFICATION' AND closed_at IS NULL ORDER BY started_at DESC LIMIT 1",
        "options": {
          "queryReplacement": "={{ [$json.lead_id] }}"
        }
      }
    },
    {
      "id": "wfa00025-0025-0025-0025-000000000025",
      "name": "Store Inbound Message \u2014 Existing Lead",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        3080,
        200
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO conversation_message (conversation_id, direction, wa_message_id, body, created_at) VALUES ($1::uuid, 'IN', $2, $3, NOW()) ON CONFLICT (wa_message_id) DO NOTHING",
        "options": {
          "queryReplacement": "={{ [$json.id, $('Bake Row Context').first()?.json?.waMsgId, $('Bake Row Context').first()?.json?.messageBody] }}"
        }
      }
    },
    {
      "id": "wfa00026-0026-0026-0026-000000000026",
      "name": "Bake Consent and Conversation",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3320,
        200
      ],
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            {
              "id": "bake-consent",
              "name": "consentStatus",
              "value": "={{ $('Upsert lead_facts from Twenty').first()?.json?.consent_status ?? $('Read lead_facts \u2014 Existing Lead').first()?.json?.consent_status ?? 'PENDING' }}",
              "type": "string"
            },
            {
              "id": "bake-conv-id",
              "name": "conversationId",
              "value": "={{ $('Get or Create Conversation \u2014 Existing Lead').first()?.json?.id ?? '' }}",
              "type": "string"
            },
            {
              "id": "bake-lead-id2",
              "name": "leadId",
              "value": "={{ $('Upsert lead_facts from Twenty').first()?.json?.lead_id ?? $('Read lead_facts \u2014 Existing Lead').first()?.json?.lead_id ?? '' }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "wfa00027-0027-0027-0027-000000000027",
      "name": "Switch on Consent Status",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        3560,
        200
      ],
      "parameters": {
        "mode": "rules",
        "rules": {
          "rules": [
            {
              "outputIndex": 0,
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "leftValue": "",
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "cond-pending",
                    "leftValue": "={{ $json.consentStatus }}",
                    "rightValue": "PENDING",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              }
            },
            {
              "outputIndex": 1,
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "leftValue": "",
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "cond-granted",
                    "leftValue": "={{ $json.consentStatus }}",
                    "rightValue": "GRANTED",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              }
            },
            {
              "outputIndex": 2,
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "leftValue": "",
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "cond-declined",
                    "leftValue": "={{ $json.consentStatus }}",
                    "rightValue": "REFUSED",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "wfa00028-0028-0028-0028-000000000028",
      "name": "Parse Consent Reply",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3800,
        80
      ],
      "parameters": {
        "language": "javaScript",
        "jsCode": "const body = ($('Bake Row Context').first()?.json?.messageBody ?? '').toLowerCase().trim();\nconst yesWords = ['yes','y','agree','ok','consent','yep','yeah','sure','accept'];\nconst noWords = ['no','n','stop','decline','refuse','nope','nah','cancel'];\nconst isYes = yesWords.some(w => body === w || body.startsWith(w + ' '));\nconst isNo  = noWords.some(w => body === w || body.startsWith(w + ' '));\nreturn [{ json: { consentReply: isYes ? 'YES' : isNo ? 'NO' : 'UNCLEAR', leadId: $('Bake Row Context').first()?.json?.inboxLeadId || '', phoneE164: $('Bake Row Context').first()?.json?.phoneE164, conversationId: $('Bake Consent and Conversation').first()?.json?.conversationId } }];"
      }
    },
    {
      "id": "wfa00029-0029-0029-0029-000000000029",
      "name": "Consent Reply \u2014 YES or NO?",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        4040,
        80
      ],
      "parameters": {
        "mode": "rules",
        "rules": {
          "rules": [
            {
              "outputIndex": 0,
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "cond-yes",
                    "leftValue": "={{ $json.consentReply }}",
                    "rightValue": "YES",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              }
            },
            {
              "outputIndex": 1,
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "cond-no",
                    "leftValue": "={{ $json.consentReply }}",
                    "rightValue": "NO",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "wfa00030-0030-0030-0030-000000000030",
      "name": "Update Lead Consent GRANTED",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "onError": "continueRegularOutput",
      "position": [
        4280,
        0
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "contentType": "json",
        "jsonBody": "={{ { query: 'mutation { updateLead(id: \"' + $('Bake Consent and Conversation').first()?.json?.leadId + '\", data: { consentStatus: \"GRANTED\" }) { id consentStatus } }' } }}",
        "options": {
          "timeout": 15000,
          "retry": {
            "enabled": true,
            "maxTries": 2,
            "waitBetweenTries": 2000
          }
        }
      }
    },
    {
      "id": "wfa00031-0031-0031-0031-000000000031",
      "name": "Update lead_facts Consent GRANTED",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        4520,
        0
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE lead_facts SET consent_status='GRANTED', updated_at=NOW() WHERE lead_id=$1",
        "options": {
          "queryReplacement": "={{ [$('Bake Consent and Conversation').first()?.json?.leadId] }}"
        }
      }
    },
    {
      "id": "wfa00032-0032-0032-0032-000000000032",
      "name": "Send Qualification Opener",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.3,
      "position": [
        4760,
        0
      ],
      "parameters": {
        "workflowId": {
          "value": "SUBFLOW_WA_SEND_ID",
          "__rl": true,
          "mode": "id"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "to": "={{ $('Bake Row Context').first()?.json?.phoneE164 }}",
            "body": "Great, thanks for agreeing! I'm here to help you find your ideal property in Accra or Ashanti. Could you tell me what you're looking for \u2014 are you buying or renting?",
            "conversationId": "={{ $('Bake Consent and Conversation').first()?.json?.conversationId }}"
          },
          "schema": [
            {
              "id": "to",
              "displayName": "to",
              "type": "string",
              "required": true
            },
            {
              "id": "body",
              "displayName": "body",
              "type": "string",
              "required": false
            },
            {
              "id": "conversationId",
              "displayName": "conversationId",
              "type": "string",
              "required": false
            }
          ]
        }
      }
    },
    {
      "id": "wfa00033-0033-0033-0033-000000000033",
      "name": "Store Outbound Opener",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        5000,
        0
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO conversation_message (conversation_id, direction, body, created_at) VALUES ($1::uuid, 'OUT', $2, NOW())",
        "options": {
          "queryReplacement": "={{ [$('Bake Consent and Conversation').first()?.json?.conversationId, 'Great, thanks for agreeing! Qualification opener sent.'] }}"
        }
      }
    },
    {
      "id": "wfa00034-0034-0034-0034-000000000034",
      "name": "Mark Inbox Processed \u2014 Consent YES",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        5240,
        0
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE lead_inbox SET status='PROCESSED', processed_at=NOW() WHERE id=$1::bigint",
        "options": {
          "queryReplacement": "={{ [$('Bake Row Context').first()?.json?.inboxId] }}"
        }
      }
    },
    {
      "id": "wfa00035-0035-0035-0035-000000000035",
      "name": "Update Lead Consent DECLINED",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "onError": "continueRegularOutput",
      "position": [
        4280,
        160
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "contentType": "json",
        "jsonBody": "={{ { query: 'mutation { updateLead(id: \"' + $('Bake Consent and Conversation').first()?.json?.leadId + '\", data: { consentStatus: \"REFUSED\" }) { id consentStatus } }' } }}",
        "options": {
          "timeout": 15000,
          "retry": {
            "enabled": true,
            "maxTries": 2,
            "waitBetweenTries": 2000
          }
        }
      }
    },
    {
      "id": "wfa00036-0036-0036-0036-000000000036",
      "name": "Update lead_facts Consent DECLINED",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        4520,
        160
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE lead_facts SET consent_status='REFUSED', do_not_contact=true, updated_at=NOW() WHERE lead_id=$1",
        "options": {
          "queryReplacement": "={{ [$('Bake Consent and Conversation').first()?.json?.leadId] }}"
        }
      }
    },
    {
      "id": "wfa00037-0037-0037-0037-000000000037",
      "name": "Send Decline Acknowledgement",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.3,
      "position": [
        4760,
        160
      ],
      "parameters": {
        "workflowId": {
          "value": "SUBFLOW_WA_SEND_ID",
          "__rl": true,
          "mode": "id"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "to": "={{ $('Bake Row Context').first()?.json?.phoneE164 }}",
            "body": "No problem at all. We won't contact you further. If you change your mind, just message us again. Take care!",
            "conversationId": "={{ $('Bake Consent and Conversation').first()?.json?.conversationId }}"
          },
          "schema": [
            {
              "id": "to",
              "displayName": "to",
              "type": "string",
              "required": true
            },
            {
              "id": "body",
              "displayName": "body",
              "type": "string",
              "required": false
            },
            {
              "id": "conversationId",
              "displayName": "conversationId",
              "type": "string",
              "required": false
            }
          ]
        }
      }
    },
    {
      "id": "wfa00038-0038-0038-0038-000000000038",
      "name": "Mark Inbox Processed \u2014 Consent NO",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        5000,
        160
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE lead_inbox SET status='PROCESSED', processed_at=NOW() WHERE id=$1::bigint",
        "options": {
          "queryReplacement": "={{ [$('Bake Row Context').first()?.json?.inboxId] }}"
        }
      }
    },
    {
      "id": "wfa00039-0039-0039-0039-000000000039",
      "name": "Mark Inbox Processed \u2014 Consent UNCLEAR",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        4280,
        300
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE lead_inbox SET status='PROCESSED', processed_at=NOW() WHERE id=$1::bigint",
        "options": {
          "queryReplacement": "={{ [$('Bake Row Context').first()?.json?.inboxId] }}"
        }
      }
    },
    {
      "id": "wfa00040-0040-0040-0040-000000000040",
      "name": "Fetch Conversation History",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        3800,
        200
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT direction, body, created_at FROM conversation_message WHERE conversation_id=$1::uuid ORDER BY created_at DESC LIMIT 30",
        "options": {
          "queryReplacement": "={{ [$('Bake Consent and Conversation').first()?.json?.conversationId] }}"
        }
      }
    },
    {
      "id": "wfa00041-0041-0041-0041-000000000041",
      "name": "Build Messages Array",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4040,
        200
      ],
      "parameters": {
        "language": "javaScript",
        "jsCode": "const rows = $input.all();\nconst sorted = rows.slice().sort((a, b) => new Date(a.json.created_at) - new Date(b.json.created_at));\nconst messages = sorted.map(r => ({ role: r.json.direction === 'IN' ? 'user' : 'assistant', content: r.json.body ?? '' }));\nconst leadId = $('Bake Consent and Conversation').first()?.json?.leadId ?? '';\nconst conversationId = $('Bake Consent and Conversation').first()?.json?.conversationId ?? '';\nconst phoneE164 = $('Bake Row Context').first()?.json?.phoneE164 ?? '';\nconst inboxId = $('Bake Row Context').first()?.json?.inboxId ?? '';\nconst waMsgId = $('Bake Row Context').first()?.json?.waMsgId ?? '';\nconst messageBody = $('Bake Row Context').first()?.json?.messageBody ?? '';\nreturn [{ json: { messages, leadId, conversationId, phoneE164, inboxId, waMsgId, messageBody } }];"
      }
    },
    {
      "id": "wfa00042-0042-0042-0042-000000000042",
      "name": "Generate Reply \u2014 gpt-4.1-mini",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.3,
      "position": [
        4280,
        200
      ],
      "parameters": {
        "workflowId": {
          "value": "SUBFLOW_OPENAI_CALL_ID",
          "__rl": true,
          "mode": "id"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "system": "You are a friendly, professional real-estate assistant for a Ghanaian agency. Your job is to qualify leads by gathering: intent (BUY or RENT), budget range in GHS, minimum bedrooms, property type(s), target areas in Greater Accra or Ashanti, desired move-in timeline in months, and financing status. Ask one question at a time. Use plain Ghanaian English. Be warm but concise. Never mention you are an AI unless directly asked.",
            "messages": "={{ $json.messages }}",
            "model": "gpt-4.1-mini",
            "max_tokens": 300,
            "workflow_label": "workflow-a-reply"
          },
          "schema": [
            {
              "id": "system",
              "displayName": "system",
              "type": "string",
              "required": true
            },
            {
              "id": "messages",
              "displayName": "messages",
              "type": "array",
              "required": true
            },
            {
              "id": "model",
              "displayName": "model",
              "type": "string",
              "required": false
            },
            {
              "id": "max_tokens",
              "displayName": "max_tokens",
              "type": "number",
              "required": false
            },
            {
              "id": "workflow_label",
              "displayName": "workflow_label",
              "type": "string",
              "required": false
            }
          ]
        }
      }
    },
    {
      "id": "wfa00043-0043-0043-0043-000000000043",
      "name": "Extract Qualification Facts \u2014 gpt-4.1-mini",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.3,
      "position": [
        4520,
        200
      ],
      "parameters": {
        "workflowId": {
          "value": "SUBFLOW_OPENAI_CALL_ID",
          "__rl": true,
          "mode": "id"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "system": "You are a data-extraction assistant. From the conversation history, extract the lead qualification facts as a JSON object. Return ONLY a valid JSON object with these exact keys: intent (BUY or RENT or null), budgetMinGhs (integer or null), budgetMaxGhs (integer or null), bedroomsMin (integer 1-6 or null), propertyType (array of strings from: APARTMENT, HOUSE, TOWNHOUSE, LAND, COMMERCIAL, SELF_CONTAIN, CHAMBER_AND_HALL, BOYS_QUARTERS; or empty array), targetAreas (array of area names or empty array), timelineMonths (integer 0-12 or null), financingStatus (CASH or MORTGAGE_PRE_APPROVED or MORTGAGE_PENDING or UNKNOWN), confidence (float 0.0-1.0 representing overall extraction confidence). No explanation, no markdown, just the JSON object.",
            "messages": "={{ $('Build Messages Array').first()?.json?.messages }}",
            "model": "gpt-4.1-mini",
            "max_tokens": 400,
            "response_format": "json_object",
            "workflow_label": "workflow-a-extract-facts"
          },
          "schema": [
            {
              "id": "system",
              "displayName": "system",
              "type": "string",
              "required": true
            },
            {
              "id": "messages",
              "displayName": "messages",
              "type": "array",
              "required": true
            },
            {
              "id": "model",
              "displayName": "model",
              "type": "string",
              "required": false
            },
            {
              "id": "max_tokens",
              "displayName": "max_tokens",
              "type": "number",
              "required": false
            },
            {
              "id": "response_format",
              "displayName": "response_format",
              "type": "string",
              "required": false
            },
            {
              "id": "workflow_label",
              "displayName": "workflow_label",
              "type": "string",
              "required": false
            }
          ]
        }
      }
    },
    {
      "id": "wfa00044-0044-0044-0044-000000000044",
      "name": "Parse Facts JSON",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4760,
        200
      ],
      "parameters": {
        "language": "javaScript",
        "jsCode": "const rawText = $json.text ?? '{}';\nlet facts = {};\ntry { facts = JSON.parse(rawText); } catch(e) { facts = {}; }\nconst intent = facts.intent ?? null;\nconst budgetMinGhs = facts.budgetMinGhs ?? null;\nconst budgetMaxGhs = facts.budgetMaxGhs ?? null;\nconst bedroomsMin = facts.bedroomsMin ?? null;\nconst propertyType = Array.isArray(facts.propertyType) ? facts.propertyType : [];\nconst targetAreas = Array.isArray(facts.targetAreas) ? facts.targetAreas : [];\nconst timelineMonths = facts.timelineMonths ?? null;\nconst financingStatus = facts.financingStatus ?? 'UNKNOWN';\nconst confidence = typeof facts.confidence === 'number' ? facts.confidence : 0;\nconst allRequired = intent !== null && budgetMinGhs !== null && budgetMaxGhs !== null && bedroomsMin !== null && propertyType.length > 0;\nconst qualified = allRequired && confidence >= 0.8;\nconst replyText = $('Generate Reply \u2014 gpt-4.1-mini').first()?.json?.text ?? '';\nconst leadId = $('Build Messages Array').first()?.json?.leadId ?? '';\nconst conversationId = $('Build Messages Array').first()?.json?.conversationId ?? '';\nconst phoneE164 = $('Build Messages Array').first()?.json?.phoneE164 ?? '';\nconst inboxId = $('Build Messages Array').first()?.json?.inboxId ?? '';\nconst waMsgId = $('Build Messages Array').first()?.json?.waMsgId ?? '';\nreturn [{ json: { intent, budgetMinGhs, budgetMaxGhs, bedroomsMin, propertyType, targetAreas, timelineMonths, financingStatus, confidence, qualified, allRequired, replyText, leadId, conversationId, phoneE164, inboxId, waMsgId } }];"
      }
    },
    {
      "id": "wfa00045-0045-0045-0045-000000000045",
      "name": "Qualification Complete?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        5000,
        200
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-qualified",
              "leftValue": "={{ $json.qualified }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "wfa00046-0046-0046-0046-000000000046",
      "name": "Update Lead Qualified Fields",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "onError": "continueRegularOutput",
      "position": [
        5240,
        80
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "contentType": "json",
        "jsonBody": "={{ (function() { const f = $json; const score = Math.round((f.confidence ?? 0) * 100); const ptArr = (f.propertyType || []).map(function(v) { return '\"' + v + '\"'; }).join(','); const taArr = (f.targetAreas || []).map(function(v) { return '\"' + v + '\"'; }).join(','); return { query: 'mutation { updateLead(id: \"' + f.leadId + '\", data: { qualifiedAt: \"' + new Date().toISOString() + '\", qualifiedScore: ' + score + (f.intent ? ', intent: \"' + f.intent + '\"' : '') + (f.budgetMinGhs !== null ? ', budgetMinGhs: ' + f.budgetMinGhs : '') + (f.budgetMaxGhs !== null ? ', budgetMaxGhs: ' + f.budgetMaxGhs : '') + (f.bedroomsMin !== null ? ', bedroomsMin: ' + f.bedroomsMin : '') + (ptArr ? ', propertyType: [' + ptArr + ']' : '') + (taArr ? ', targetAreas: [' + taArr + ']' : '') + (f.timelineMonths !== null ? ', timelineMonths: ' + f.timelineMonths : '') + (f.financingStatus ? ', financingStatus: \"' + f.financingStatus + '\"' : '') + ' }) { id qualifiedAt } }' }; })() }}",
        "options": {
          "timeout": 15000,
          "retry": {
            "enabled": true,
            "maxTries": 2,
            "waitBetweenTries": 2000
          }
        }
      }
    },
    {
      "id": "wfa00047-0047-0047-0047-000000000047",
      "name": "Assign Agent",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        5480,
        80
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT assign_agent_for_property_type($1) AS agent_id",
        "options": {
          "queryReplacement": "={{ [$('Parse Facts JSON').first()?.json?.propertyType?.[0] ?? 'APARTMENT'] }}"
        }
      }
    },
    {
      "id": "wfa00048-0048-0048-0048-000000000048",
      "name": "Agent Found?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        5720,
        80
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-agent-found",
              "leftValue": "={{ $json.agent_id ?? '' }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "wfa00049-0049-0049-0049-000000000049",
      "name": "Update Lead Assigned Agent",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "onError": "continueRegularOutput",
      "position": [
        5960,
        0
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "contentType": "json",
        "jsonBody": "={{ { query: 'mutation { updateLead(id: \"' + $('Parse Facts JSON').first()?.json?.leadId + '\", data: { assignedAgentId: \"' + $json.agent_id + '\" }) { id assignedAgent { id } } }' } }}",
        "options": {
          "timeout": 15000,
          "retry": {
            "enabled": true,
            "maxTries": 2,
            "waitBetweenTries": 2000
          }
        }
      }
    },
    {
      "id": "wfa00050-0050-0050-0050-000000000050",
      "name": "Update lead_facts Assigned Agent",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        6200,
        0
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE lead_facts SET assigned_agent_id=$1, qualified_at=NOW(), updated_at=NOW() WHERE lead_id=$2",
        "options": {
          "queryReplacement": "={{ [$('Assign Agent').first()?.json?.agent_id, $('Parse Facts JSON').first()?.json?.leadId] }}"
        }
      }
    },
    {
      "id": "wfa00051-0051-0051-0051-000000000051",
      "name": "Create ReviewTask \u2014 AGENT_POOL_EMPTY",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "onError": "continueRegularOutput",
      "position": [
        5960,
        160
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "contentType": "json",
        "jsonBody": "={{ { query: 'mutation { createReviewTask(data: { kind: \"AGENT_POOL_EMPTY\", subjectLead: { connect: { where: { id: \"' + ($('Parse Facts JSON').first()?.json?.leadId ?? '') + '\" } } } }) { id kind } }' } }}",
        "options": {
          "timeout": 15000,
          "retry": {
            "enabled": true,
            "maxTries": 2,
            "waitBetweenTries": 2000
          }
        }
      }
    },
    {
      "id": "wfa00052-0052-0052-0052-000000000052",
      "name": "Calibration Window Active?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        6440,
        80
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-calibration",
              "leftValue": "={{ $env.CALIBRATION_WINDOW_ACTIVE ?? 'false' }}",
              "rightValue": "true",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "wfa00053-0053-0053-0053-000000000053",
      "name": "Create ReviewTask \u2014 LEAD_REPLY_REVIEW",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "onError": "continueRegularOutput",
      "position": [
        6680,
        0
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "contentType": "json",
        "jsonBody": "={{ { query: 'mutation { createReviewTask(data: { kind: \"LEAD_REPLY_REVIEW\", subjectLead: { connect: { where: { id: \"' + ($('Parse Facts JSON').first()?.json?.leadId ?? '') + '\" } } } }) { id kind } }' } }}",
        "options": {
          "timeout": 15000,
          "retry": {
            "enabled": true,
            "maxTries": 2,
            "waitBetweenTries": 2000
          }
        }
      }
    },
    {
      "id": "wfa00054-0054-0054-0054-000000000054",
      "name": "Send Qualified Reply",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.3,
      "position": [
        6680,
        160
      ],
      "parameters": {
        "workflowId": {
          "value": "SUBFLOW_WA_SEND_ID",
          "__rl": true,
          "mode": "id"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "to": "={{ $('Parse Facts JSON').first()?.json?.phoneE164 }}",
            "body": "={{ $('Parse Facts JSON').first()?.json?.replyText }}",
            "conversationId": "={{ $('Parse Facts JSON').first()?.json?.conversationId }}"
          },
          "schema": [
            {
              "id": "to",
              "displayName": "to",
              "type": "string",
              "required": true
            },
            {
              "id": "body",
              "displayName": "body",
              "type": "string",
              "required": false
            },
            {
              "id": "conversationId",
              "displayName": "conversationId",
              "type": "string",
              "required": false
            }
          ]
        }
      }
    },
    {
      "id": "wfa00055-0055-0055-0055-000000000055",
      "name": "Store Outbound Qualified Reply",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        6920,
        160
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO conversation_message (conversation_id, direction, body, created_at) VALUES ($1::uuid, 'OUT', $2, NOW())",
        "options": {
          "queryReplacement": "={{ [$('Parse Facts JSON').first()?.json?.conversationId, $('Parse Facts JSON').first()?.json?.replyText] }}"
        }
      }
    },
    {
      "id": "wfa00056-0056-0056-0056-000000000056",
      "name": "Mark Inbox Processed \u2014 Qualified",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        7160,
        160
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE lead_inbox SET status='PROCESSED', processed_at=NOW() WHERE id=$1::bigint",
        "options": {
          "queryReplacement": "={{ [$('Parse Facts JSON').first()?.json?.inboxId] }}"
        }
      }
    },
    {
      "id": "wfa00057-0057-0057-0057-000000000057",
      "name": "Count Outbound Turns",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        5240,
        320
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT COUNT(*) AS turn_count FROM conversation_message WHERE conversation_id=$1::uuid AND direction='OUT'",
        "options": {
          "queryReplacement": "={{ [$('Parse Facts JSON').first()?.json?.conversationId] }}"
        }
      }
    },
    {
      "id": "wfa00058-0058-0058-0058-000000000058",
      "name": "Turn Limit Reached?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        5480,
        320
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-turn-limit",
              "leftValue": "={{ Number($json.turn_count ?? 0) }}",
              "rightValue": 8,
              "operator": {
                "type": "number",
                "operation": "gte"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "wfa00059-0059-0059-0059-000000000059",
      "name": "Create ReviewTask \u2014 LEAD_QUALIFICATION_STUCK",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "onError": "continueRegularOutput",
      "position": [
        5720,
        240
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $env.TWENTY_API_URL + '/graphql' }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $env.TWENTY_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "contentType": "json",
        "jsonBody": "={{ { query: 'mutation { createReviewTask(data: { kind: \"LEAD_QUALIFICATION_STUCK\", subjectLead: { connect: { where: { id: \"' + ($('Parse Facts JSON').first()?.json?.leadId ?? '') + '\" } } } }) { id kind } }' } }}",
        "options": {
          "timeout": 15000,
          "retry": {
            "enabled": true,
            "maxTries": 2,
            "waitBetweenTries": 2000
          }
        }
      }
    },
    {
      "id": "wfa00060-0060-0060-0060-000000000060",
      "name": "Mark Inbox Processed \u2014 Stuck",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        5960,
        240
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE lead_inbox SET status='PROCESSED', processed_at=NOW() WHERE id=$1::bigint",
        "options": {
          "queryReplacement": "={{ [$('Parse Facts JSON').first()?.json?.inboxId] }}"
        }
      }
    },
    {
      "id": "wfa00061-0061-0061-0061-000000000061",
      "name": "Send Ongoing Qualification Reply",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.3,
      "position": [
        5720,
        400
      ],
      "parameters": {
        "workflowId": {
          "value": "SUBFLOW_WA_SEND_ID",
          "__rl": true,
          "mode": "id"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "to": "={{ $('Parse Facts JSON').first()?.json?.phoneE164 }}",
            "body": "={{ $('Parse Facts JSON').first()?.json?.replyText }}",
            "conversationId": "={{ $('Parse Facts JSON').first()?.json?.conversationId }}"
          },
          "schema": [
            {
              "id": "to",
              "displayName": "to",
              "type": "string",
              "required": true
            },
            {
              "id": "body",
              "displayName": "body",
              "type": "string",
              "required": false
            },
            {
              "id": "conversationId",
              "displayName": "conversationId",
              "type": "string",
              "required": false
            }
          ]
        }
      }
    },
    {
      "id": "wfa00062-0062-0062-0062-000000000062",
      "name": "Store Outbound Qualification Reply",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        5960,
        400
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO conversation_message (conversation_id, direction, body, created_at) VALUES ($1::uuid, 'OUT', $2, NOW())",
        "options": {
          "queryReplacement": "={{ [$('Parse Facts JSON').first()?.json?.conversationId, $('Parse Facts JSON').first()?.json?.replyText] }}"
        }
      }
    },
    {
      "id": "wfa00063-0063-0063-0063-000000000063",
      "name": "Mark Inbox Processed \u2014 Qualifying",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        6200,
        400
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE lead_inbox SET status='PROCESSED', processed_at=NOW() WHERE id=$1::bigint",
        "options": {
          "queryReplacement": "={{ [$('Parse Facts JSON').first()?.json?.inboxId] }}"
        }
      }
    },
    {
      "id": "wfa00064-0064-0064-0064-000000000064",
      "name": "Mark Inbox Processed \u2014 DECLINED",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        3800,
        400
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE lead_inbox SET status='PROCESSED', processed_at=NOW() WHERE id=$1::bigint",
        "options": {
          "queryReplacement": "={{ [$('Bake Row Context').first()?.json?.inboxId] }}"
        }
      }
    },
    {
      "id": "wfa00065-0065-0065-0065-000000000065",
      "name": "Batch Done \u2014 Exit",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        920,
        80
      ],
      "parameters": {}
    },
    {
      "id": "wfa00099-0099-0099-0099-000000000099",
      "name": "Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "typeVersion": 1,
      "position": [
        200,
        600
      ],
      "parameters": {}
    },
    {
      "id": "wfa00100-0100-0100-0100-000000000100",
      "name": "Log A Error",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "alwaysOutputData": true,
      "position": [
        440,
        600
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO workflow_errors (workflow_name, execution_id, node_name, error_message, error_stack, input_payload) VALUES ('workflow-a-lead-intake', $1, $2, $3, $4, $5::jsonb)",
        "options": {
          "queryReplacement": "={{ [$json.execution.id, ($json.execution.lastNodeExecuted || 'unknown'), ($json.execution.error.message || 'unknown error'), ($json.execution.error.stack ? $json.execution.error.stack.split('\\n')[0] : ''), '{\"source\":\"error_trigger\"}'] }}"
        }
      }
    }
  ],
  "connections": {
    "Cron \u2014 Every 1 Min": {
      "main": [
        [
          {
            "node": "Claim Inbox Batch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claim Inbox Batch": {
      "main": [
        [
          {
            "node": "Has Claimed Rows?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Claimed Rows?": {
      "main": [
        [
          {
            "node": "Process Each Row",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Process Each Row": {
      "main": [
        [
          {
            "node": "Batch Done \u2014 Exit",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Bake Row Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Bake Row Context": {
      "main": [
        [
          {
            "node": "Resolve Lead by Phone",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Resolve Lead by Phone": {
      "main": [
        [
          {
            "node": "GQL Errors \u2014 Resolve Lead?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GQL Errors \u2014 Resolve Lead?": {
      "main": [
        [
          {
            "node": "Log GQL Resolve Error",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Lead Exists?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log GQL Resolve Error": {
      "main": [
        [
          {
            "node": "Mark Inbox Failed \u2014 GQL Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Inbox Failed \u2014 GQL Error": {
      "main": [
        [
          {
            "node": "Process Each Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lead Exists?": {
      "main": [
        [
          {
            "node": "Read lead_facts \u2014 Existing Lead",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create Lead in Twenty",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Lead in Twenty": {
      "main": [
        [
          {
            "node": "GQL Errors \u2014 Create Lead?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GQL Errors \u2014 Create Lead?": {
      "main": [
        [
          {
            "node": "Log Create Lead GQL Error",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Insert lead_facts \u2014 New Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Create Lead GQL Error": {
      "main": [
        [
          {
            "node": "Mark Inbox Failed \u2014 Create Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Inbox Failed \u2014 Create Lead": {
      "main": [
        [
          {
            "node": "Process Each Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert lead_facts \u2014 New Lead": {
      "main": [
        [
          {
            "node": "Get or Create Conversation \u2014 New Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get or Create Conversation \u2014 New Lead": {
      "main": [
        [
          {
            "node": "Store Inbound Message \u2014 New Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Inbound Message \u2014 New Lead": {
      "main": [
        [
          {
            "node": "Send Consent Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Consent Request": {
      "main": [
        [
          {
            "node": "Store Outbound Consent Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Outbound Consent Request": {
      "main": [
        [
          {
            "node": "Mark Inbox Processed \u2014 New Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Inbox Processed \u2014 New Lead": {
      "main": [
        [
          {
            "node": "Process Each Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read lead_facts \u2014 Existing Lead": {
      "main": [
        [
          {
            "node": "lead_facts Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "lead_facts Found?": {
      "main": [
        [
          {
            "node": "Get or Create Conversation \u2014 Existing Lead",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Upsert lead_facts from Twenty",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upsert lead_facts from Twenty": {
      "main": [
        [
          {
            "node": "Get or Create Conversation \u2014 Existing Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get or Create Conversation \u2014 Existing Lead": {
      "main": [
        [
          {
            "node": "Store Inbound Message \u2014 Existing Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Inbound Message \u2014 Existing Lead": {
      "main": [
        [
          {
            "node": "Bake Consent and Conversation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Bake Consent and Conversation": {
      "main": [
        [
          {
            "node": "Switch on Consent Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch on Consent Status": {
      "main": [
        [
          {
            "node": "Parse Consent Reply",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Fetch Conversation History",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Mark Inbox Processed \u2014 DECLINED",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Consent Reply": {
      "main": [
        [
          {
            "node": "Consent Reply \u2014 YES or NO?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Consent Reply \u2014 YES or NO?": {
      "main": [
        [
          {
            "node": "Update Lead Consent GRANTED",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Update Lead Consent DECLINED",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Mark Inbox Processed \u2014 Consent UNCLEAR",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Lead Consent GRANTED": {
      "main": [
        [
          {
            "node": "Update lead_facts Consent GRANTED",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update lead_facts Consent GRANTED": {
      "main": [
        [
          {
            "node": "Send Qualification Opener",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Qualification Opener": {
      "main": [
        [
          {
            "node": "Store Outbound Opener",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Outbound Opener": {
      "main": [
        [
          {
            "node": "Mark Inbox Processed \u2014 Consent YES",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Inbox Processed \u2014 Consent YES": {
      "main": [
        [
          {
            "node": "Process Each Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Lead Consent DECLINED": {
      "main": [
        [
          {
            "node": "Update lead_facts Consent DECLINED",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update lead_facts Consent DECLINED": {
      "main": [
        [
          {
            "node": "Send Decline Acknowledgement",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Decline Acknowledgement": {
      "main": [
        [
          {
            "node": "Mark Inbox Processed \u2014 Consent NO",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Inbox Processed \u2014 Consent NO": {
      "main": [
        [
          {
            "node": "Process Each Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Inbox Processed \u2014 Consent UNCLEAR": {
      "main": [
        [
          {
            "node": "Process Each Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Inbox Processed \u2014 DECLINED": {
      "main": [
        [
          {
            "node": "Process Each Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Conversation History": {
      "main": [
        [
          {
            "node": "Build Messages Array",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Messages Array": {
      "main": [
        [
          {
            "node": "Generate Reply \u2014 gpt-4.1-mini",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Reply \u2014 gpt-4.1-mini": {
      "main": [
        [
          {
            "node": "Extract Qualification Facts \u2014 gpt-4.1-mini",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Qualification Facts \u2014 gpt-4.1-mini": {
      "main": [
        [
          {
            "node": "Parse Facts JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Facts JSON": {
      "main": [
        [
          {
            "node": "Qualification Complete?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Qualification Complete?": {
      "main": [
        [
          {
            "node": "Update Lead Qualified Fields",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Count Outbound Turns",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Lead Qualified Fields": {
      "main": [
        [
          {
            "node": "Assign Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assign Agent": {
      "main": [
        [
          {
            "node": "Agent Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agent Found?": {
      "main": [
        [
          {
            "node": "Update Lead Assigned Agent",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create ReviewTask \u2014 AGENT_POOL_EMPTY",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Lead Assigned Agent": {
      "main": [
        [
          {
            "node": "Update lead_facts Assigned Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update lead_facts Assigned Agent": {
      "main": [
        [
          {
            "node": "Calibration Window Active?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create ReviewTask \u2014 AGENT_POOL_EMPTY": {
      "main": [
        [
          {
            "node": "Calibration Window Active?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calibration Window Active?": {
      "main": [
        [
          {
            "node": "Create ReviewTask \u2014 LEAD_REPLY_REVIEW",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Qualified Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create ReviewTask \u2014 LEAD_REPLY_REVIEW": {
      "main": [
        [
          {
            "node": "Mark Inbox Processed \u2014 Qualified",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Qualified Reply": {
      "main": [
        [
          {
            "node": "Store Outbound Qualified Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Outbound Qualified Reply": {
      "main": [
        [
          {
            "node": "Mark Inbox Processed \u2014 Qualified",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Inbox Processed \u2014 Qualified": {
      "main": [
        [
          {
            "node": "Process Each Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Count Outbound Turns": {
      "main": [
        [
          {
            "node": "Turn Limit Reached?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Turn Limit Reached?": {
      "main": [
        [
          {
            "node": "Create ReviewTask \u2014 LEAD_QUALIFICATION_STUCK",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Ongoing Qualification Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create ReviewTask \u2014 LEAD_QUALIFICATION_STUCK": {
      "main": [
        [
          {
            "node": "Mark Inbox Processed \u2014 Stuck",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Inbox Processed \u2014 Stuck": {
      "main": [
        [
          {
            "node": "Process Each Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Ongoing Qualification Reply": {
      "main": [
        [
          {
            "node": "Store Outbound Qualification Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Outbound Qualification Reply": {
      "main": [
        [
          {
            "node": "Mark Inbox Processed \u2014 Qualifying",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Inbox Processed \u2014 Qualifying": {
      "main": [
        [
          {
            "node": "Process Each Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Trigger": {
      "main": [
        [
          {
            "node": "Log A Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "tags": [
    {
      "name": "real-estate-automation"
    },
    {
      "name": "workflow-a"
    },
    {
      "name": "version-1.0"
    }
  ]
}