This workflow corresponds to n8n.io template #9332 — we link there as the canonical source.
This workflow follows the Google Sheets → LinkedIn 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": "Automated LinkedIn Job Posting with AI Content Generator",
"tags": [],
"nodes": [
{
"id": "ff66c07a-7c70-4b1f-9b08-439051889d16",
"name": "Recrutei Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"position": [
-64,
0
],
"parameters": {
"path": "new-job-created",
"options": {},
"httpMethod": "POST"
},
"credentials": {},
"typeVersion": 2.1
},
{
"id": "70282417-5778-4e57-871d-5efef5bc21e0",
"name": "Clean and Standardize Job Data",
"type": "n8n-nodes-base.code",
"position": [
144,
0
],
"parameters": {
"jsCode": "// Maps over each item in the input\nreturn items.map(item => {\n\u00a0 // item.json is the main object of the item in n8n\n\u00a0 const body = item.json.body;\n\n\u00a0 // Helper function to convert 0/1 to \"no\"/\"yes\"\n\u00a0 const convertBoolean = (value) => value === 1 ? 'yes' : 'no';\n\n\u00a0 // Apply conversion to the desired fields\n\u00a0 body.fixed_remuneration = convertBoolean(body.fixed_remuneration);\n\u00a0 body.remote = convertBoolean(body.remote);\n\u00a0 body.pcd = convertBoolean(body.pcd);\n\u00a0 body.is_inclusive = convertBoolean(body.is_inclusive);\n\n\u00a0 // Return the modified 'body' object\n\u00a0 return body;\n});"
},
"typeVersion": 2
},
{
"id": "52cfd7ce-57f0-4e30-af4c-ab03c93ecc8f",
"name": "Publish LinkedIn Post",
"type": "n8n-nodes-base.linkedIn",
"position": [
1312,
0
],
"parameters": {
"text": "={{ $json.message.content }}",
"person": "YOUR_LINKEDIN_PROFILE_ID",
"additionalFields": {}
},
"credentials": {},
"typeVersion": 1
},
{
"id": "db5dfcdf-471c-45e0-b13b-47046e5a57cf",
"name": "Google Sheets Logging",
"type": "n8n-nodes-base.googleSheets",
"position": [
1520,
0
],
"parameters": {
"columns": {
"value": {
"Job Title": "={{ $('Clean and Standardize Job Data').item.json.title }}",
"Published": "Yes"
},
"schema": [
{
"id": "Job Title",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Job Title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Published",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Published",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"Job Title"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "YOUR_SHEET_ID_HERE",
"cachedResultName": "Job Posting Log"
}
},
"credentials": {},
"typeVersion": 4.7
},
{
"id": "434cd22e-0acd-412e-bcd4-d99094f93022",
"name": "Transform Data to AI Prompt",
"type": "n8n-nodes-base.code",
"position": [
464,
0
],
"parameters": {
"jsCode": "// Gets the first item from the input array, which contains the job data.\nconst vagaData = $input.item.json;\n\n// Mapping of original field names to translation and output text.\n// 'skip': true for fields we don't want to include in the final text.\nconst fieldMap = {\n\u00a0 \u00a0 \"title\": { \"label\": \"Job Title\" },\n\u00a0 \u00a0 \"manager\": { \"label\": \"Hiring Manager\" },\n\u00a0 \u00a0 \"quantity\": { \"label\": \"Vacancy Quantity\" },\n\u00a0 \u00a0 \"client\": { \"label\": \"Client Name\" },\n\u00a0 \u00a0 \"department\": { \"label\": \"Department\" },\n\u00a0 \u00a0 \"pipe\": { \"label\": \"Pipeline/Stage\" },\n\u00a0 \u00a0 \"internal_code\": { \"label\": \"Internal Code\" },\n\u00a0 \u00a0 \"status\": { \"label\": \"Status\" },\n\u00a0 \u00a0 \"type\": { \"label\": \"Job Type\" },\n\u00a0 \u00a0 \"sla\": { \"label\": \"SLA (Max Deadline)\" },\n\u00a0 \u00a0 \"expired\": { \"label\": \"Expiration Date\" },\n\u00a0 \u00a0 \"regime\": { \"label\": \"Contract Regime\" },\n\u00a0 \u00a0 \"public_link\": { \"label\": \"Public Link\" },\n\u00a0 \u00a0 \"remuneration_type\": { \"label\": \"Remuneration Type\" },\n\u00a0 \u00a0 \"remuneration\": { \"label\": \"Remuneration Value\" },\n\u00a0 \u00a0 \"fixed_remuneration\": { \"label\": \"Fixed Remuneration\" },\n\u00a0 \u00a0 \"description\": { \"label\": \"Detailed Description\", \"is_long_text\": true },\n\u00a0 \u00a0 \"skills\": { \"label\": \"Key Skills\" },\n\u00a0 \u00a0 \"benefits\": { \"label\": \"Benefits\" },\n\u00a0 \u00a0 \"remote\": { \"label\": \"Remote Work\" },\n\u00a0 \u00a0 \"location\": { \"label\": \"Location\" },\n\u00a0 \u00a0 \"country\": { \"label\": \"Country\" },\n\u00a0 \u00a0 \"state\": { \"label\": \"State\" },\n\u00a0 \u00a0 \"city\": { \"label\": \"City\" },\n\u00a0 \u00a0 \"pcd\": { \"label\": \"PWD Vacancy\" },\n\u00a0 \u00a0 \"is_inclusive\": { \"label\": \"Inclusive Vacancy\" },\n\u00a0 \u00a0 \n\u00a0 \u00a0 // Fields to be ignored\n\u00a0 \u00a0 \"id\": { \"skip\": true },\n\u00a0 \u00a0 \"client_id\": { \"skip\": true },\n\u00a0 \u00a0 \"company_department_id\": { \"skip\": true },\n\u00a0 \u00a0 \"pipe_id\": { \"skip\": true },\n\u00a0 \u00a0 \"remuneration_from\": { \"skip\": true },\n\u00a0 \u00a0 \"remuneration_to\": { \"skip\": true }\n};\n\nlet outputText = \"\";\noutputText += `## Job Details: ${vagaData.title}\\n\\n`;\noutputText += `---\\n\\n`;\n\n// Iterate over the mapping to build the text\nfor (const key in fieldMap) {\n\u00a0 \u00a0 if (fieldMap.hasOwnProperty(key) && !fieldMap[key].skip) {\n\u00a0 \u00a0 \u00a0 \u00a0 const fieldInfo = fieldMap[key];\n\u00a0 \u00a0 \u00a0 \u00a0 const label = fieldInfo.label;\n\u00a0 \u00a0 \u00a0 \u00a0 let value = vagaData[key];\n\n\u00a0 \u00a0 \u00a0 \u00a0 // Handle null or empty values\n\u00a0 \u00a0 \u00a0 \u00a0 if (value === null || value === \"\" || value === undefined) {\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 value = \"Not Informed\";\n\u00a0 \u00a0 \u00a0 \u00a0 }\n\u00a0 \u00a0 \u00a0 \u00a0 \n\u00a0 \u00a0 \u00a0 \u00a0 // Special formatting for description (long text, likely HTML)\n\u00a0 \u00a0 \u00a0 \u00a0 if (fieldInfo.is_long_text) {\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0// Try to remove basic HTML tags to clean the description, but maintain list formatting\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 const cleanDescription = String(value)\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 .replace(/<p>|<\\/p>|<br\\s*\\/?>/gi, ' ') // Replaces paragraphs and breaks with space\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 .replace(/<h[1-6]>(.*?)<\\/h[1-6]>/gi, '\\n**$1**\\n') // Formats headers as bold\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 .replace(/<\\/?ul>|<\\/?ol>/gi, '') // Removes ul/ol tags\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 .replace(/<li>/gi, '\u00a0 - ') // Formats list items\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 .replace(/<\\/?strong>|<\\/?b>/gi, '**') // Bold\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 .replace(/<\\/?em>|<\\/?i>/gi, '*') // Italic\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 .replace(/\\s\\s+/g, ' ') // Removes multiple spaces\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 .trim();\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 outputText += `### ${label}:\\n`;\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 outputText += `${cleanDescription}\\n\\n`;\n\u00a0 \u00a0 \u00a0 \u00a0 } \n\u00a0 \u00a0 \u00a0 \u00a0 // Special formatting for remuneration (currency)\n\u00a0 \u00a0 \u00a0 \u00a0 else if (key === \"remuneration\" && typeof value === 'number') {\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 const formattedValue = new Intl.NumberFormat('en-US', { // Changed to en-US for international template\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 style: 'currency',\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 currency: 'USD',\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 minimumFractionDigits: 2\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 }).format(value);\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 outputText += `**${label}:** ${formattedValue}\\n`;\n\u00a0 \u00a0 \u00a0 \u00a0 } \n\u00a0 \u00a0 \u00a0 \u00a0 // Normal formatting for other fields\n\u00a0 \u00a0 \u00a0 \u00a0 else {\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 outputText += `**${label}:** ${value}\\n`;\n\u00a0 \u00a0 \u00a0 \u00a0 }\n\u00a0 \u00a0 }\n}\n\n// The n8n Code node expects an array of objects as output.\nreturn [{\n\u00a0 \u00a0 json: {\n\u00a0 \u00a0 \u00a0 \u00a0 detailedJobPrompt: outputText // Renamed key to be more descriptive\n\u00a0 \u00a0 }\n}];"
},
"typeVersion": 2
},
{
"id": "9c8b68f8-1b0d-44ba-945a-35cc1e3c6798",
"name": "Sticky Note - Trigger",
"type": "n8n-nodes-base.stickyNote",
"position": [
-128,
-96
],
"parameters": {
"width": 448,
"height": 320,
"content": "This is the **entry point** of the workflow. It listens for a POST request from the Recrutei ATS whenever a new job is created/published. You must copy the Webhook URL and configure it in Recrutei."
},
"typeVersion": 1
},
{
"id": "574549fd-e687-4380-8be3-25d54999baf1",
"name": "Sticky Note - Data Pre-processing",
"type": "n8n-nodes-base.stickyNote",
"position": [
336,
-96
],
"parameters": {
"color": 4,
"width": 400,
"height": 320,
"content": "Uses two Code nodes to clean the raw data (Code 1: Boolean conversion) and structure it (Code 2: Markdown prompt generation) for optimal AI interpretation."
},
"typeVersion": 1
},
{
"id": "ee3012c2-007d-4a0f-a4de-7d2a34939838",
"name": "Sticky Note - AI Generation",
"type": "n8n-nodes-base.stickyNote",
"position": [
752,
-96
],
"parameters": {
"color": 5,
"width": 448,
"height": 320,
"content": "The **OpenAI model** acts as a professional copywriter. It takes the structured prompt and generates an engaging, marketing-focused text ready for LinkedIn."
},
"typeVersion": 1
},
{
"id": "2130e4ef-7f40-4906-b432-ef5867203a22",
"name": "AI Content Generator",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
864,
0
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "GPT-4O-MINI"
},
"options": {},
"messages": {
"values": [
{
"content": "={{ $json.detailedJobPrompt }}"
},
{
"role": "system",
"content": "You are a professional HR Marketing Copywriter. Your task is to receive detailed job information and transform it into an engaging, attractive LinkedIn post for candidate attraction. Focus on benefits, culture, and key responsibilities. Include relevant hashtags."
}
]
}
},
"credentials": {},
"typeVersion": 1.8
},
{
"id": "bb6ce9b4-e5c1-4de1-8b66-c76347a26dbf",
"name": "Sticky Note - Publish & Log",
"type": "n8n-nodes-base.stickyNote",
"position": [
1216,
-96
],
"parameters": {
"color": 6,
"width": 512,
"height": 320,
"content": "The final content is published on LinkedIn. Finally, the job title and publishing status are logged in the **Google Sheets Logging** node for internal audit."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"connections": {
"AI Content Generator": {
"main": [
[
{
"node": "Publish LinkedIn Post",
"type": "main",
"index": 0
}
]
]
},
"Publish LinkedIn Post": {
"main": [
[
{
"node": "Google Sheets Logging",
"type": "main",
"index": 0
}
]
]
},
"Recrutei Webhook Trigger": {
"main": [
[
{
"node": "Clean and Standardize Job Data",
"type": "main",
"index": 0
}
]
]
},
"Transform Data to AI Prompt": {
"main": [
[
{
"node": "AI Content Generator",
"type": "main",
"index": 0
}
]
]
},
"Clean and Standardize Job Data": {
"main": [
[
{
"node": "Transform Data to AI Prompt",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow automates the publication of new job vacancies on LinkedIn immediately after they are created in the Recrutei ATS (Applicant Tracking System). It leverages a Code node to pre-process the job data and a powerful AI model (GPT-4o-mini, configured via the OpenAI node)…
Source: https://n8n.io/workflows/9332/ — 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.
Instantly map all internal URLs, perform AI-powered (ChatGPT) analysis, and deliver results in HTML via webhook, Google Sheets, or email. All from your own n8n instance!
Watch on Youtube▶️
This workflow is perfect for marketing agencies, SEO consultants, and growth specialists who need to scale personalized outreach without spending hours on manual research.
This is for creators who run Patreon and/or Kofi pages, support donations and want to automate their communication process.
Imagine your recruitment process transformed into a sleek, efficient, AI-powered assembly line for talent. That's exactly what this system creates. It automates the heavy lifting, allowing your human