{
  "id": "RWghkZDBJi9mm0Bq",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "[Template] Building Relationships (param)",
  "tags": [],
  "nodes": [
    {
      "id": "55136a9d-4d1e-455d-8b26-f449c28da2bb",
      "name": "trigger: form submission",
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "position": [
        -336,
        0
      ],
      "parameters": {
        "event": "rowAdded",
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "sheetName": "={{ $env.SHEETS_TAB || 'crm' }}",
        "documentId": "={{ $env.SHEETS_ID }}"
      },
      "typeVersion": 1
    },
    {
      "id": "9e262787-c907-4bf7-b8e4-2867ffe2946f",
      "name": "get screenshot",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -128,
        0
      ],
      "parameters": {
        "fileId": "={{ (function () { const col = $env.COL_DRIVE_URL || 'Upload a screenshot or image of the person'; const s = $json[col] || ''; const m = s.match(/(?:\\/file\\/d\\/|\\/d\\/|open\\?id=|uc\\?id=|thumbnail\\?id=|id=)([-\\w]{10,})/); return m ? m[1] : ''; })() }}",
        "options": {
          "binaryProperty": "data"
        },
        "operation": "download"
      },
      "typeVersion": 3
    },
    {
      "id": "6219058a-3a24-48d7-b2a9-cf861a6ae74b",
      "name": "Analyze image",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        112,
        0
      ],
      "parameters": {
        "text": "You extract structured data from images of profiles, email signatures, business cards, event badges, resumes, or screenshots.\n\nRules:\n- OUTPUT STRICT JSON ONLY. No markdown, no prose, no comments.\n- Return exactly these 5 keys; if a field isn\u2019t clearly present, use null.\n- Prefer the primary/most prominent person if multiple appear.\n- Normalize whitespace and title-case personal names; keep original casing for company/university.\n- Keep description to 1\u20132 sentences summarizing what\u2019s visible (role, focus, notable lines). Do not invent info.\n\nReturn exactly:\n{\n  \"name\": string|null,\n  \"job_title\": string|null,\n  \"company\": string|null,\n  \"university\": string|null,\n  \"description\": string|null\n}",
        "modelId": "={{ $env.OPENAI_VISION_MODEL || 'chatgpt-4o-latest' }}",
        "options": {},
        "resource": "image",
        "inputType": "binary",
        "operation": "analyze",
        "binaryPropertyName": "data"
      },
      "typeVersion": 1.8
    },
    {
      "id": "1fc377bc-18a7-4fe1-897f-4a0a8fcb9bf1",
      "name": "structure info",
      "type": "n8n-nodes-base.code",
      "position": [
        352,
        0
      ],
      "parameters": {
        "jsCode": "// n8n Code node (JavaScript). Mode: \"Run Once for Each Item\"\nfunction titleCaseName(s) {\n  if (!s || typeof s !== 'string') return s;\n  return s\n    .trim()\n    .split(/\\s+/)\n    .map(w => w[0] ? (w[0].toUpperCase() + w.slice(1).toLowerCase()) : w)\n    .join(' ');\n}\n\nfunction clean(v) {\n  if (v === undefined || v === null) return null;\n  const s = String(v).trim();\n  return s === '' ? null : s;\n}\n\nconst out = [];\n\nfor (const item of items) {\n  // 1) Get raw model output (handles various OpenAI node shapes)\n  let raw =\n    item.json?.text ??                           // common \"Simplify Output\" field\n    item.json?.content ??                        // some OpenAI nodes\n    item.json?.response ??                       // alt\n    item.json?.choices?.[0]?.message?.content ?? // raw OpenAI\n    item.json;                                   // last resort\n\n  // 2) Parse to object (strip code fences, smart quotes, double-parse fallback)\n  let obj = raw;\n  if (typeof raw === 'string') {\n    let s = raw.trim().replace(/^```(?:json)?\\s*|\\s*```$/g, '');\n    s = s.replace(/[\\u201C\\u201D]/g, '\"').replace(/[\\u2018\\u2019]/g, \"'\");\n    try { obj = JSON.parse(s); }\n    catch {\n      obj = JSON.parse(JSON.parse(s)); // handles quoted-JSON-in-a-string\n    }\n  }\n\n  // 3) Accept common alias keys from the model, then normalize\n  const aliases = {\n    name: ['name', 'full_name', 'person', 'candidate'],\n    job_title: ['job_title', 'title', 'role', 'position', 'headline'],\n    company: ['company', 'org', 'employer', 'organization'],\n    location: ['location', 'city', 'based_in', 'place'],\n    university: ['university', 'school', 'alma_mater', 'education'],\n    description: ['description', 'summary', 'about', 'bio']\n  };\n\n  const pick = (o, keys) => {\n    for (const k of keys) if (o && o[k] != null) return o[k];\n    return null;\n  };\n\n  const result = {\n    name:        titleCaseName(clean(pick(obj, aliases.name))),\n    job_title:   clean(pick(obj, aliases.job_title)),\n    company:     clean(pick(obj, aliases.company)),\n    location:    clean(pick(obj, aliases.location)),\n    university:  clean(pick(obj, aliases.university)),\n    description: clean(pick(obj, aliases.description))\n  };\n\n  // 4) Optional: trim description length\n  if (result.description && result.description.length > 300) {\n    result.description = result.description.slice(0, 297) + '...';\n  }\n\n  out.push({ json: result });\n}\n\nreturn out;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "5346ea2f-b3e0-4056-9026-30dc3ad5320f",
      "name": "update crm",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        560,
        0
      ],
      "parameters": {
        "columns": {
          "value": {
            "company": "={{ $json.company }}",
            "location": "={{ $json.location }}",
            "full name": "={{ $json.name }}",
            "job title": "={{ $json.job_title }}",
            "university": "={{ $json.university }}",
            "description": "={{ $json.description }}",
            "Upload a screenshot or image of the person": "={{ $('get screenshot').item.json[$env.COL_DRIVE_URL || 'Upload a screenshot or image of the person'] }}"
          },
          "schema": [
            {
              "id": "Timestamp",
              "type": "string",
              "display": true,
              "removed": true,
              "displayName": "Timestamp",
              "canBeUsedToMatch": true
            },
            {
              "id": "Upload a screenshot or image of the person",
              "type": "string",
              "display": true,
              "displayName": "Upload a screenshot or image of the person",
              "canBeUsedToMatch": true
            },
            {
              "id": "Quick notes about the person",
              "type": "string",
              "display": true,
              "removed": true,
              "displayName": "Quick notes about the person",
              "canBeUsedToMatch": true
            },
            {
              "id": "full name",
              "type": "string",
              "display": true,
              "displayName": "full name",
              "canBeUsedToMatch": true
            },
            {
              "id": "company",
              "type": "string",
              "display": true,
              "displayName": "company",
              "canBeUsedToMatch": true
            },
            {
              "id": "job title",
              "type": "string",
              "display": true,
              "displayName": "job title",
              "canBeUsedToMatch": true
            },
            {
              "id": "location",
              "type": "string",
              "display": true,
              "displayName": "location",
              "canBeUsedToMatch": true
            },
            {
              "id": "description",
              "type": "string",
              "display": true,
              "displayName": "description",
              "canBeUsedToMatch": true
            },
            {
              "id": "university",
              "type": "string",
              "display": true,
              "displayName": "university",
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Upload a screenshot or image of the person"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": "={{ $env.SHEETS_TAB || 'crm' }}",
        "documentId": "={{ $env.SHEETS_ID }}"
      },
      "typeVersion": 4.7
    },
    {
      "id": "b410e112-ca36-4d17-96c8-777fbea7d3c0",
      "name": "Message a model",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        768,
        0
      ],
      "parameters": {
        "modelId": "={{ $env.OPENAI_TEXT_MODEL || 'gpt-4o-mini' }}",
        "options": {
          "temperature": 0.2
        },
        "messages": {
          "values": [
            {
              "content": "=NOTES (must use): {{ $('trigger: form submission').item.json[$env.COL_NOTES || 'Quick notes about the person'] }}\n\nFIELDS (use only if present; never guess):\nname: {{ $json.name || $json[\"full name\"] }}\ntitle: {{ $json.job_title || $json[\"job title\"] }}\ncompany: {{ $json.company }}\nlocation: {{ $json.location }}\nuniversity: {{ $json.university }}\ndescription: {{ $json.description }}\n\nTASK\nWrite ONE LinkedIn DM that:\n- Cites exactly ONE concrete detail from NOTES.\n- Optionally weaves in title/company/location if present.\n- Makes ONE light ask.\n- Ends with a question.\nPlain text only, \u2264450 chars, no added facts beyond NOTES/FIELDS."
            },
            {
              "role": "system",
              "content": "You write short LinkedIn follow-ups grounded in structured fields.\n\nGROUNDING & SAFETY\n- ALWAYS anchor on NOTES. If a detail is not in NOTES or the provided fields, OMIT it.\n- NEVER invent, infer, or generalize.\n- If a field is empty or unclear, ignore it\u2014don\u2019t guess.\n- Keep claims strictly to what\u2019s provided.\n\nSTYLE\n- Plain text only (no emojis, bullets, or brackets). \u2264 450 characters.\n- Friendly, crisp, professional. Reference ONE concrete detail from NOTES.\n- Suggest ONE light next step (share resource / quick chat / intro).\n- End with ONE question.\n\nCONTENT RULES\n- If name is available, greet by first name only.\n- Use title/company/location only if present and useful.\n- Do not mention university unless present and relevant."
            }
          ]
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "2c060fb9-5a0d-45a4-8b72-623c2d64be88",
      "name": "update crm with message",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1120,
        0
      ],
      "parameters": {
        "columns": {
          "value": {
            "follow-up message": "={{ $json.message?.content || $json.text || $json.content }}",
            "Upload a screenshot or image of the person": "={{ $('get screenshot').item.json[$env.COL_DRIVE_URL || 'Upload a screenshot or image of the person'] }}"
          },
          "schema": [
            {
              "id": "Timestamp",
              "type": "string",
              "display": true,
              "removed": true,
              "displayName": "Timestamp",
              "canBeUsedToMatch": true
            },
            {
              "id": "Upload a screenshot or image of the person",
              "type": "string",
              "display": true,
              "displayName": "Upload a screenshot or image of the person",
              "canBeUsedToMatch": true
            },
            {
              "id": "Quick notes about the person",
              "type": "string",
              "display": true,
              "removed": true,
              "displayName": "Quick notes about the person",
              "canBeUsedToMatch": true
            },
            {
              "id": "full name",
              "type": "string",
              "display": true,
              "removed": true,
              "displayName": "full name",
              "canBeUsedToMatch": true
            },
            {
              "id": "company",
              "type": "string",
              "display": true,
              "removed": true,
              "displayName": "company",
              "canBeUsedToMatch": true
            },
            {
              "id": "job title",
              "type": "string",
              "display": true,
              "removed": true,
              "displayName": "job title",
              "displayNameSrc": "job title",
              "canBeUsedToMatch": true
            },
            {
              "id": "location",
              "type": "string",
              "display": true,
              "removed": true,
              "displayName": "location",
              "canBeUsedToMatch": true
            },
            {
              "id": "description",
              "type": "string",
              "display": true,
              "removed": true,
              "displayName": "description",
              "canBeUsedToMatch": true
            },
            {
              "id": "university",
              "type": "string",
              "display": true,
              "removed": true,
              "displayName": "university",
              "canBeUsedToMatch": true
            },
            {
              "id": "follow-up message",
              "type": "string",
              "display": true,
              "displayName": "follow-up message",
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Upload a screenshot or image of the person"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": "={{ $env.SHEETS_TAB || 'crm' }}",
        "documentId": "={{ $env.SHEETS_ID }}"
      },
      "typeVersion": 4.7
    },
    {
      "id": "15d58a4e-6c00-4e4a-afda-09f82722eb30",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -368,
        -208
      ],
      "parameters": {
        "width": 192,
        "content": "Trigger:\nWhen a 2-field only Google Form is submitted with a LinkedIn header screenshot + quick personal notes. "
      },
      "typeVersion": 1
    },
    {
      "id": "9f91086d-210b-4fc8-89e4-415375dafb37",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -128,
        -208
      ],
      "parameters": {
        "width": 150,
        "content": "This step gets the new screenshot to parse information"
      },
      "typeVersion": 1
    },
    {
      "id": "c24d446a-3d2d-4639-99f8-05ac599900ed",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        80,
        -208
      ],
      "parameters": {
        "width": 150,
        "content": "Open AI's vision model analyses the screenshot to extract info"
      },
      "typeVersion": 1
    },
    {
      "id": "5102f336-add5-4992-a9fa-2542021f7558",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        304,
        -208
      ],
      "parameters": {
        "width": 150,
        "content": "Extracted information is structured "
      },
      "typeVersion": 1
    },
    {
      "id": "d54eb503-b086-431c-b483-fa9c67701c88",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        528,
        -208
      ],
      "parameters": {
        "width": 150,
        "content": "Google sheet with form submission info is updated with added fields"
      },
      "typeVersion": 1
    },
    {
      "id": "6a945f3a-6926-4f6d-9d3d-d9415af4355e",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        784,
        -208
      ],
      "parameters": {
        "width": 150,
        "content": "With all this info AND your original notes, Open AI generates a message"
      },
      "typeVersion": 1
    },
    {
      "id": "dbd68a25-b501-4f06-a1ea-dc746fced6f3",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1056,
        -208
      ],
      "parameters": {
        "width": 150,
        "content": "crm updated with message"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "callerPolicy": "workflowsFromSameOwner",
    "executionOrder": "v1"
  },
  "versionId": "5bc78610-e614-4340-8703-a7e7bcf2d274",
  "connections": {
    "update crm": {
      "main": [
        [
          {
            "node": "Message a model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze image": {
      "main": [
        [
          {
            "node": "structure info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "get screenshot": {
      "main": [
        [
          {
            "node": "Analyze image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "structure info": {
      "main": [
        [
          {
            "node": "update crm",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Message a model": {
      "main": [
        [
          {
            "node": "update crm with message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "trigger: form submission": {
      "main": [
        [
          {
            "node": "get screenshot",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}