{
  "nodes": [
    {
      "id": "3377d1a8-776c-4cf5-aae0-c899eaf2289f",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        112,
        864
      ],
      "parameters": {
        "rule": {
          "interval": [
            {}
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "8aff285b-90dd-4532-aa51-49c4fa34fb00",
      "name": "HubSpot: Get Associations",
      "type": "n8n-nodes-base.hubspot",
      "position": [
        992,
        864
      ],
      "parameters": {
        "dealId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.id }}"
        },
        "filters": {},
        "resource": "deal",
        "operation": "get",
        "authentication": "oAuth2"
      },
      "typeVersion": 2.2
    },
    {
      "id": "194c69ab-a676-4130-889b-38eda4299015",
      "name": "HubSpot: Get Email",
      "type": "n8n-nodes-base.hubspot",
      "position": [
        1440,
        864
      ],
      "parameters": {
        "contactId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.contactId }}"
        },
        "operation": "get",
        "authentication": "oAuth2",
        "additionalFields": {
          "propertiesCollection": {
            "propertiesValues": {
              "properties": [
                "hs_buying_role",
                "email",
                "firstname",
                "notes_last_contacted"
              ],
              "propertyMode": "valueOnly"
            }
          }
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "ad8bc677-9ce2-430a-87ba-b2d0d9c4141b",
      "name": "AI: Generate Email",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1984,
        864
      ],
      "parameters": {
        "text": "=Your task is to write a short, friendly, and helpful email to a customer regarding their pending contract.\n\n## Rules\n- Be professional, helpful, and warm.\n- DO NOT sound corporate or angry.\n- The goal is to re-engage them and offer help.\n- You are writing on behalf of the Customer Success team.\n\n## Content to Include\n- Greet them by their first name.\n- Gently mention that it's been a little while since we sent over the contract, and I wanted to check in.\n- Ask if they have run into any questions or if there is anything holding up the process on their end.\n- Offer to help and provide a link to a calendar to book a quick call: [Book a quick 15-minute call]([Your Calendar Link])\n\n## Signature\nEnd the email with \"Thanks,\" followed by the sender's signature.\n\n**Important:** The signature must be formatted as clean, simple HTML (use <br> for line breaks), exactly like this:\n\n[Sender Name]\n\n[Company Name]\n\n## Use the variables below\n- Recipient's first name: {{ $('HubSpot: Get Email').item.json.properties.firstname.value }}\n- Calendar Link: {{ $('Config: Setup Variables').item.json.calendar_link }}\n- Sender Name: {{ $('Config: Setup Variables').item.json.sender_name }}\n- Company Name: {{ $('Config: Setup Variables').item.json.company_name }}\n\nIMPORTANT: Output the body as raw HTML (e.g. <p>, <br>). Do not use Markdown.",
        "options": {},
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 2.2
    },
    {
      "id": "dcfc5002-faff-4ef3-9cfc-8ae6ccd35521",
      "name": "Model: GPT-5-mini",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1984,
        1088
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5-mini",
          "cachedResultName": "gpt-5-mini"
        },
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "2a3b79d8-f278-498b-8b5a-1cdc583a680a",
      "name": "Parser: Email Structure",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        2128,
        1088
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"subject\": {\n      \"type\": \"string\",\n      \"description\": \"The subject line for the email\"\n    },\n    \"body\": {\n      \"type\": \"string\",\n      \"description\": \"The full, complete body of the email, formatted in HTML\"\n    }\n  },\n  \"required\": [\"subject\", \"body\"]\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "e347d306-a5d8-4ad9-911b-1c5ab0555783",
      "name": "Slack: Notify CSM",
      "type": "n8n-nodes-base.slack",
      "position": [
        2464,
        864
      ],
      "parameters": {
        "text": "=\ud83d\udd14 STALLED ONBOARDING ALERT \ud83d\udd14\nCustomer: {{ $('HubSpot: Get Email').item.json.properties.firstname.value }}\nDeal: {{ $('Code: Get Primary Contact').item.json.dealName }}\nThis customer has been in the 'Onboarding in Progress' stage for over 2 weeks. We just sent them an automated re-engagement email.  Please follow up with them.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": ""
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "typeVersion": 2.3
    },
    {
      "id": "0170b296-387e-44ae-9c69-c179449d1077",
      "name": "Gmail: Send Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2688,
        864
      ],
      "parameters": {
        "sendTo": "={{ $('HubSpot: Get Email').item.json.properties.email.value }}",
        "message": "={{ $('AI: Generate Email').item.json.output.body }}",
        "options": {},
        "subject": "={{ $('AI: Generate Email').item.json.output.subject }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "a26569b6-7a74-4d76-919e-1968d80e85a0",
      "name": "Config: Setup Variables",
      "type": "n8n-nodes-base.set",
      "position": [
        320,
        864
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "7397ffc9-77ea-4ab1-8685-480a77d22ec7",
              "name": "sender_name",
              "type": "string",
              "value": "Your Sender Name"
            },
            {
              "id": "abd823ce-cb6c-4deb-ae88-879768e9cff2",
              "name": "company_name",
              "type": "string",
              "value": "Your Company Name"
            },
            {
              "id": "6e61132f-b7ac-433f-af11-4509f95dd904",
              "name": "calendar_link",
              "type": "string",
              "value": "https://your-calendar-link.com"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "6a974580-fcd2-4358-9f1b-d7da41a9c640",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        128,
        64
      ],
      "parameters": {
        "width": 592,
        "height": 352,
        "content": "# Automate Stalled HubSpot Deals\n\nDaily check for deals stalled in the **\"Contract Sent\"** stage, sending an AI re-engagement email to the primary contact.\n\n## Setup\n1. **Credentials**: Connect HubSpot, OpenAI, Slack, and Gmail.\n2. **Config**: Check the `Config: Setup Variables` node to set your name and calendar link.\n3. **HubSpot Filter**: Open `HubSpot: Search Stalled Deals`. Ensure the Deal Stage matches your pipeline and that `hs_is_stalled` is set to `true`.\n4. **Safety Gate**: The `Filter: Last Activity Check` node is set to a **7-day threshold** to prevent over-contacting recently touched deals."
      },
      "typeVersion": 1
    },
    {
      "id": "5edca252-58c9-4ec6-b347-57ae132326d1",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        128,
        1264
      ],
      "parameters": {
        "color": 6,
        "width": 368,
        "height": 212,
        "content": "## Contact me\n- If you need any modification to this workflow\n- if you need some help with this workflow\n- Or if you need any workflow in n8n, Make, or Langchain / Langgraph\n\nWrite to me: [thomas@pollup.net](mailto:thomas@pollup.net)\n\n**Take a look at my others workflows [here](https://n8n.io/creators/zeerobug/)**\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "596d8540-d81c-4467-b7dd-83a48ded4fb2",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        256,
        496
      ],
      "parameters": {
        "color": 7,
        "width": 224,
        "height": 576,
        "content": "### Workflow Variables\n\nThis node holds all your user-specific settings. You must fill in these values for the AI prompt to work correctly:\n* `sender_name`\n* `company_name`\n* `calendar_link`"
      },
      "typeVersion": 1
    },
    {
      "id": "62664f69-7c83-4959-8315-050f5a9a9843",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        960,
        496
      ],
      "parameters": {
        "color": 7,
        "width": 832,
        "height": 576,
        "content": "### Filter Logic\n\n1. **HubSpot: Get Associations**: Fetches the internal IDs of contacts linked to the deal.\n2. **Code: Get Primary Contact**: Programmatically selects the first associated contact to ensure the re-engagement remains personalized and non-spammy.\n3. **HubSpot: Get Email**: Retrieves the contact's email, first name, and the `notes_last_contacted` timestamp.\n4. **Filter: Last Activity Check**: A secondary safety gate that only allows the workflow to proceed if the deal has had zero manual activity for at least **7 days**."
      },
      "typeVersion": 1
    },
    {
      "id": "358e5b3e-ef61-4ece-b4e3-11b86e2c5a43",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        512,
        496
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 576,
        "content": "### Search Stalled Deals\n\n* **HubSpot: Search Stalled Deals**: Retrieves all deals currently sitting in your targeted pipeline stage (currently set to **\"Contract Sent\"**).\n* **Metadata Extraction**: Collects deal names and the `notes_last_contacted` property, which is required for the downstream safety logic."
      },
      "typeVersion": 1
    },
    {
      "id": "bc79839d-18a8-47c8-b6f1-98606b4f52d7",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1824,
        496
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 736,
        "content": "### Generate AI Email\n\n* **AI: Generate Email**: The agent analyzes the contact details to write a professional, warm check-in.\n* **Parser**: Structures the LLM response into a Subject line and Email Body.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "99919b62-010e-44ef-8747-7366f580591c",
      "name": "HubSpot: Log Email",
      "type": "n8n-nodes-base.hubspot",
      "position": [
        2912,
        864
      ],
      "parameters": {
        "type": "email",
        "metadata": {
          "subject": "={{ $('AI: Generate Email').item.json.output.subject }}"
        },
        "resource": "engagement",
        "authentication": "oAuth2",
        "additionalFields": {
          "associations": {
            "dealIds": "={{ $('Code: Get Primary Contact').item.json.dealId }}",
            "contactIds": "={{ $('Code: Get Primary Contact').item.json.contactId }}"
          }
        }
      },
      "typeVersion": 2
    },
    {
      "id": "bf819058-a0a4-4ec3-a474-4c2161b75d4e",
      "name": "Sticky Note Actions",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2416,
        496
      ],
      "parameters": {
        "color": 7,
        "width": 800,
        "height": 578,
        "content": "### Notify & Log\n\n* **Slack**: Alerts team.\n* **Gmail**: Sends HTML email.\n* **HubSpot: Log Email**: Logs to **Contact & Deal** timeline (Updates 'Last Contacted')."
      },
      "typeVersion": 1
    },
    {
      "id": "e074db39-9704-479d-af49-dd964bd7328a",
      "name": "Code: Get Primary Contact",
      "type": "n8n-nodes-base.code",
      "position": [
        1216,
        864
      ],
      "parameters": {
        "jsCode": "// Get all deal items from the previous node.\nconst allDeals = $input.all();\n\n// This list will hold our final, flat list of all contacts.\nconst allContactsList = [];\n\n// Loop through each deal that was found.\nfor (const dealItem of allDeals) {\n  \n  // Get the list of associated contact IDs from this deal.\n  const contactIds = dealItem.json.associations.associatedVids;\n  \n  // Get the deal's ID and name.\n  const originalDealId = dealItem.json.dealId;\n  const originalDealName = dealItem.json.properties.dealname.value;\n  const dealLastContacted = dealItem.json.properties.notes_last_contacted ? dealItem.json.properties.notes_last_contacted.value : null;\n\n  // Only proceed if the deal actually has contacts.\n  if (contactIds && contactIds.length > 0) {\n    \n    // Limit to the first contact to avoid spamming multiple people.\n    const primaryId = contactIds[0];\n      \n    allContactsList.push({\n      json: {\n        contactId: primaryId,\n        dealId: originalDealId,\n        dealName: originalDealName,\n        dealLastContacted: dealLastContacted\n      }\n    });\n  }\n}\n\n// Return the final list.\nreturn allContactsList;"
      },
      "typeVersion": 2
    },
    {
      "id": "3b23d351-e1a7-4d12-9b12-569bc3d229e5",
      "name": "Filter: Last Activity Check",
      "type": "n8n-nodes-base.if",
      "position": [
        1664,
        864
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cf2fa97a-9d46-4f8c-ae0b-85bdeea1857b",
              "operator": {
                "type": "number",
                "operation": "lt"
              },
              "leftValue": "={{ $('Code: Get Primary Contact').item.json.dealLastContacted.toNumber() }}",
              "rightValue": "={{ $now.minus(7, 'days').toMillis() }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "098901b6-b513-4229-872d-3c39072471f0",
      "name": "HubSpot: Search Stalled Deals",
      "type": "n8n-nodes-base.hubspot",
      "position": [
        672,
        864
      ],
      "parameters": {
        "resource": "deal",
        "operation": "search",
        "returnAll": true,
        "authentication": "oAuth2",
        "filterGroupsUi": {
          "filterGroupsValues": [
            {
              "filtersUi": {
                "filterValues": [
                  {
                    "value": "contractsent",
                    "propertyName": "dealstage"
                  },
                  {
                    "value": "true",
                    "propertyName": "hs_is_stalled"
                  }
                ]
              }
            }
          ]
        },
        "additionalFields": {
          "properties": [
            "dealname",
            "hs_v2_date_entered_current_stage",
            "notes_last_contacted"
          ]
        }
      },
      "typeVersion": 2.2
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Config: Setup Variables",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail: Send Email": {
      "main": [
        [
          {
            "node": "HubSpot: Log Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Model: GPT-5-mini": {
      "ai_languageModel": [
        [
          {
            "node": "AI: Generate Email",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Slack: Notify CSM": {
      "main": [
        [
          {
            "node": "Gmail: Send Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI: Generate Email": {
      "main": [
        [
          {
            "node": "Slack: Notify CSM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HubSpot: Get Email": {
      "main": [
        [
          {
            "node": "Filter: Last Activity Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config: Setup Variables": {
      "main": [
        [
          {
            "node": "HubSpot: Search Stalled Deals",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parser: Email Structure": {
      "ai_outputParser": [
        [
          {
            "node": "AI: Generate Email",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Code: Get Primary Contact": {
      "main": [
        [
          {
            "node": "HubSpot: Get Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HubSpot: Get Associations": {
      "main": [
        [
          {
            "node": "Code: Get Primary Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter: Last Activity Check": {
      "main": [
        [
          {
            "node": "AI: Generate Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HubSpot: Search Stalled Deals": {
      "main": [
        [
          {
            "node": "HubSpot: Get Associations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}