This workflow corresponds to n8n.io template #16702 — we link there as the canonical source.
This workflow follows the HTTP Request → Postgres 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 →
{
"id": "4bdpFHsJhB5CrpTt",
"name": "LinkedIn | Scheduled Posting (Twenty CRM \u2192 Unipile)",
"tags": [],
"nodes": [
{
"id": "ec0e4c03-c85b-4c71-af86-4e97faa0b786",
"name": "Manual Trigger - Test Run",
"type": "n8n-nodes-base.manualTrigger",
"position": [
144,
784
],
"parameters": {},
"typeVersion": 1
},
{
"id": "d97ff359-0318-4f2f-9546-4416aff99488",
"name": "Fetch Scheduled Posts (Twenty CRM)",
"type": "n8n-nodes-base.postgres",
"position": [
368,
880
],
"parameters": {
"query": "-- Replace \"your_schema\" with your own Postgres/Twenty CRM schema name that stores your scheduled-post tracking table\nSELECT\n sp.id,\n sp.name,\n sp.\"scheduledAt\",\n sp.status,\n sp.\"retryCount\",\n sp.\"lastError\",\n sp.name AS \"postText\",\n sp.\"attachmentUrlPrimaryLinkUrl\" AS \"attachmentUrl\",\n sp.\"linkedinAccountId\" AS \"linkedinAccountRowId\",\n la.\"accountId\" AS \"unipileAccountId\",\n la.name AS \"linkedinAccountName\"\nFROM your_schema.\"_linkedinScheduledPost\" sp\nJOIN your_schema.\"_linkedinAccountUnipile\" la ON la.id = sp.\"linkedinAccountId\"\nWHERE\n sp.status = 'SCHEDULED'::your_schema.\"_linkedinScheduledPost_status_enum\"\n AND sp.\"scheduledAt\" <= now()\n AND COALESCE(sp.\"retryCount\", 0) < 2\n AND (la.active = true)\nORDER BY sp.\"scheduledAt\" ASC\nLIMIT 10;",
"options": {},
"operation": "executeQuery"
},
"typeVersion": 2.6
},
{
"id": "69d3e974-0ee3-48e3-a6a1-e3dbe14f09fa",
"name": "Cron - Every 5 Minutes",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
144,
976
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes"
}
]
}
},
"typeVersion": 1.3
},
{
"id": "caf68daf-f85c-4e8e-a455-cca3bb05d7a1",
"name": "Check Attachment Present?",
"type": "n8n-nodes-base.if",
"position": [
816,
800
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "exists"
},
"leftValue": "={{ $json.attachmentUrl }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.3
},
{
"id": "f16810c2-0058-4a5b-b303-8883730752d3",
"name": "Build SharePoint ShareId",
"type": "n8n-nodes-base.code",
"position": [
1040,
704
],
"parameters": {
"jsCode": "// Input: item.json.attachmentUrl = your SharePoint sharing link\nconst url = $input.first().json.attachmentUrl;\nconst b64 = Buffer.from(url, 'utf8').toString('base64');\nconst b64url = b64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '');\nreturn [{ json: { shareId: `u!${b64url}` } }];"
},
"typeVersion": 2
},
{
"id": "c47a92ba-bd03-48f3-b439-97159493e276",
"name": "SharePoint - Resolve Drive Item",
"type": "n8n-nodes-base.httpRequest",
"position": [
1264,
704
],
"parameters": {
"url": "=https://graph.microsoft.com/v1.0/shares/{{$json.shareId}}/driveItem",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "microsoftEntraOAuth2Api"
},
"typeVersion": 4.4
},
{
"id": "959581f8-56f6-461b-8274-cc28be04f2e2",
"name": "SharePoint - Download Attachment (Binary)",
"type": "n8n-nodes-base.httpRequest",
"position": [
1488,
704
],
"parameters": {
"url": "=https://graph.microsoft.com/v1.0/drives/{{$json.parentReference.driveId}}/items/{{$json.id}}/content",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "microsoftEntraOAuth2Api"
},
"typeVersion": 4.4
},
{
"id": "9dd12013-ad35-48bb-a8ea-dd68abf6814a",
"name": "Unipile - Create Post (With Attachment)",
"type": "n8n-nodes-base.httpRequest",
"position": [
1712,
704
],
"parameters": {
"url": "https://api30.unipile.com:16038/api/v1/posts",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "multipart-form-data",
"sendHeaders": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "account_id",
"value": "={{ $('Fetch Scheduled Posts (Twenty CRM)').item.json.unipileAccountId }}"
},
{
"name": "text",
"value": "={{ $('Fetch Scheduled Posts (Twenty CRM)').item.json.postText }}"
},
{
"name": "attachments",
"parameterType": "formBinaryData",
"inputDataFieldName": "data"
}
]
},
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "accept",
"value": "application/json"
}
]
}
},
"typeVersion": 4.4
},
{
"id": "2f8af441-aaff-4a70-bb21-6c4050c053e7",
"name": "Unipile - Get Post Details",
"type": "n8n-nodes-base.httpRequest",
"position": [
1936,
800
],
"parameters": {
"url": "=https://api30.unipile.com:16038/api/v1/posts/{{ $json.post_id }}",
"options": {},
"sendQuery": true,
"sendHeaders": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "account_id",
"value": "={{ $('Fetch Scheduled Posts (Twenty CRM)').item.json.unipileAccountId }}"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "accept",
"value": "application/json"
}
]
}
},
"typeVersion": 4.4
},
{
"id": "198805a4-80a1-42cf-bca8-ad715156016f",
"name": "Mark Post as POSTED (Twenty CRM Update)",
"type": "n8n-nodes-base.postgres",
"position": [
2160,
880
],
"parameters": {
"query": "UPDATE your_schema.\"_linkedinScheduledPost\"\nSET\n status = 'POSTED'::your_schema.\"_linkedinScheduledPost_status_enum\",\n \"postedAt\" = now(),\n \"updatedAt\" = now(),\n \"postId\" = '{{ $json.id }}',\n \"postUrlPrimaryLinkUrl\" = '{{ $json.share_url }}'\nWHERE\n \"id\" = '{{ $(\"Fetch Scheduled Posts (Twenty CRM)\").item.json.id }}';",
"options": {},
"operation": "executeQuery"
},
"typeVersion": 2.6
},
{
"id": "2cd41c18-a082-4f8a-b887-69fdd55e7a8f",
"name": "Process Posts (Split in Batches)",
"type": "n8n-nodes-base.splitInBatches",
"position": [
592,
880
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "45f41c63-a94b-4b9d-8d3c-c75479377f16",
"name": "Unipile - Create Post (Text Only)",
"type": "n8n-nodes-base.httpRequest",
"position": [
1712,
896
],
"parameters": {
"url": "https://api30.unipile.com:16038/api/v1/posts",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "multipart-form-data",
"sendHeaders": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "account_id",
"value": "={{ $('Fetch Scheduled Posts (Twenty CRM)').item.json.unipileAccountId }}"
},
{
"name": "text",
"value": "={{ $('Fetch Scheduled Posts (Twenty CRM)').item.json.postText }}"
}
]
},
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "accept",
"value": "application/json"
}
]
}
},
"typeVersion": 4.4
},
{
"id": "199b0e49-17b7-43d7-93bd-fcafa0a721f2",
"name": "Sticky Note dd8392fa",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1552,
400
],
"parameters": {
"color": 4,
"width": 1424,
"height": 612,
"content": "LinkedIn Auto-Publish & Tracking (Twenty CRM \u2192 Unipile)\n\nWho it's for:\nTeams using a Postgres-backed content calendar (e.g., Twenty CRM) wanting auto-publishing and tracking for LinkedIn.\n\nHow it works:\nRuns every 5 mins, fetches due posts from Postgres, and publishes them to LinkedIn via Unipile (with optional SharePoint images). Writes the live URL back to the CRM.\n\nSetup:\n\nConnect credentials for Postgres, SharePoint (Azure AD), and Unipile.\n\nUpdate your_schema in the Postgres nodes.\n\nEnable the Cron trigger for production.\n\nRequirements:\n\u2022 Postgres DB with a scheduled-posts table\n\u2022 Unipile account connected to LinkedIn\n\u2022 Microsoft 365/SharePoint (if using images)"
},
"typeVersion": 1
},
{
"id": "d221d32c-ec2d-48c6-acbe-da4b0b4ddc2b",
"name": "Sticky Note 3ee98d6a",
"type": "n8n-nodes-base.stickyNote",
"position": [
96,
448
],
"parameters": {
"color": 6,
"width": 300,
"height": 200,
"content": "Phase 0 - Triggers\n\u2022 Cron Node: Runs every 5 mins (Production)\n\u2022 Manual Node: For testing only"
},
"typeVersion": 1
},
{
"id": "86220a79-95e4-49fe-8424-57c3432d40dd",
"name": "Sticky Note 206db8db",
"type": "n8n-nodes-base.stickyNote",
"position": [
432,
448
],
"parameters": {
"color": 7,
"width": 300,
"height": 200,
"content": "Phase 1 - Fetch Due Posts\nQueries Twenty CRM (Postgres) for up to 10 posts where:\n\u2022 Status = SCHEDULED\n\u2022 scheduledAt <= NOW()\n\u2022 Retry < 2"
},
"typeVersion": 1
},
{
"id": "04a0a782-ecb2-401f-923b-43a37ae0c7de",
"name": "Sticky Note 15275059",
"type": "n8n-nodes-base.stickyNote",
"position": [
752,
448
],
"parameters": {
"color": 4,
"width": 300,
"height": 200,
"content": "Phase 2 - Batching & Routing\nProcesses posts one-by-one.\n\u2022 Route A: Text-only posts\n\u2022 Route B: Posts with attachments"
},
"typeVersion": 1
},
{
"id": "bd23ff58-216b-4bc3-a9b3-96a306f9fe27",
"name": "Sticky Note 8ad5702d",
"type": "n8n-nodes-base.stickyNote",
"position": [
1088,
448
],
"parameters": {
"color": 2,
"width": 300,
"height": 200,
"content": "Phase 3A - Attachment (SharePoint)\nConverts Share URL \u2192 ID \u2192 Metadata, then downloads the binary file.\n(Note: Keep the binary field name \"data\" for Unipile)."
},
"typeVersion": 1
},
{
"id": "d2386123-9237-4426-abb3-d9af283d9b4c",
"name": "Sticky Note a91d73e4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1424,
448
],
"parameters": {
"width": 300,
"height": 200,
"content": "Phase 3B - Publish (Unipile)\nPublishes to LinkedIn. Ensure account_id and text are mapped to the correct Unipile account and post content."
},
"typeVersion": 1
},
{
"id": "b516f265-5703-4f5d-a651-04399598b617",
"name": "Sticky Note 6021a440",
"type": "n8n-nodes-base.stickyNote",
"position": [
1744,
448
],
"parameters": {
"color": 7,
"width": 300,
"height": 200,
"content": "Phase 4 - Verify & Update\nFetches the live LinkedIn URL from Unipile. Updates the source CRM row with: Status = POSTED, Live URL, and Post ID."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "5741d061-ab3d-4048-a978-da20cd26efe3",
"nodeGroups": [],
"connections": {
"Cron - Every 5 Minutes": {
"main": [
[
{
"node": "Fetch Scheduled Posts (Twenty CRM)",
"type": "main",
"index": 0
}
]
]
},
"Build SharePoint ShareId": {
"main": [
[
{
"node": "SharePoint - Resolve Drive Item",
"type": "main",
"index": 0
}
]
]
},
"Check Attachment Present?": {
"main": [
[
{
"node": "Build SharePoint ShareId",
"type": "main",
"index": 0
}
],
[
{
"node": "Unipile - Create Post (Text Only)",
"type": "main",
"index": 0
}
]
]
},
"Manual Trigger - Test Run": {
"main": [
[
{
"node": "Fetch Scheduled Posts (Twenty CRM)",
"type": "main",
"index": 0
}
]
]
},
"Unipile - Get Post Details": {
"main": [
[
{
"node": "Mark Post as POSTED (Twenty CRM Update)",
"type": "main",
"index": 0
}
]
]
},
"SharePoint - Resolve Drive Item": {
"main": [
[
{
"node": "SharePoint - Download Attachment (Binary)",
"type": "main",
"index": 0
}
]
]
},
"Process Posts (Split in Batches)": {
"main": [
[],
[
{
"node": "Check Attachment Present?",
"type": "main",
"index": 0
}
]
]
},
"Unipile - Create Post (Text Only)": {
"main": [
[
{
"node": "Unipile - Get Post Details",
"type": "main",
"index": 0
}
]
]
},
"Fetch Scheduled Posts (Twenty CRM)": {
"main": [
[
{
"node": "Process Posts (Split in Batches)",
"type": "main",
"index": 0
}
]
]
},
"Mark Post as POSTED (Twenty CRM Update)": {
"main": [
[
{
"node": "Process Posts (Split in Batches)",
"type": "main",
"index": 0
}
]
]
},
"Unipile - Create Post (With Attachment)": {
"main": [
[
{
"node": "Unipile - Get Post Details",
"type": "main",
"index": 0
}
]
]
},
"SharePoint - Download Attachment (Binary)": {
"main": [
[
{
"node": "Unipile - Create Post (With Attachment)",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Automate your LinkedIn content calendar. This workflow fetches scheduled posts from a PostgreSQL database (Twenty CRM), downloads attached media from SharePoint, and publishes them seamlessly to LinkedIn using Unipile. A Schedule Trigger (Cron) fires every 5 minutes to check for…
Source: https://n8n.io/workflows/16702/ — 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.
06 - Body Recovery (LinkedIn JSON-LD). Uses postgres, httpRequest. Event-driven trigger; 9 nodes.
Turn your blog into a set-and-forget content engine: every new article is instantly repurposed into channel-specific social posts with visuals, keeping your brand visible on LinkedIn, X, and Reddit wi
This n8n workflow automatically shares content from a Telegram Channel to multiple platforms like WordPress, Facebook, X/Twitter, and LinkedIn. It uses a Switch node to detect the type of content—text
Hacker News to Video Template - AlexK1919. Uses hackerNews, s3, httpRequest, dropbox. Event-driven trigger; 48 nodes.
Disclaimer: this workflow only works on self-hosted instances due to the file system usage.