{
  "id": "t7e4sQzh96e0xaq7",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "contact enrichment",
  "tags": [
    {
      "id": "0zTM91F1nQEHLKzN",
      "name": "template_upload",
      "createdAt": "2025-07-17T15:01:40.334Z",
      "updatedAt": "2025-07-17T15:01:40.334Z"
    }
  ],
  "nodes": [
    {
      "id": "dbf704c3-2b0e-4158-b5d8-ca1d138d141a",
      "name": "When Executed by Another Workflow",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "position": [
        -336,
        752
      ],
      "parameters": {
        "workflowInputs": {
          "values": [
            {
              "name": "name"
            },
            {
              "name": "email"
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "747be8f0-cf2b-412d-b7a9-93051ff0a11b",
      "name": "Enrich with Apollo",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        128,
        752
      ],
      "parameters": {
        "url": "https://api.apollo.io/api/v1/people/match",
        "method": "POST",
        "options": {},
        "sendQuery": true,
        "sendHeaders": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "reveal_personal_emails",
              "value": "false"
            },
            {
              "name": "reveal_phone_number",
              "value": "false"
            },
            {
              "name": "email",
              "value": "={{ $json.email }}"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Cache-Control",
              "value": "no-cache"
            },
            {
              "name": "accept",
              "value": "application/json"
            },
            {
              "name": "x-api-key",
              "value": "YOUR-API-KEY"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "3630169c-2677-4124-a19b-f9d1a2bfdcce",
      "name": "Found?",
      "type": "n8n-nodes-base.if",
      "position": [
        352,
        752
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "8037609c-e921-4ef9-9d34-375a6bd471ad",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.person && ($json.person.name || $json.person.email) }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "00a26c55-aa97-4255-b002-cb77bc1a8780",
      "name": "Enrich in HubSpot1",
      "type": "n8n-nodes-base.hubspot",
      "position": [
        3168,
        752
      ],
      "parameters": {
        "email": "={{ $json.output.email }}",
        "options": {},
        "authentication": "oAuth2",
        "additionalFields": {
          "city": "={{ $json.output.city }}",
          "country": "={{ $json.output.country_region }}",
          "jobTitle": "={{ $json.output.job_title }}",
          "customPropertiesUi": {
            "customPropertiesValues": [
              {
                "value": "={{ $json.output.linkedin_url }}",
                "property": "linkedin_account"
              },
              {
                "value": "={{ $json.output.experience_summary }}",
                "property": "experience_summary"
              },
              {
                "value": "={{ $json.output.education_summary }}",
                "property": "education_summary"
              },
              {
                "value": "={{ Math.floor(Date.now() / 86400000) * 86400000 }}",
                "property": "last_enrichment_date"
              },
              {
                "value": "={{ $json.output.recent_linkedin_activity }}",
                "property": "recent_linkedin_posts"
              }
            ]
          }
        }
      },
      "credentials": {
        "hubspotOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 2.1
    },
    {
      "id": "01dd3096-dcb3-4e0d-97ac-581a7d08b55f",
      "name": "Auto-fixing Output Parser2",
      "type": "@n8n/n8n-nodes-langchain.outputParserAutofixing",
      "position": [
        2672,
        912
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "b819ac28-783c-49d5-8125-fb824fd30661",
      "name": "Structured Output Parser2",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        2832,
        1040
      ],
      "parameters": {
        "jsonSchemaExample": "{\n  \"email\": \"user@example.com\",\n  \"linkedin_url\": \"http://www.linkedin.com/in/testuser1\",\n  \"job_title\": \"CEO & Co-Founder\",\n  \"city\": \"Denver\",\n  \"state\": \"Colorado\",\n  \"country_region\": \"United States\",\n  \"experience_summary\": \"User experience content\",\n  \"education_summary\": \"Education Level content\",\n  \"recent_linkedin_activity\": \"\u2022 Posted about winning 82 % of competitive deals and key sales mantras (6 hours ago)\\n\u2022 Shared guidance on setting realistic expectations for a new VP Sales\u2019 impact (1 day ago)\"\n}"
      },
      "typeVersion": 1.2
    },
    {
      "id": "06391ddf-2071-4b80-86ad-85c922ae33c8",
      "name": "OpenAI Chat Model5",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        2672,
        1040
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "3aa928c0-6f12-4f5c-9acb-018b0b87667e",
      "name": "Enrichment summary agent",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        2528,
        752
      ],
      "parameters": {
        "text": "=Here\u2019s the person data to enrich for CRM: \n{{ JSON.stringify($json, null, 2) }}\n\nHere's the recent activity to summarize for the CRM:\n{{ $json.recent_activity }}",
        "messages": {
          "messageValues": [
            {
              "message": "=# Overview\nYou are \u201cHubSpot-Enrichment-Agent\u201d, a data-cleaning assistant that converts messy person JSON enrichment data into concise, human-friendly fields ready to upsert into HubSpot. \n\n## OBJECTIVE\nReturn **one** JSON object with exactly these keys:\n\n{\n  \"email\": string|null,\n  \"linkedin_url\": string|null,\n  \"job_title\": string|null, // title at the company that matches the email domain\n  \"city\": string|null,\n  \"state\": string|null,\n  \"country_region\": string|null,\n  \"experience_summary\": string|null, // 3-4 recent roles, bullet list, newest \u2192 oldest\n  \"education_summary\": string|null, // highest degree(s) & institution(s), 1-2 lines\n  \"recent_linkedin_activity\": string|null\n}\n\n## RULES & LOGIC\n\n1. **Email, LinkedIn URL, City, Country/Region**  \n   *Pass through directly if present; else `null`.*\n\n2. **Job Title (relevant)**  \n   - Use the employment record whose `organization_name` (or website) shares the **same email domain** as `email`.  \n   - If no domain match, pick the most recent item where `current == true`.  \n   - Strip membership-only titles (\u201cMember\u201d, \u201cAngel Investor\u201d) unless no better option exists.\n\n3. **Experience Summary**  \n   - Sort `employment_history` by `start_date` DESC.  \n   - Select the first **3\u20134** roles that are either `current == true` _or_ have an `end_date` within the last 10 years.  \n   - Format each as:  \n     `\u2022 <Title>, <Org> \u2014 <YYYY-start> \u2192 <\"Present\" | YYYY-end>`  \n   - Join with line breaks.  Return `null` if list is empty.\n\n4. **Education Summary**  \n   - Look for the highest\u2010level degree (Doctorate > MBA/Master\u2019s > Bachelor\u2019s > Associate > Other).  \n   - Summarise as:  \n     `<Degree>, <Institution> (<YYYY-end>)`.  \n   - If multiple relevant degrees, include up to two lines.  \n   - If no education data, return `null`.\n\n5. **Recent LinkedIn Activity**  \n   - If a `recent_activity` or similar field exists, pull the latest **1-3** public posts or updates.  \n   - Summarise in plain text, e.g.,  \n     `\u201cCommented on procurement AI article\u201d (3 days ago)`  \n   - If no activity data, set to `null`. Do NOT make up activity.\n\n6. **Null handling**  \n   - Any missing or empty field **must** be literal `null` (not empty string).\n\n7. **Output style**  \n   - Pure JSON, no markdown.  \n   - No additional keys, comments, or nested objects.\n\n## Grammar and Punctuation\n  - ensure that your output is cleanly formatted for readiness to import into CRM, including removing uneceesary spaces or other symbols and capitalizing names. (e.g., \"sales director\" should become \"Sales Director\")\n\nReturn only that final JSON. Example output:\n{\n  \"email\": \"dustin.c@gatekeeperhq.com\",\n  \"linkedin_url\": \"https://www.linkedin.com/in/dustinclinard\",\n  \"job_title\": \"Chief Revenue Officer (CRO)\",\n  \"city\": \"Concord\",\n  \"country_region\": \"United States\",\n  \"experience_summary\": \"\u2022 Chief Revenue Officer, Gatekeeper \u2014 2024 \u2192 Present\\n\u2022 Founding Member, Revenue Collective \u2014 2019 \u2192 Present\\n\u2022 Member, Partnership Leaders \u2014 2020 \u2192 Present\\n\u2022 Founder & Growth Consultant, Sales Growth Musings \u2014 2016 \u2192 Present\",\n  \"education_summary\": null,\n  \"recent_linkedin_activity\": null\n}"
            }
          ]
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.6
    },
    {
      "id": "f3d84acb-fe43-4d14-ab74-97a0e5a336a7",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        48,
        592
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 400,
        "content": "## Enrich with Apollo"
      },
      "typeVersion": 1
    },
    {
      "id": "8f9f9c6c-e9b9-4502-97a2-b4bf3c5a396c",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2448,
        608
      ],
      "parameters": {
        "color": 4,
        "width": 540,
        "height": 620,
        "content": "## Summarize enrichment data\nEnsure it's clean and matches our CRM needs"
      },
      "typeVersion": 1
    },
    {
      "id": "9cc13aca-f660-470f-addb-f17f51f725e7",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3104,
        608
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 400,
        "content": "## Enrich HubSpot"
      },
      "typeVersion": 1
    },
    {
      "id": "6c4e7437-9a58-4aad-956c-268bb23920e8",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        2048,
        752
      ],
      "parameters": {
        "mode": "combine",
        "options": {
          "includeUnpaired": true
        },
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.1
    },
    {
      "id": "5f3436aa-94d0-46f9-83fd-e60726b8821c",
      "name": "OpenAI Chat Model6",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        2512,
        912
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "o3",
          "cachedResultName": "o3"
        },
        "options": {
          "responseFormat": "json_object"
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "624d158e-08bc-42df-add7-9462b39cb8de",
      "name": "Get recent posts",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        832,
        912
      ],
      "parameters": {
        "url": "https://fresh-linkedin-profile-data.p.rapidapi.com/get-profile-posts",
        "options": {},
        "sendQuery": true,
        "sendHeaders": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "linkedin_url",
              "value": "={{ $json.person.linkedin_url }}"
            },
            {
              "name": "type",
              "value": "posts"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "x-rapidapi-host",
              "value": "fresh-linkedin-profile-data.p.rapidapi.com"
            },
            {
              "name": "x-rapidapi-key",
              "value": "<YOUR API KEY>"
            }
          ]
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2,
      "alwaysOutputData": true
    },
    {
      "id": "45dfddb1-ad00-419b-8309-e91de878989d",
      "name": "Split Out",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        1040,
        912
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "data"
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "6b392b08-3dff-4201-ba66-a979ac7d301a",
      "name": "Filter out reshares and old posts",
      "type": "n8n-nodes-base.filter",
      "position": [
        1264,
        912
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "44e79a7e-7ec9-4724-a53b-3e647b6a629a",
              "operator": {
                "type": "boolean",
                "operation": "false",
                "singleValue": true
              },
              "leftValue": "={{ $json.reshared }}",
              "rightValue": ""
            },
            {
              "id": "8da29df1-cbfb-42cd-a85f-62b2b4647802",
              "operator": {
                "type": "dateTime",
                "operation": "afterOrEquals"
              },
              "leftValue": "={{ $json.posted }}",
              "rightValue": "={{$now.minus({ days: 30 }).toISO()}}"
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.2,
      "alwaysOutputData": true
    },
    {
      "id": "466bbf0f-54e8-4c2c-9637-f70925329b72",
      "name": "Limit to 3",
      "type": "n8n-nodes-base.limit",
      "position": [
        1680,
        912
      ],
      "parameters": {
        "maxItems": 3
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "fa0bd77e-310e-4a64-83c9-c4f3b0fddb7a",
      "name": "Sort by post date",
      "type": "n8n-nodes-base.sort",
      "position": [
        1488,
        912
      ],
      "parameters": {
        "options": {},
        "sortFieldsUi": {
          "sortField": [
            {
              "order": "descending",
              "fieldName": "posted"
            }
          ]
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "b45d1bb7-5a3f-43f6-ae9c-7a912e3b1114",
      "name": "Aggregate",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        1904,
        912
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "c0e75ad6-08db-4bda-9c69-4067adcc4951",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        624,
        784
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3,
      "alwaysOutputData": false
    },
    {
      "id": "69bd14dd-f6d8-4220-ba6b-dd17d2d1ea8b",
      "name": "Extract fields",
      "type": "n8n-nodes-base.set",
      "position": [
        2272,
        752
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "19c926c1-da4e-44e2-abce-6d7d172cb0bb",
              "name": "person_data",
              "type": "string",
              "value": "={{ $json.person }}"
            },
            {
              "id": "62615717-7bc7-44c4-9304-7177dfcf3ee8",
              "name": "recent_activity",
              "type": "string",
              "value": "={{ $json.data }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "5b9ce241-42a6-468a-be87-2462248836af",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        768,
        848
      ],
      "parameters": {
        "color": 5,
        "width": 1260,
        "height": 220,
        "content": "## Grab Recent LinkedIn Posts"
      },
      "typeVersion": 1
    },
    {
      "id": "f76caacf-bf40-4aa7-b48c-60ebf9c772a8",
      "name": "clean",
      "type": "n8n-nodes-base.code",
      "position": [
        -128,
        752
      ],
      "parameters": {
        "jsCode": "/**********************************************************************\n *  Deduplicate contacts by email & drop empties\n *  Input:  items[] (each item = { json: { \u2026 } })\n *  Output: one item per unique, non-empty email\n **********************************************************************/\nconst normEmail = e => (typeof e === 'string' ? e.trim().toLowerCase() : '');\n\nconst seen  = new Set();\nconst clean = [];\n\nfor (const itm of items) {\n  // Handle both flat and nested structures\n  const data   = itm.json.output ?? itm.json;   // supports {output:{\u2026}} too\n  const email  = normEmail(data.email);\n\n  if (!email || seen.has(email)) continue;      // skip blanks & dupes\n\n  seen.add(email);\n  clean.push({\n    json: {\n      name:  data.name  ?? null,\n      email // already normalised\n    }\n  });\n}\n\nreturn clean;"
      },
      "typeVersion": 2
    },
    {
      "id": "e95c04cb-b9e3-433d-b510-77e1b9b7abf2",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1632,
        848
      ],
      "parameters": {
        "height": 208,
        "content": "Decide how many posts you want to analyse"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "ad6478e5-597d-450d-ab64-d506159731f2",
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Extract fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "clean": {
      "main": [
        [
          {
            "node": "Enrich with Apollo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Found?": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          },
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Aggregate": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out": {
      "main": [
        [
          {
            "node": "Filter out reshares and old posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Limit to 3": {
      "main": [
        [
          {
            "node": "Aggregate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract fields": {
      "main": [
        [
          {
            "node": "Enrichment summary agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ],
        [
          {
            "node": "Get recent posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get recent posts": {
      "main": [
        [
          {
            "node": "Split Out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort by post date": {
      "main": [
        [
          {
            "node": "Limit to 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enrich in HubSpot1": {
      "main": [
        []
      ]
    },
    "Enrich with Apollo": {
      "main": [
        [
          {
            "node": "Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model5": {
      "ai_languageModel": [
        [
          {
            "node": "Auto-fixing Output Parser2",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model6": {
      "ai_languageModel": [
        [
          {
            "node": "Enrichment summary agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Enrichment summary agent": {
      "main": [
        [
          {
            "node": "Enrich in HubSpot1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser2": {
      "ai_outputParser": [
        [
          {
            "node": "Auto-fixing Output Parser2",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Auto-fixing Output Parser2": {
      "ai_outputParser": [
        [
          {
            "node": "Enrichment summary agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Filter out reshares and old posts": {
      "main": [
        [
          {
            "node": "Sort by post date",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Executed by Another Workflow": {
      "main": [
        [
          {
            "node": "clean",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}