{
  "nodes": [
    {
      "id": "2bdfeaf7-f4db-446d-84bc-68e914138e28",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -112,
        192
      ],
      "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 Markdown\"\n    }\n  },\n  \"required\": [\"subject\", \"body\"]\n}"
      },
      "typeVersion": 1.2
    },
    {
      "id": "46c35571-3823-4cb9-8e07-df85c8fd6a2f",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2368,
        240
      ],
      "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": "3d3a2b5f-c5cd-4fdc-bc1d-55956b6b7809",
      "name": "If Deal Is Won",
      "type": "n8n-nodes-base.if",
      "position": [
        -1824,
        -32
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "b70f4699-008f-4924-8e69-af4fa69422a5",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $(\"Trigger: Deal Is 'Closed Won'\").item.json.body[0].propertyValue }}",
              "rightValue": "contact.creation"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "3b7f2eb6-c57f-454e-8bca-dece1d7ac508",
      "name": "Configure Template Variables",
      "type": "n8n-nodes-base.set",
      "position": [
        -2048,
        -32
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "11a8b9e9-a7ed-454a-9aef-a9137c0e17ea",
              "name": "company_name",
              "type": "string",
              "value": "Your Company Name"
            },
            {
              "id": "f2dcfe2e-3145-4a30-9731-0a8d02c7aa9a",
              "name": "sender_name",
              "type": "string",
              "value": "Your Sender Name"
            },
            {
              "id": "18b5c0bd-4e75-4b98-92fc-5fca90a8b680",
              "name": "sender_email",
              "type": "string",
              "value": "user@example.com"
            }
          ]
        }
      },
      "notesInFlow": true,
      "typeVersion": 3.4
    },
    {
      "id": "e26d9e1d-de10-445d-bbc7-aa20753c3bbb",
      "name": "If Role is 'Champion'",
      "type": "n8n-nodes-base.if",
      "position": [
        -480,
        -32
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "90d9c369-80df-4938-adac-af3b452f97ca",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.properties.hs_buying_role.value }}",
              "rightValue": "CHAMPION"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "e22c6e48-5899-4f13-b857-65d499e296ca",
      "name": "HubSpot: Get Deal Details",
      "type": "n8n-nodes-base.hubspot",
      "position": [
        -1152,
        -32
      ],
      "parameters": {
        "dealId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $(\"Trigger: Deal Is 'Closed Won'\").item.json.body[0].objectId }}"
        },
        "filters": {},
        "resource": "deal",
        "operation": "get",
        "authentication": "oAuth2"
      },
      "typeVersion": 2.2
    },
    {
      "id": "8f569481-1ee0-4441-8269-a0630cb116d3",
      "name": "Split Contact IDs",
      "type": "n8n-nodes-base.code",
      "position": [
        -928,
        -32
      ],
      "parameters": {
        "jsCode": "// Get the list of contact IDs\nconst vids = $('HubSpot: Get Deal Details').first().json.associations.associatedVids;\n\n// Make sure it's always a list (even if there's only one)\nconst vidsList = [].concat(vids);\n\n// Return each ID as a separate item for the next node to process\nreturn vidsList.map(id => {\n  return {\n    json: {\n      contactId: id\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "7ccef67d-88ba-4ee2-88b8-ea0888b57319",
      "name": "HubSpot: Get Contact Details",
      "type": "n8n-nodes-base.hubspot",
      "position": [
        -704,
        -32
      ],
      "parameters": {
        "contactId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.contactId }}"
        },
        "operation": "get",
        "authentication": "oAuth2",
        "additionalFields": {
          "propertiesCollection": {
            "propertiesValues": {
              "properties": [
                "hs_buying_role"
              ],
              "propertyMode": "valueOnly"
            }
          }
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "8f0f446d-2705-4e0b-a982-76a04c9010ca",
      "name": "AI: Write Welcome Email",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -256,
        -32
      ],
      "parameters": {
        "text": "=Your task is to write a warm, personalized welcome email to a new customer.\n\n## Content to Include\n- Congratulate them on their purchase.\n- Welcome them to the [Your Company Name] family.\n- Let them know that their new Customer Success Manager will be in touch shortly to schedule a kickoff call.\n- Provide a link to a \"Getting Started\" video: [Link to Your Video]\n- Provide a link to a \"Help Documentation\" page: [Link to Your Help Doc]\n\n## Rules\n- Be professional, enthusiastic, and welcoming.\n- You are writing on behalf of the sender.\n\n## Use the variables below\nSender's name:  {{ $('Configure Template Variables').item.json.sender_name }}\nSender's email: {{ $('Configure Template Variables').item.json.sender_email }}\nSender's company name: {{ $('Configure Template Variables').item.json.company_name }}\nRecipient's first name: {{ $('HubSpot: Get Contact Details').item.json.properties.firstname.value }}\nRecipient's last name: {{ $('HubSpot: Get Contact Details').item.json.properties.lastname.value }}\nRecipient's email: {{ $('HubSpot: Get Contact Details').item.json.properties.email.value }}",
        "options": {
          "systemMessage": "=# Overview\nYou are a professional Customer Success Manager.\n"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.9
    },
    {
      "id": "c3795f85-b2cf-4baf-86f5-7919234d1cad",
      "name": "AI Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -256,
        192
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "gpt-4o-mini"
        },
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "7262a3e6-dae7-4011-ab90-54897deb9f19",
      "name": "Util: Markdown to HTML",
      "type": "n8n-nodes-base.markdown",
      "position": [
        96,
        -32
      ],
      "parameters": {
        "mode": "markdownToHtml",
        "options": {},
        "markdown": "={{ $json.output.body }}"
      },
      "typeVersion": 1
    },
    {
      "id": "366e7c5d-f6df-4896-bd32-ae1fba5b4f49",
      "name": "Gmail: Send Welcome Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        320,
        -32
      ],
      "parameters": {
        "sendTo": "={{ $('HubSpot: Get Contact Details').item.json.properties.email.value }}",
        "message": "={{ $json.data }}",
        "options": {},
        "subject": "={{ $json.output.subject }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "1033acf3-11d8-4ea2-89f2-13ceda2e9f8d",
      "name": "HubSpot: Assign Contact Owner",
      "type": "n8n-nodes-base.hubspot",
      "position": [
        544,
        -32
      ],
      "parameters": {
        "email": "={{ $('HubSpot: Get Contact Details').item.json.properties.email.value }}",
        "options": {},
        "authentication": "oAuth2",
        "additionalFields": {
          "contactOwner": "={{ $('Find Least Busy CSM').item.json.csm_id }}"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "cb68f201-669e-4f65-a065-93e1e1586fd5",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2368,
        -1104
      ],
      "parameters": {
        "width": 768,
        "height": 656,
        "content": "# Automate a CSM assignment and email send from a HubSpot onboarding\n\nThis workflow triggers when a HubSpot deal is marked `Closed Won`, assigns the **least busy CSM based on a Data Table**, and sends a personalized welcome email generated by AI.\n\n## How to Set Up\n1.  **Create CSM Data Table:** You must first create an n8n Data Table with the following setup:\n    * **Name:** `csm_assignments`\n    * **Columns:** `csm_id` (String) and `deal_count` (Number)\n    * **Data:** Add one row for each CSM, putting their HubSpot Owner ID in `csm_id` and `0` in `deal_count`.\n2.  **Link Data Table Nodes:** Open the `Get CSM List` and `Increment CSM Deal Count` nodes. In the **Table** field, select the `csm_assignments` table you just created.\n3.  **Configure Variables:** Open the **`Configure Template Variables`** node and fill in your sender info (`company_name`, `sender_name`, and `sender_email`).\n4.  **Add Credentials:** Add your credentials to all other required nodes:\n    * `Trigger: Deal Is 'Closed Won'`\n    * `HubSpot: Get Deal Details`\n    * `HubSpot: Get Contact Details`\n    * `HubSpot: Assign Contact Owner`\n    * `AI Model` (This node is connected to the AI agent and holds the credentials)\n    * `Gmail: Send Welcome Email`\n5.  **Customize AI Prompt:** Open the **`AI: Write Welcome Email`** node (the node you posted) and edit the prompt to match your company's tone.\n6.  **Activate Workflow:** Save and activate the workflow."
      },
      "typeVersion": 1
    },
    {
      "id": "c1c64eb1-7108-4579-8b77-3cadb79fab4d",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2464,
        -352
      ],
      "parameters": {
        "color": 7,
        "width": 352,
        "height": 480,
        "content": "This node is the **live** trigger for the workflow.\n\n* **Action:** It watches for changes to the \"Is closed won\" property on a HubSpot deal.\n* **Heads Up:** This trigger will fire when the property changes to **True** *and* when it changes back to **False**.\n* **Filtering:** The `If Deal Is Won` node downstream is essential, as it filters for the **True** case, allowing the rest of the workflow to run only when a deal is won.\n* **Credential:** This node requires a **\"HubSpot Developer API\"** credential to function."
      },
      "typeVersion": 1
    },
    {
      "id": "d1cc6510-b720-4076-910a-b528a72b6f62",
      "name": "Trigger: Deal Is 'Closed Won'",
      "type": "n8n-nodes-base.hubspotTrigger",
      "position": [
        -2272,
        -32
      ],
      "parameters": {
        "eventsUi": {
          "eventValues": [
            {
              "name": "deal.propertyChange",
              "property": "=hs_is_closed_won"
            }
          ]
        },
        "additionalFields": {
          "maxConcurrentRequests": 5
        }
      },
      "typeVersion": 1
    },
    {
      "id": "04545ef6-cae4-4c4e-bf96-ea4ac26be3ad",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -304,
        -352
      ],
      "parameters": {
        "color": 7,
        "width": 336,
        "height": 672,
        "content": "### AI Email Writer\n* Uses AI to write a personalized Welcome Email.\n* Requires OpenAI credentials.\n* You can customize the prompt in the `AI: Write Welcome Email` node."
      },
      "typeVersion": 1
    },
    {
      "id": "7d1fd6d1-7ded-4760-8158-55f9e9f4d209",
      "name": "Get CSM List",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        -1600,
        -32
      ],
      "parameters": {
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "id",
          "value": ""
        }
      },
      "typeVersion": 1
    },
    {
      "id": "f5774385-f71b-4b5c-bf67-54d8f184af06",
      "name": "Find Least Busy CSM",
      "type": "n8n-nodes-base.code",
      "position": [
        -1376,
        -32
      ],
      "parameters": {
        "jsCode": "// Get the list of CSMs from the previous node\nconst csmList = $input.all();\n\n// Check if the table is empty\nif (csmList.length === 0) {\n  throw new Error(\"CSM assignment table is empty. Please add CSMs to the Data Table.\");\n}\n\n// Sort the list to find the CSM with the lowest deal_count\n// The input item looks like: { json: { csm_id: '123', deal_count: 0 }, row_id: 'abc' }\nconst sortedList = csmList.sort((a, b) => {\n  return a.json.deal_count - b.json.deal_count;\n});\n\n// The least busy CSM is the first one in the sorted list\nconst leastBusyCsm = sortedList[0];\n\n// Return the data needed for the next nodes\nreturn {\n  csm_id: leastBusyCsm.json.csm_id,\n  row_id_to_update: leastBusyCsm.json.id,\n  current_count: leastBusyCsm.json.deal_count\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "46936c4a-7dcf-4cc0-bce9-6f06d230ee92",
      "name": "Increment CSM Deal Count",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        768,
        -32
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "filters": {
          "conditions": [
            {
              "keyValue": "={{ $('Find Least Busy CSM').item.json.row_id_to_update }}"
            }
          ]
        },
        "options": {},
        "operation": "update",
        "dataTableId": {
          "__rl": true,
          "mode": "id",
          "value": ""
        }
      },
      "typeVersion": 1
    },
    {
      "id": "426da06b-eb9f-4a6d-8b13-a2cffd6dd6ba",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1664,
        -352
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 480,
        "content": "### Least-Busy CSM Assignment\n\nThese nodes find the CSM with the lowest `deal_count` from the `csm_assignments` Data Table, assign them the new deal, and then add 1 to their count to keep the table in sync."
      },
      "typeVersion": 1
    },
    {
      "id": "e9606b09-3cbc-4f51-a1e9-23ba1a617aa4",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        704,
        -352
      ],
      "parameters": {
        "color": 7,
        "width": 544,
        "height": 512,
        "content": "This node updates the CSM's deal count.\n\n**After you select your `csm_assignments` table:**\n1.  Go to the \"Fields to Update\" section.\n2.  Click **Add Field**.\n3.  **Field:** Select `deal_count`.\n4.  **Value:** Switch to expression mode (`f(x)`) and paste:\n    `{{ $('Find Least Busy CSM').item.json.current_count + 1 }}`"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "AI Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI: Write Welcome Email",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Get CSM List": {
      "main": [
        [
          {
            "node": "Find Least Busy CSM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Deal Is Won": {
      "main": [
        [
          {
            "node": "Get CSM List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Contact IDs": {
      "main": [
        [
          {
            "node": "HubSpot: Get Contact Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Least Busy CSM": {
      "main": [
        [
          {
            "node": "HubSpot: Get Deal Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Role is 'Champion'": {
      "main": [
        [
          {
            "node": "AI: Write Welcome Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Util: Markdown to HTML": {
      "main": [
        [
          {
            "node": "Gmail: Send Welcome Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI: Write Welcome Email": {
      "main": [
        [
          {
            "node": "Util: Markdown to HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "AI: Write Welcome Email",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Gmail: Send Welcome Email": {
      "main": [
        [
          {
            "node": "HubSpot: Assign Contact Owner",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HubSpot: Get Deal Details": {
      "main": [
        [
          {
            "node": "Split Contact IDs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure Template Variables": {
      "main": [
        [
          {
            "node": "If Deal Is Won",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HubSpot: Get Contact Details": {
      "main": [
        [
          {
            "node": "If Role is 'Champion'",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HubSpot: Assign Contact Owner": {
      "main": [
        [
          {
            "node": "Increment CSM Deal Count",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trigger: Deal Is 'Closed Won'": {
      "main": [
        [
          {
            "node": "Configure Template Variables",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}