{
  "name": "Lead Workflow C - Calendly Meeting Booked Detection",
  "nodes": [
    {
      "id": "2c27b53c-a6e4-4675-a50c-f0d8440d2788",
      "name": "Calendly Invitee Created Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -860,
        680
      ],
      "parameters": {
        "path": "leads/calendly-invitee-created",
        "httpMethod": "POST",
        "responseMode": "onReceived",
        "options": {}
      }
    },
    {
      "id": "ea07c01b-c9fb-4fd5-a594-f194f6f4443f",
      "name": "Extract Calendly Invitee Email",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -600,
        680
      ],
      "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 extracts the invitee email from Calendly and prepares parameterized update and notification payloads.\nconst env = (typeof $env !== \"undefined\") ? $env : ((typeof process !== \"undefined\" && process.env) ? process.env : {});\nconst payload = $json;\nconst roots = [payload, payload.body, payload.payload, payload.body?.payload, payload.event, payload.body?.event].filter(Boolean);\nlet email = \"\";\nlet name = \"\";\nfor (const root of roots) {\n  email = email || root.email || root.invitee_email || root.invitee?.email || root.resource?.email || root.resource?.invitee_email || \"\";\n  name = name || root.name || root.invitee_name || root.invitee?.name || root.resource?.name || \"\";\n}\nemail = String(email).trim().toLowerCase();\nname = String(name || \"A lead\").trim();\nif (!email || !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)) {\n  throw new Error(\"Calendly payload did not include a valid invitee email.\");\n}\n\nconst airtableBaseUrl = \"https://api.airtable.com/v0/\" + encodeURIComponent(env.AIRTABLE_BASE_ID || \"REPLACE_AIRTABLE_BASE_ID\") + \"/\" + encodeURIComponent(env.AIRTABLE_TABLE_NAME || \"Leads\");\nconst airtablePatchBody = {\n  performUpsert: { fieldsToMergeOn: [\"Email\"] },\n  records: [\n    {\n      fields: {\n        Email: email,\n        Name: name,\n        Status: \"meeting_booked\",\n        \"Meeting Booked\": true,\n      },\n    },\n  ],\n};\n\nconst gotifyPayload = {\n  title: \"Lead booked a call\",\n  message: name + \" (\" + email + \") booked a call.\",\n  priority: 8,\n};\n\nreturn {\n  email,\n  name,\n  airtableBaseUrl,\n  airtablePatchBody,\n  gotifyPayload,\n  pgParams: [email, airtableBaseUrl, JSON.stringify(airtablePatchBody), JSON.stringify(gotifyPayload)],\n};"
      }
    },
    {
      "id": "75cb4abb-3800-429d-aa9f-9b737a680ad5",
      "name": "Mark Lead Meeting Booked in Postgres",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        -340,
        680
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE leads\nSET meeting_booked = TRUE,\n    status = 'meeting_booked'\nWHERE LOWER(email) = LOWER($1)\nRETURNING\n  id,\n  name,\n  email,\n  meeting_booked,\n  status,\n  $2::text AS airtable_base_url,\n  $3::jsonb AS airtable_patch_body,\n  $4::jsonb AS gotify_payload;",
        "options": {
          "queryReplacement": "={{ $json.pgParams }}"
        }
      }
    },
    {
      "id": "a057ca12-735c-4f99-94f4-f8fc77208021",
      "name": "Patch Airtable Meeting Booked Status",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -80,
        680
      ],
      "parameters": {
        "method": "PATCH",
        "url": "={{ $json.airtable_base_url }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.airtable_patch_body) }}",
        "options": {}
      }
    },
    {
      "id": "22b9a06e-952d-47ff-82f8-204dfacc7c5e",
      "name": "Notify Gotify Meeting Booked",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        180,
        680
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ (($env.GOTIFY_URL || \"http://gotify:80\").replace(/\\/$/, \"\")) + \"/message\" }}",
        "authentication": "none",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "X-Gotify-Key",
              "value": "={{ $env.GOTIFY_TOKEN }}"
            }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($(\"Mark Lead Meeting Booked in Postgres\").first().json.gotify_payload) }}",
        "options": {}
      }
    }
  ],
  "connections": {
    "Calendly Invitee Created Webhook": {
      "main": [
        [
          {
            "node": "Extract Calendly Invitee Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Calendly Invitee Email": {
      "main": [
        [
          {
            "node": "Mark Lead Meeting Booked in Postgres",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Lead Meeting Booked in Postgres": {
      "main": [
        [
          {
            "node": "Patch Airtable Meeting Booked Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Patch Airtable Meeting Booked Status": {
      "main": [
        [
          {
            "node": "Notify Gotify Meeting Booked",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Tirane"
  }
}
