{
  "nodes": [
    {
      "id": "09a7c560-e540-4643-ad75-d1af11dcd7ed",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        1248,
        304
      ],
      "parameters": {
        "rule": {
          "interval": [
            {}
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "9ab7d97d-4358-4e21-9e2a-d9e70eecbea6",
      "name": "Get Leads",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1456,
        304
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultName": "leads"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1IM0qnZlhYK7Pbj__xwyPgEprW-5-P3RBnen1xGZjSSk",
          "cachedResultName": "linkedin_connection_request_template"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "406ea666-cf94-46a8-a8e4-caa3ebd4a5c9",
      "name": "Filter",
      "type": "n8n-nodes-base.filter",
      "position": [
        1648,
        304
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "d7a7f360-9796-4544-be34-834b20134833",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.connection_request_status }}",
              "rightValue": "UserInvitationSent"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "e499cc36-ed44-42d1-94c5-de8117f99ecc",
      "name": "Limit Connection Request",
      "type": "n8n-nodes-base.limit",
      "position": [
        1824,
        304
      ],
      "parameters": {
        "maxItems": 10
      },
      "typeVersion": 1
    },
    {
      "id": "29311348-574e-4c90-9496-a6deca605334",
      "name": "Get Linkedin Username",
      "type": "n8n-nodes-base.code",
      "position": [
        2080,
        304
      ],
      "parameters": {
        "jsCode": "const linkedinUrl = $input.first().json.linkedin_url;\nconst cleanUrl = linkedinUrl.replace(/\\/$/, '');\nconst username = cleanUrl.split('/in/')[1]?.split('/')[0] || null;\n\nreturn [{\n  json: {\n    username: username\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "4fb137da-fe83-4ad4-a090-7d43992257f1",
      "name": "Data Arrangement",
      "type": "n8n-nodes-base.set",
      "position": [
        2352,
        304
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "85e65f5e-2c0f-49ed-ab1c-c055ae3d6803",
              "name": "username",
              "type": "string",
              "value": "={{ $json.username }}"
            },
            {
              "id": "4010206c-e5a4-41e0-9f3b-4a42509ed118",
              "name": "row_number",
              "type": "number",
              "value": "={{ $('Filter').item.json.row_number }}"
            },
            {
              "id": "2631f30b-9886-4547-809e-be8bba4f0cba",
              "name": "first_name",
              "type": "string",
              "value": "={{ $('Filter').item.json.first_name }}"
            },
            {
              "id": "ba11efc2-09ec-465a-aa03-265a29a98408",
              "name": "connection_note",
              "type": "string",
              "value": "={{ $('Filter').item.json.connection_note }}"
            },
            {
              "id": "5bb53f3e-ee85-437d-8dc1-3d1ba5d92922",
              "name": "unipile_DSN",
              "type": "string",
              "value": "unipile-DSN"
            },
            {
              "id": "16c58cb4-3ce0-48c0-aaba-b7679f6aa30c",
              "name": "unipile_api_key",
              "type": "string",
              "value": "your-api-key-here"
            },
            {
              "id": "07d81241-ded9-4df1-ba96-cd5730f5a960",
              "name": "unipile_account_ID",
              "type": "string",
              "value": "your-unipile-linkedin-account-id-here"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "32de3e00-855b-4738-81be-8869af3000ba",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        2592,
        304
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "5074fbd6-a232-4e95-af39-b48df19384d9",
      "name": "Get Linkedin Provider ID",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2864,
        320
      ],
      "parameters": {
        "url": "=https://{{ $json.unipile_DSN }}/api/v1/users/{{ $json.username }}",
        "options": {},
        "sendQuery": true,
        "sendHeaders": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "account_id",
              "value": "={{ $json.unipile_account_ID }}"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "X-API-KEY",
              "value": "={{ $json.unipile_api_key }}"
            },
            {
              "name": "accept",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "2777a601-7dd8-407c-b949-24e741f180fb",
      "name": "Send Connection Request",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3136,
        320
      ],
      "parameters": {
        "url": "=https://{{ $('Loop Over Items').item.json.unipile_DSN }}/api/v1/users/invite",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "provider_id",
              "value": "={{ $json.provider_id }}"
            },
            {
              "name": "account_id",
              "value": "={{ $('Data Arrangement').item.json.unipile_account_ID }}"
            },
            {
              "name": "message",
              "value": "={{ $('Loop Over Items').item.json.connection_note }}"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "X-API-KEY",
              "value": "={{ $('Loop Over Items').item.json.unipile_api_key }}"
            },
            {
              "name": "accept",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "634e66be-9e37-4cae-bf99-637538794a9e",
      "name": "Update Connection Request Status and ID",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3344,
        320
      ],
      "parameters": {
        "columns": {
          "value": {
            "row_number": "={{ $('Loop Over Items').item.json.row_number }}",
            "invitation_id": "={{ $json.invitation_id }}",
            "connection_request_status": "={{ $json.object }}"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "row_number"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultName": "leads"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1IM0qnZlhYK7Pbj__xwyPgEprW-5-P3RBnen1xGZjSSk",
          "cachedResultName": "linkedin_connection_request_template"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "b5588ed4-5802-4b53-92ff-ba0e3206c358",
      "name": "Wait",
      "type": "n8n-nodes-base.wait",
      "position": [
        3600,
        320
      ],
      "parameters": {
        "unit": "minutes"
      },
      "typeVersion": 1.1
    },
    {
      "id": "6da65c08-5492-42c3-8815-ccc9aab317da",
      "name": "\ud83d\udcd6 Setup Instructions",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        656,
        -320
      ],
      "parameters": {
        "color": 4,
        "width": 500,
        "height": 820,
        "content": "# \ud83d\udd17 LinkedIn Auto Connection Request\n**Automated LinkedIn outreach \u2014 personalized, safe, and trackable.**\n\n### \ud83c\udfaf What This Workflow Does:\n\u2705 Reads leads from Google Sheets\n\u2705 Filters out already-contacted leads\n\u2705 Sends personalized connection requests via LinkedIn\n\u2705 Tracks every invitation back in your sheet\n\u2705 Adds delays to avoid LinkedIn restrictions\n\n### \ud83d\udccb Setup Required:\n\n**1\ufe0f\u20e3 Google Sheet**\nMake sure your sheet has these columns:\n`first_name` | `last_name` | `linkedin_url` | `connection_note` | `connection_request_status` | `invitation_id` | `row_number`\n\n**2\ufe0f\u20e3 Unipile Account**\n- Sign up at: https://unipile.com\n- Connect your LinkedIn account\n- Get your: API Key, DSN, and Account ID\n- Fill these in the **Data Arrangement** node\n\n**3\ufe0f\u20e3 Schedule**\n- Set your preferred run frequency in the Schedule Trigger node\n- Recommended: every 4\u20138 hours for safe outreach\n\n**4\ufe0f\u20e3 Set Your Daily Limit**\n- Open the **Limit Connection Request** node\n- Set max items to 10\u201315 per run (safe range)\n\n### \ud83d\ude80 Quick Start:\n1. Complete setup steps above\n2. Activate the workflow\n3. Add leads to your Google Sheet\n4. Watch connections go out automatically!"
      },
      "typeVersion": 1
    },
    {
      "id": "a99c390c-053c-43bc-98a2-2f79f28a972f",
      "name": "\ud83d\udce5 Lead Collection Path",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1200,
        -144
      ],
      "parameters": {
        "color": 5,
        "width": 760,
        "height": 640,
        "content": "## \ud83d\udce5 LEAD COLLECTION & FILTERING\n\n**Pulls leads from Google Sheets and prepares them for outreach.**\n\n### \ud83d\udd04 Flow:\n1\ufe0f\u20e3 **Schedule Trigger** \u2014 Runs on your set schedule\n2\ufe0f\u20e3 **Get Leads** \u2014 Reads all rows from the 'leads' sheet\n3\ufe0f\u20e3 **Filter** \u2014 Keeps only leads where `connection_request_status` is empty (not yet contacted)\n4\ufe0f\u20e3 **Limit** \u2014 Caps the batch size per run\n\n### \ud83d\udca1 Key Points:\n- Leads already sent a request are automatically skipped\n- Adjust the Limit node to control daily volume\n- Recommended limit: **10\u201315 per run** to stay safe\n\n### \ud83d\udcca Sheet Columns Needed:\n`linkedin_url` | `connection_note` | `connection_request_status`"
      },
      "typeVersion": 1
    },
    {
      "id": "8e4edf7a-df26-4243-833c-ea2782328793",
      "name": "\u2699\ufe0f Data Preparation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2000,
        -224
      ],
      "parameters": {
        "color": 3,
        "width": 540,
        "height": 724,
        "content": "## \u2699\ufe0f DATA PREPARATION\n\n**Extracts the LinkedIn username and arranges all data needed for the API calls.**\n\n### \ud83d\udd04 Flow:\n1\ufe0f\u20e3 **Get LinkedIn Username** \u2014 Parses the username from the LinkedIn URL\n   - Input: `https://linkedin.com/in/john-doe/`\n   - Output: `john-doe`\n\n2\ufe0f\u20e3 **Data Arrangement** \u2014 Bundles all required fields into one clean item:\n   - `username` \u2192 for API lookup\n   - `row_number` \u2192 to update the correct sheet row later\n   - `connection_note` \u2192 personalized message per lead\n   - `unipile_DSN`, `unipile_api_key`, `unipile_account_ID` \u2192 your API credentials\n\n### \u2699\ufe0f Setup \u2014 Fill In the Data Arrangement Node:\n- `unipile-DSN` \u2192 Your Unipile DSN\n- `your-api-key-here` \u2192 Your Unipile API Key\n- `your-unipile-linkedin-account-id-here` \u2192 Your LinkedIn Account ID"
      },
      "typeVersion": 1
    },
    {
      "id": "4cd20a6e-04e1-4351-985f-67cef7dde723",
      "name": "\ud83d\udce8 Connection Request Loop",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2576,
        -112
      ],
      "parameters": {
        "color": 6,
        "width": 820,
        "height": 608,
        "content": "## \ud83d\udce8 CONNECTION REQUEST LOOP\n\n**Sends connection requests one by one with a delay between each.**\n\n### \ud83d\udd04 Flow:\n1\ufe0f\u20e3 **Loop Over Items** \u2014 Processes leads one at a time\n2\ufe0f\u20e3 **Get LinkedIn Provider ID** \u2014 Calls Unipile API to resolve the username to LinkedIn's internal `provider_id`\n3\ufe0f\u20e3 **Send Connection Request** \u2014 Posts the invite with your personalized note\n4\ufe0f\u20e3 **Update Status & ID** \u2014 Writes `UserInvitationSent` + `invitation_id` back to Google Sheets\n5\ufe0f\u20e3 **Wait** \u2014 Pauses between requests to mimic human behavior\n\ud83d\udd01 Loop repeats until all leads in the batch are processed\n\n### \u26a0\ufe0f LinkedIn Safety Tips:\n- Set Wait to **3\u20135 minutes** for safest operation\n- Keep daily total under **20 requests**\n- Use a warmed-up LinkedIn account\n- Vary your connection notes per lead"
      },
      "typeVersion": 1
    },
    {
      "id": "6a329601-d7e7-4b78-96fe-f710a8aae766",
      "name": "\u2705 Tracking & Updates",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3408,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 660,
        "content": "## \u2705 TRACKING & STATUS UPDATE\n\n**Keeps your Google Sheet up to date after every request.**\n\n### \ud83d\udcdd What Gets Written Back:\n| Column | Value |\n|---|---|\n| `connection_request_status: ` | `UserInvitationSent` |\n| `invitation_id: ` | Unique ID from Unipile |\n\n### \ud83c\udfaf Why This Matters:\n- The **Filter node** uses `connection_request_status` to skip leads on future runs\n- `invitation_id` can be used for follow-up message workflows\n- Matched by `row_number` \u2014 always updates the exact correct row\n\n### \ud83d\udca1 Build On This:\nYou can extend this workflow to send follow-up messages to accepted connections using the stored `invitation_id`."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Wait": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter": {
      "main": [
        [
          {
            "node": "Limit Connection Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Leads": {
      "main": [
        [
          {
            "node": "Filter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [],
        [
          {
            "node": "Get Linkedin Provider ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Data Arrangement": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Get Leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Linkedin Username": {
      "main": [
        [
          {
            "node": "Data Arrangement",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Connection Request": {
      "main": [
        [
          {
            "node": "Update Connection Request Status and ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Linkedin Provider ID": {
      "main": [
        [
          {
            "node": "Send Connection Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Limit Connection Request": {
      "main": [
        [
          {
            "node": "Get Linkedin Username",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Connection Request Status and ID": {
      "main": [
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}