This workflow follows the Google Sheets → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Send Personalized DMs to LinkedIn Profile Visitors",
"nodes": [
{
"parameters": {
"content": "## Send Personalized DMs to LinkedIn Profile Visitors\n\nThis workflow automatically identifies people who visited your LinkedIn profile and sends them personalized direct messages or connection requests based on their connection status.\n\n### Who is this for?\nSales professionals, recruiters, founders, and networkers who want to convert profile visitors into meaningful connections without manual outreach.\n\n### How it works\n1. **Fetch Visitors** \u2013 Retrieves your LinkedIn profile visitors from the past 7 days via ConnectSafely.ai API\n2. **Deduplicate** \u2013 Checks Google Sheets to avoid messaging the same person twice\n3. **Route by Connection** \u2013 Sends DMs to 1st-degree connections or connection requests to others\n4. **Track Progress** \u2013 Logs all outreach to Google Sheets for tracking\n\n### Setup steps\n1. Get your ConnectSafely.ai API key from [connectsafely.ai](https://connectsafely.ai)\n2. Configure the HTTP Bearer Auth credential with your API key\n3. Connect your Google Sheets account and create a sheet with columns: `Name`, `Linkedin URL`, `Status`\n4. Update the Google Sheets node with your sheet ID\n5. Customize the message templates in the Code nodes\n\n### Customization\n| What | Where |\n|------|-------|\n| Message templates | \"Generate DM for Connected User\" and \"Generate Message for New Connection\" Code nodes |\n| Fetch frequency | Schedule Trigger node |\n| Time range for visitors | \"Fetch Profile Visitors\" HTTP Request body |\n\n**Note:** This template requires a ConnectSafely.ai account and API access.",
"height": 852,
"width": 588
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-2304,
-240
],
"id": "f64b448a-f0eb-4dd8-a05f-20711f8f1c26",
"name": "Workflow Overview"
},
{
"parameters": {
"content": "## 1. Fetch & Split Visitors\nRetrieves profile visitors and prepares them for individual processing.",
"height": 284,
"width": 460,
"color": 7
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1686,
84
],
"id": "a0c192b8-e394-4ba1-ab5a-af47abdf6937",
"name": "Section 1 - Fetch Visitors"
},
{
"parameters": {
"content": "## 2. Deduplicate\nChecks if visitor was already contacted to avoid duplicate outreach.",
"height": 316,
"width": 520,
"color": 7
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1040,
48
],
"id": "3a053157-fec1-465e-9154-8189edf61c96",
"name": "Section 2 - Deduplicate"
},
{
"parameters": {
"content": "## 3. Route & Send Messages\nSends DMs to connected users or connection requests to new prospects.",
"height": 532,
"width": 844,
"color": 7
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-304,
-176
],
"id": "ae2c1f44-4f82-42fc-bfc5-4243d477f9c8",
"name": "Section 3 - Send Messages"
},
{
"parameters": {
"content": "## 4. Track & Loop\nLogs outreach to Google Sheets and processes next visitor.",
"height": 532,
"width": 480,
"color": 7
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
576,
-176
],
"id": "d859a78a-66dd-47b4-ba6a-2471c96aee86",
"name": "Section 4 - Track Progress"
},
{
"parameters": {
"jsCode": "const url = $('Loop Over Items').first().json.navigationUrl\n\nlet profile_id = null;\n\nif (url) {\n // Remove query params and trailing slash\n const cleanUrl = url.split('?')[0].replace(/\\/$/, '');\n\n // Extract profile ID after /in/\n const match = cleanUrl.match(/linkedin\\.com\\/in\\/([^/]+)/);\n\n if (match) {\n profile_id = match[1];\n }\n}\n\nreturn {\n profile_id,\n};\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-272,
64
],
"id": "10ccda8b-2c59-48ea-a2ba-a50725c4f198",
"name": "Extract Profile ID from URL"
},
{
"parameters": {
"method": "POST",
"url": "https://api.connectsafely.ai/linkedin/profile/visitors",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{\"timeRange\":\"past_7_days\",\"start\":0,\"fetchAll\":true}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-1392,
208
],
"id": "0d693106-d391-4c15-8eee-c878b5c24e3c",
"name": "Fetch Profile Visitors"
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "weeks"
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-1616,
208
],
"id": "7000a0b8-3431-4fca-b696-2c4fee4d0aeb",
"name": "Weekly Schedule Trigger"
},
{
"parameters": {
"method": "POST",
"url": "https://api.connectsafely.ai/linkedin/connect",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "profileId",
"value": "={{ $('Extract Profile ID from URL').item.json.profile_id }}"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
400,
160
],
"id": "6a27975a-28b9-4e9d-8cb3-f9c1731b735e",
"name": "Send Connection Request"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "0cca670d-4ced-4027-98f4-9337048d8a1d",
"leftValue": "={{ $('Loop Over Items').item.json.connectionDegree }}",
"rightValue": "1st",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-48,
64
],
"id": "22cb5dc4-555d-4cba-8599-d4d128e20180",
"name": "Check if 1st Degree Connection"
},
{
"parameters": {
"method": "POST",
"url": "https://api.connectsafely.ai/linkedin/message",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "recipientProfileId",
"value": "={{ $('Extract Profile ID from URL').item.json.profile_id }}"
},
{
"name": "message",
"value": "={{ $json.message }}"
},
{
"name": "messageType",
"value": "inmail"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
400,
-32
],
"id": "2fedafe7-567e-4cdc-83db-7da8ce3a9fa6",
"name": "Send DM to Connected User"
},
{
"parameters": {
"jsCode": "// Get recipient name\nconst rawName = $('Loop Over Items').first().json.name || 'there';\nconst name = rawName.trim();\n\n// Sender footer - CUSTOMIZE THIS\nconst footer = `\\n\\n\u2014 Your Name`;\n\n// Message bodies for NON-connected users (connection requests)\n// CUSTOMIZE THESE MESSAGES for your use case\nconst messages = [\n \"Noticed you checked out my profile, so I thought I'd reach out and connect. I'm working on [YOUR PRODUCT] \u2014 [BRIEF DESCRIPTION]. Happy to connect and share ideas.\",\n\n \"Saw that you viewed my profile \u2014 always great to connect with like-minded professionals. I'm building [YOUR PRODUCT] to help with [VALUE PROP]. Would love to stay in touch.\",\n\n \"Thanks for stopping by my profile! I'm currently working on [YOUR PRODUCT], focused on [BENEFIT]. Let's connect and exchange insights.\",\n\n \"Noticed your visit on my profile and thought I'd say hello. I'm building [YOUR PRODUCT] to help [TARGET AUDIENCE]. Happy to connect!\",\n\n \"Hey there! I saw you viewed my profile and wanted to connect. I'm working on [YOUR PRODUCT] \u2014 [BRIEF VALUE]. Looking forward to connecting.\"\n];\n\n// Pick random body\nconst body = messages[Math.floor(Math.random() * messages.length)];\n\n// FINAL MESSAGE\nconst finalMessage = `Hey ${name},\\n\\n${body}${footer}`;\n\nreturn {\n message: finalMessage,\n};\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
176,
160
],
"id": "af60a93f-7b21-4187-a243-93c648b0f348",
"name": "Generate Message for New Connection"
},
{
"parameters": {
"jsCode": "// Get recipient name\nconst rawName = $('Loop Over Items').first().json.name || 'there';\nconst name = rawName.trim();\n\n// Sender footer - CUSTOMIZE THIS\nconst footer = `\\n\\n\u2014 Your Name`;\n\n// Message bodies for CONNECTED users (direct messages)\n// CUSTOMIZE THESE MESSAGES for your use case\nconst messages = [\n \"Noticed you recently checked out my profile, so I thought I'd reach out. I've been working on [YOUR PRODUCT] \u2014 [BRIEF DESCRIPTION]. Happy to share if it's relevant.\",\n\n \"Saw your visit on my profile and wanted to say hello. I'm currently building [YOUR PRODUCT] to help professionals [VALUE PROP].\",\n\n \"I noticed you viewed my profile, so I thought I'd connect the dots. I've been working on [YOUR PRODUCT], focused on [BENEFIT].\",\n\n \"Thanks for stopping by my profile! I'm building [YOUR PRODUCT] to help [TARGET AUDIENCE]. Happy to chat if useful.\",\n\n \"Noticed your profile visit and thought I'd reach out. I'm working on [YOUR PRODUCT] \u2014 [BRIEF VALUE PROPOSITION].\"\n];\n\n// Pick random body\nconst body = messages[Math.floor(Math.random() * messages.length)];\n\n// FINAL MESSAGE\nconst finalMessage = `Hey ${name},\\n\\n${body}${footer}`;\n\nreturn {\n message: finalMessage,\n};\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
176,
-32
],
"id": "a80ac098-2ff0-456b-8675-151fc9c0c170",
"name": "Generate DM for Connected User"
},
{
"parameters": {
"fieldToSplitOut": "visitors",
"options": {}
},
"type": "n8n-nodes-base.splitOut",
"typeVersion": 1,
"position": [
-1168,
208
],
"id": "f472a968-d6c3-4a94-a323-3bd80c31eaad",
"name": "Split Visitors Array"
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
-944,
208
],
"id": "0fec9210-7b84-4618-990c-a73462a32be5",
"name": "Loop Over Items"
},
{
"parameters": {},
"type": "n8n-nodes-base.noOp",
"name": "Continue to Next Visitor",
"typeVersion": 1,
"position": [
848,
64
],
"id": "f32e7660-6f6f-4566-ae5d-9da2353f5fe0"
},
{
"parameters": {},
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
1072,
304
],
"id": "2a92195e-9b9f-4613-8111-8b3c046f18a8",
"name": "Wait Between Messages"
},
{
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Sheet1",
"cachedResultUrl": ""
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"Status": "DONE",
"Linkedin URL": "={{ $('Loop Over Items').item.json.navigationUrl }}",
"Name": "={{ $('Loop Over Items').item.json.name }}"
},
"matchingColumns": [
"Linkedin URL"
],
"schema": [
{
"id": "Name",
"displayName": "Name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "Linkedin URL",
"displayName": "Linkedin URL",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Status",
"displayName": "Status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
624,
-32
],
"id": "04b34961-4e22-4d7a-85b4-d8f4e2cb78e5",
"name": "Log DM Sent to Sheet"
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Sheet1",
"cachedResultUrl": ""
},
"filtersUI": {
"values": [
{
"lookupColumn": "Linkedin URL",
"lookupValue": "={{ $json.navigationUrl }}"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-720,
136
],
"id": "80964e3b-aac9-4950-8cf5-99378edba2e2",
"name": "Check if Already Contacted",
"alwaysOutputData": true
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "149e3e45-fe35-4ef5-bbdd-da8107b82802",
"leftValue": "={{ $json.isEmpty() }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-496,
136
],
"id": "61c6dbdc-97f1-4bae-8cae-baf823f63803",
"name": "Skip if Already Contacted"
},
{
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Sheet1",
"cachedResultUrl": ""
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"Status": "DONE",
"Linkedin URL": "={{ $('Loop Over Items').item.json.navigationUrl }}",
"Name": "={{ $('Loop Over Items').item.json.name }}"
},
"matchingColumns": [
"Linkedin URL"
],
"schema": [
{
"id": "Name",
"displayName": "Name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "Linkedin URL",
"displayName": "Linkedin URL",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Status",
"displayName": "Status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
624,
160
],
"id": "23d345d5-df7c-41f2-a181-7b731b631a50",
"name": "Log Connection Request to Sheet"
}
],
"connections": {
"Extract Profile ID from URL": {
"main": [
[
{
"node": "Check if 1st Degree Connection",
"type": "main",
"index": 0
}
]
]
},
"Fetch Profile Visitors": {
"main": [
[
{
"node": "Split Visitors Array",
"type": "main",
"index": 0
}
]
]
},
"Weekly Schedule Trigger": {
"main": [
[
{
"node": "Fetch Profile Visitors",
"type": "main",
"index": 0
}
]
]
},
"Send Connection Request": {
"main": [
[
{
"node": "Log Connection Request to Sheet",
"type": "main",
"index": 0
}
]
]
},
"Check if 1st Degree Connection": {
"main": [
[
{
"node": "Generate DM for Connected User",
"type": "main",
"index": 0
}
],
[
{
"node": "Generate Message for New Connection",
"type": "main",
"index": 0
}
]
]
},
"Send DM to Connected User": {
"main": [
[
{
"node": "Log DM Sent to Sheet",
"type": "main",
"index": 0
}
]
]
},
"Generate Message for New Connection": {
"main": [
[
{
"node": "Send Connection Request",
"type": "main",
"index": 0
}
]
]
},
"Generate DM for Connected User": {
"main": [
[
{
"node": "Send DM to Connected User",
"type": "main",
"index": 0
}
]
]
},
"Split Visitors Array": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[],
[
{
"node": "Check if Already Contacted",
"type": "main",
"index": 0
}
]
]
},
"Continue to Next Visitor": {
"main": [
[
{
"node": "Wait Between Messages",
"type": "main",
"index": 0
}
]
]
},
"Wait Between Messages": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Log DM Sent to Sheet": {
"main": [
[
{
"node": "Continue to Next Visitor",
"type": "main",
"index": 0
}
]
]
},
"Check if Already Contacted": {
"main": [
[
{
"node": "Skip if Already Contacted",
"type": "main",
"index": 0
}
]
]
},
"Skip if Already Contacted": {
"main": [
[
{
"node": "Extract Profile ID from URL",
"type": "main",
"index": 0
}
],
[
{
"node": "Wait Between Messages",
"type": "main",
"index": 0
}
]
]
},
"Log Connection Request to Sheet": {
"main": [
[
{
"node": "Continue to Next Visitor",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Send Personalized DMs to LinkedIn Profile Visitors. Uses httpRequest, googleSheets. Scheduled trigger; 21 nodes.
Source: https://gist.github.com/connectsafely/01ebb6d1d5f137b0cde6306fd7ded6a0 — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This template is ideal for sales teams, recruiters, business development professionals, and relationship managers who need to monitor changes in their network's LinkedIn profiles. Perfect for agencies
Save time - Eliminate manual LinkedIn posting and content scheduling tasks Stay consistent - Automated daily posting keeps your LinkedIn profile active and engaging Keep control - Preview every post b
> Set up n8n self-hosted instance using https://tino.vn/vps-n8n?affid=388 > Use the code ==VPSN8N== for up to 39% off.
> Recommended: Self-hosted via tino.vn/vps-n8n?affid=388 — use code VPSN8N for up to 39% off.
Send personalized LinkedIn connection requests automatically using n8n, Google Sheets, and Unipile — with built-in safety limits, duplicate prevention, and full invitation tracking.