This workflow follows the Chainllm → Google Sheets 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": "AI CRM Automation",
"nodes": [
{
"parameters": {
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"documentId": {
"__rl": true,
"value": "1T3CzZOvM8vHFwUQnxG9ACV0aTKochbskQ-dvtCTzaoo",
"mode": "list",
"cachedResultName": "AI CRM Automation",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1T3CzZOvM8vHFwUQnxG9ACV0aTKochbskQ-dvtCTzaoo/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Sheet1",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1T3CzZOvM8vHFwUQnxG9ACV0aTKochbskQ-dvtCTzaoo/edit#gid=0"
},
"event": "rowAdded",
"options": {}
},
"id": "803b0419-4a4b-4b83-a6ae-999cffa8d992",
"name": "Google Sheets Trigger",
"type": "n8n-nodes-base.googleSheetsTrigger",
"typeVersion": 1,
"position": [
400,
48
],
"credentials": {
"googleSheetsTriggerOAuth2Api": {
"name": "<your credential>"
}
},
"notes": "Polls the 'Leads Intake' sheet every minute for newly added rows. Any new lead row triggers automatic enrichment."
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "crma-field-001",
"name": "row_number",
"value": "={{ $json.row_number }}",
"type": "number"
},
{
"id": "crma-field-002",
"name": "fullName",
"value": "={{ $json.Name || 'Unknown' }}",
"type": "string"
},
{
"id": "crma-field-003",
"name": "email",
"value": "={{ $json.Email || '' }}",
"type": "string"
},
{
"id": "crma-field-004",
"name": "company",
"value": "={{ $json.Company || 'Unknown' }}",
"type": "string"
},
{
"id": "crma-field-005",
"name": "industry",
"value": "={{ $json.Industry || '' }}",
"type": "string"
},
{
"id": "crma-field-006",
"name": "notes",
"value": "={{ $json.Notes || '' }}",
"type": "string"
}
]
},
"options": {}
},
"id": "8c21a76a-01d6-4b4f-a677-281ba5b6979f",
"name": "Normalize Row Data",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
624,
48
],
"notes": "Maps raw spreadsheet columns into clean, named variables. row_number is kept so the result can be written back to the same row."
},
{
"parameters": {
"options": {
"maxTokens": 1500,
"temperature": 1
}
},
"id": "88605b53-9cdb-4015-9716-0fbaf2ae17a3",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1,
"position": [
848,
240
],
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"promptType": "define",
"text": "=Analyze this CRM lead and return ONLY a valid JSON object \u2014 no markdown, no explanation, no template placeholders of any kind (never output brackets like [Your Name] or [Company]; use only the real values provided below or write \"Not provided\" if a field is genuinely missing).\n\nLead data:\nName: {{ $json.fullName }}\nEmail: {{ $json.email }}\nCompany: {{ $json.company }}\nIndustry: {{ $json.industry }}\nNotes: {{ $json.notes }}\n\nReturn exactly this JSON structure:\n{\n \"leadScore\": integer 0-100,\n \"qualification\": \"one of: Qualified | Needs Nurturing | Not Qualified\",\n \"enrichmentSummary\": \"2-3 sentences describing the company and opportunity, using only the real data provided\",\n \"recommendedNextStep\": \"one concrete next action for the sales rep\"\n}"
},
"id": "0d866e3c-191a-456a-a926-7f3b873ebcca",
"name": "Analyze & Enrich Lead",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"typeVersion": 1.5,
"position": [
848,
48
],
"notes": "Sends the normalized lead to gpt-4.1-mini. System prompt explicitly forbids placeholder/template output \u2014 this is the fix for the earlier bug where unfilled brackets like [Your Name] were written to the sheet."
},
{
"parameters": {
"jsCode": "const item = $input.first();\n\n// Step 1: Get raw text from the LLM Chain output\nlet rawText = '';\ntry {\n rawText = item.json.text || item.json.output || '';\n} catch (e) {\n rawText = '';\n}\n\n// Step 2: Strip markdown fences and parse JSON\nlet parsed;\ntry {\n const cleaned = rawText\n .replace(/```json/gi, '')\n .replace(/```/g, '')\n .trim();\n parsed = JSON.parse(cleaned);\n} catch (e) {\n parsed = {};\n}\n\n// Step 3: Detect leftover template placeholders (the original bug) and treat as invalid\nfunction hasPlaceholder(value) {\n return typeof value === 'string' && /\\[(your|company|email|phone|name|title)[^\\]]*\\]/i.test(value);\n}\n\n// Step 4: Allowed values and safe defaults\nconst validQualification = ['Qualified', 'Needs Nurturing', 'Not Qualified'];\n\nconst rawScore = parseInt(parsed.leadScore, 10);\nconst leadScore = isNaN(rawScore) ? 0 : Math.min(100, Math.max(0, rawScore));\n\nconst result = {\n row_number: $('Normalize Row Data').item.json.row_number,\n leadScore,\n qualification: validQualification.includes(parsed.qualification)\n ? parsed.qualification\n : 'Needs Nurturing',\n enrichmentSummary: (typeof parsed.enrichmentSummary === 'string' && parsed.enrichmentSummary.length > 5 && !hasPlaceholder(parsed.enrichmentSummary))\n ? parsed.enrichmentSummary\n : 'Insufficient data for a summary. Manual review recommended.',\n recommendedNextStep: (typeof parsed.recommendedNextStep === 'string' && parsed.recommendedNextStep.length > 3 && !hasPlaceholder(parsed.recommendedNextStep))\n ? parsed.recommendedNextStep\n : 'Manually review this lead \u2014 automated enrichment was incomplete.'\n};\n\nreturn [{ json: result }];"
},
"id": "a2827ff6-2f82-4566-b89a-481be012d9e0",
"name": "Parse & Validate Enrichment",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1120,
48
],
"notes": "Parses the AI's JSON output. Rejects any field still containing unfilled template brackets (e.g. [Your Name]) and replaces it with a safe fallback instead of writing broken data to the sheet."
},
{
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "1T3CzZOvM8vHFwUQnxG9ACV0aTKochbskQ-dvtCTzaoo",
"mode": "list",
"cachedResultName": "AI CRM Automation",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1T3CzZOvM8vHFwUQnxG9ACV0aTKochbskQ-dvtCTzaoo/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Sheet1",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1T3CzZOvM8vHFwUQnxG9ACV0aTKochbskQ-dvtCTzaoo/edit#gid=0"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"leadScore": "={{ $json.leadScore }}",
"qualification": "={{ $json.qualification }}",
"enrichmentSummary": "={{ $json.enrichmentSummary }}",
"recommendedNextStep": "={{ $json.recommendedNextStep }}",
"Email": "={{ $('Normalize Row Data').item.json.email }}"
},
"matchingColumns": [
"Email"
],
"schema": [
{
"id": "Name",
"displayName": "Name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Email",
"displayName": "Email",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "Company",
"displayName": "Company",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Industry",
"displayName": "Industry",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Notes",
"displayName": "Notes",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "row_number",
"displayName": "row_number",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "leadScore",
"displayName": "leadScore",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "qualification",
"displayName": "qualification",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "enrichmentSummary",
"displayName": "enrichmentSummary",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "recommendedNextStep",
"displayName": "recommendedNextStep",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"id": "2540c6b7-faf7-49ae-a21d-aa624f1de515",
"name": "Append or Update Row",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
1280,
48
],
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"notes": "Writes the validated enrichment fields back into the same row, matched by row_number."
}
],
"connections": {
"Google Sheets Trigger": {
"main": [
[
{
"node": "Normalize Row Data",
"type": "main",
"index": 0
}
]
]
},
"Normalize Row Data": {
"main": [
[
{
"node": "Analyze & Enrich Lead",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Analyze & Enrich Lead",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Analyze & Enrich Lead": {
"main": [
[
{
"node": "Parse & Validate Enrichment",
"type": "main",
"index": 0
}
]
]
},
"Parse & Validate Enrichment": {
"main": [
[
{
"node": "Append or Update Row",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"versionId": "760103f5-0df4-4eb7-9687-b30fbc9d6f09",
"meta": {
"templateCredsSetupCompleted": true
},
"nodeGroups": [],
"id": "O092aeRDxo0uJKOj",
"tags": [
{
"updatedAt": "2026-07-25T18:18:08.685Z",
"createdAt": "2026-07-25T18:18:08.685Z",
"id": "G8elLSnd3ySOhhG3",
"name": "crm-automation"
},
{
"updatedAt": "2026-07-25T18:18:08.707Z",
"createdAt": "2026-07-25T18:18:08.707Z",
"id": "9oczA1WlNB9PVRVX",
"name": "google-sheets"
},
{
"updatedAt": "2026-07-12T08:55:20.350Z",
"createdAt": "2026-07-12T08:55:20.350Z",
"id": "zLEptWcWohWystrd",
"name": "openai"
},
{
"updatedAt": "2026-07-19T15:39:41.698Z",
"createdAt": "2026-07-19T15:39:41.698Z",
"id": "RZhcdgrGYx5XyAIk",
"name": "ai-automation"
}
]
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleSheetsOAuth2ApigoogleSheetsTriggerOAuth2ApiopenAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
AI CRM Automation. Uses googleSheetsTrigger, lmChatOpenAi, chainLlm, googleSheets. Event-driven trigger; 6 nodes.
Source: https://github.com/nextwave-ai/ai-automation-portfolio/blob/main/Projects/AI-CRM-Automation/workflow.json — 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 end-to-end AI-powered recruitment automation workflow helps HR and talent acquisition teams automate the complete hiring pipeline—from resume intake and parsing to GPT-4-based evaluation, TA appr
> by Thomas Vie Thomas@pollup.net
Workflow 2823. Uses httpRequest, lmChatOpenAi, outputParserStructured, chainLlm. Event-driven trigger; 19 nodes.
2823. Uses httpRequest, lmChatOpenAi, outputParserStructured, chainLlm. Event-driven trigger; 19 nodes.
This workflow watches for new rows in Google Sheets, uses an OpenAI chat model to score and categorize lead intent with structured output, writes the results back to the same sheet, and posts only hig