This workflow corresponds to n8n.io template #9236 — we link there as the canonical source.
This workflow follows the Chainllm → Gmail 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": "T8iqiPFY6WdVtlC0",
"name": "Retention Tracking Post-Hire",
"tags": [],
"nodes": [
{
"id": "c315357f-e9ad-43c8-89f3-e0a030fb6308",
"name": "When clicking \u2018Execute workflow\u2019",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-544,
-32
],
"parameters": {},
"typeVersion": 1
},
{
"id": "5703baf8-53a0-46a7-b8fe-1bddb3da80a5",
"name": "Candidate Data Fetch",
"type": "n8n-nodes-base.googleSheets",
"position": [
-256,
64
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1454922719,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Uldk_4BxWbdZTDZxFUeohIfeBmGHHqVEl9Ogb0l6R8Y/edit#gid=1454922719",
"cachedResultName": "Retention Summary)"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1Uldk_4BxWbdZTDZxFUeohIfeBmGHHqVEl9Ogb0l6R8Y",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Uldk_4BxWbdZTDZxFUeohIfeBmGHHqVEl9Ogb0l6R8Y/edit?usp=drivesdk",
"cachedResultName": "Interviewer Brief Pack "
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.6
},
{
"id": "76e5ed93-981c-402f-ba97-0f6c07b4642e",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
880,
464
],
"parameters": {
"height": 384,
"content": "## \u26a0\ufe0f Error Handling Logic (Google Sheets \u2013 Error Log) \n\n**Action:** \n- Appends failed runs into the *Error Log Sheet*. \n\n**Description:** \n- Records details like error_id and error message. \n- Provides visibility into workflow issues. \n- Ensures graceful handling of bad data without breaking the workflow. \n"
},
"typeVersion": 1
},
{
"id": "d48ac595-d48e-401d-88bd-a31c3cd596d4",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
592,
176
],
"parameters": {
"height": 448,
"content": "## \u2705 Data Validation \n\n**Action:** \n- Validates whether both candidate and trait arrays contain records. \n\n**Description:** \n- Condition checks: `candidates.length > 0` AND `traits.length > 0`. \n- If TRUE \u2192 proceeds to LLM digest generation. \n- If FALSE \u2192 workflow routes to error logging. \n- Prevents empty datasets from reaching AI/email stages. \n"
},
"typeVersion": 1
},
{
"id": "bddb3bf5-c3bb-4899-b6ff-aa22b953b366",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
304,
-496
],
"parameters": {
"width": 304,
"height": 464,
"content": "## \ud83e\uddee Candidate Scoring & Data Normalization (Code Node) \n\n**Action:** \n- Cleans, normalizes, and enriches the merged dataset. \n\n**Description:** \n- Splits rows into two arrays: `candidates[]` and `traits[]`. \n- Normalizes headers, trims spaces, and standardizes data. \n- Builds a trait \u2192 weight lookup map from the Retention Summary. \n- Calculates each candidate\u2019s `Candidate_Score` based on their traits. \n- Outputs clean JSON with both candidates and traits. \n"
},
"typeVersion": 1
},
{
"id": "549cdd14-5514-4413-9e91-ec3ddd9aa771",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
80,
192
],
"parameters": {
"width": 272,
"height": 384,
"content": "## \ud83d\udd00 Merge Candidate + Trait Data \n\n**Action:** \n- Combines candidate-level and trait-level rows into one dataset. \n\n**Description:** \n- Unifies inputs from both sheets into a single stream. \n- Ensures both granular (candidates) and aggregated (traits) data are processed together. \n- Passes consolidated data into the scoring and enrichment step. \n"
},
"typeVersion": 1
},
{
"id": "b9f7939e-a01f-400f-b5b1-45b9202f6bc4",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-352,
-576
],
"parameters": {
"width": 288,
"height": 400,
"content": "## \ud83d\udcd1 Trait Summary Fetch (Google Sheets \u2013 Retention Summary) \n\n**Action:** \n- Retrieves aggregated trait-level data from the Retention Summary sheet. \n\n**Description:** \n- Collects retention stats like hires, stayed, left, retention %, and weight adjustment. \n- Identifies which traits are positively or negatively correlated with retention. \n- Feeds into candidate scoring logic to adjust weights dynamically. \n"
},
"typeVersion": 1
},
{
"id": "a5260ee9-3a6a-4cc1-ae50-426cb4e7d372",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-336,
240
],
"parameters": {
"width": 272,
"height": 416,
"content": "## \ud83d\udcd1 Candidate Data Fetch (Google Sheets \u2013 Hires Tracking) \n\n**Action:** \n- Retrieves the candidate\u2019s details from the designated Google Sheet. \n\n**Description:** \n- Pulls structured information such as candidate name, role, traits, start date, and retention status. \n- This sheet acts as the source of truth for post-hire outcomes. \n- Ensures accurate and up-to-date records for downstream scoring and reporting. \n"
},
"typeVersion": 1
},
{
"id": "5b4a9840-0dd7-4fcc-b6d1-a2ca015d5daf",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
1168,
192
],
"parameters": {
"height": 384,
"content": "## \ud83e\udde0 AI Processing Backend (Azure OpenAI Node) \n\n**Action:** \n- Executes GPT processing using Azure OpenAI. \n\n**Description:** \n- Takes candidate + trait JSON input. \n- Applies strict prompting rules (no hallucination, only dataset values). \n- Returns formatted HTML insights for downstream use. \n"
},
"typeVersion": 1
},
{
"id": "94d3d781-c9a4-4778-8029-440612132c7f",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
1024,
-512
],
"parameters": {
"width": 368,
"height": 464,
"content": "## \ud83e\udd16 Retention Digest Generator (LLM Chain) \n\n**Action:** \n- Generates an HTML Retention Digest using Azure OpenAI. \n\n**Description:** \n- Summarizes retention insights into a structured email. \n- Sections include: \n - TL;DR Summary \n - Top Traits (positive) \n - Weak Traits (negative) \n - Candidate Highlights with scores \n - 3 Actionable JD Refinement Tips \n- Ensures valid, styled HTML output ready for email. \n"
},
"typeVersion": 1
},
{
"id": "b6bf37e0-ebfd-48d4-a47b-66d6bbaa2991",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
1504,
-496
],
"parameters": {
"height": 432,
"content": "## \ud83d\udce7 Email Delivery (Gmail \u2013 Send Digest) \n\n**Action:** \n- Sends the Retention Digest via Gmail. \n\n**Description:** \n- Uses the HTML generated by the LLM as the email body. \n- Subject: *Retention Analysis Digest \u2013 Weekly Update*. \n- Recipients: Hiring managers / stakeholders. \n- Automates communication of insights directly to decision-makers. \n"
},
"typeVersion": 1
},
{
"id": "82cb57ca-7214-4789-ba0e-c49871938f13",
"name": " Trait Summary Fetch ",
"type": "n8n-nodes-base.googleSheets",
"position": [
-256,
-144
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 834845387,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Uldk_4BxWbdZTDZxFUeohIfeBmGHHqVEl9Ogb0l6R8Y/edit#gid=834845387",
"cachedResultName": "Hires Tracking"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1Uldk_4BxWbdZTDZxFUeohIfeBmGHHqVEl9Ogb0l6R8Y",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Uldk_4BxWbdZTDZxFUeohIfeBmGHHqVEl9Ogb0l6R8Y/edit?usp=drivesdk",
"cachedResultName": "Interviewer Brief Pack "
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.6
},
{
"id": "41c489c1-9aa2-4ea7-a000-c57e0cd237f0",
"name": "Merge Candidate + Trait Data",
"type": "n8n-nodes-base.merge",
"position": [
128,
0
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "88119bb1-b48a-4d71-95da-6f012e87830e",
"name": "Candidate Scoring & Data Normalization",
"type": "n8n-nodes-base.code",
"position": [
416,
0
],
"parameters": {
"jsCode": "// ---- STEP 1: Separate inputs ----\nconst allRows = $input.all();\n\n// Identify rows with candidate info (Hire Tracking) vs traits (Retention Summary)\nconst candidateRaw = allRows.filter(r => r.json[\"Candidate \"] || r.json.Candidate);\nconst traitsRaw = allRows.filter(r => r.json.Trait);\n\n// ---- STEP 2: Normalize Candidate Data ----\nconst candidates = candidateRaw.map(c => {\n return {\n row_number: c.json.row_number,\n Candidate: (c.json[\"Candidate \"] || c.json.Candidate || \"\").trim(),\n Role: (c.json[\"Role \"] || c.json.Role || \"\").trim(),\n Traits: (c.json[\"Traits \"] || c.json.Traits || \"\").trim(),\n Start_Date: c.json[\"Start Date\"] || null,\n Status: (c.json[\"Status \"] || c.json.Status || \"\").trim(),\n Retention_30: c.json[\"Retention_30 \"] ?? c.json.Retention_30 ?? null,\n Retention_90: c.json[\"Retention_90\"] ?? null,\n Candidate_Score: c.json[\"Candidate_Score\"] || 0\n };\n});\n\n// ---- STEP 3: Normalize Traits Summary ----\nconst traits = traitsRaw.map(t => {\n return {\n Trait: t.json.Trait,\n Total_Hires: parseInt(t.json.Total_Hires, 10) || 0,\n Stayed_90: parseInt(t.json.Stayed_90, 10) || 0,\n Left_90: parseInt(t.json.Left_90, 10) || 0,\n \"Retention_%\": parseFloat(t.json[\"Retention_%\"]) || 0,\n Weight_Adjust: parseInt(t.json.Weight_Adjust, 10) || 0,\n Candidate_Score: t.json.Candidate_Score || 0\n };\n});\n\n// ---- STEP 4: Build Trait Weight Lookup ----\nconst weightMap = {};\nfor (const t of traits) {\n weightMap[t.Trait] = t.Weight_Adjust;\n}\n\n// ---- STEP 5: Calculate Candidate Scores ----\nfor (const c of candidates) {\n let score = 0;\n const candidateTraits = (c.Traits || \"\").split(\",\").map(t => t.trim());\n candidateTraits.forEach(trait => {\n score += weightMap[trait] ?? 0;\n });\n c.Candidate_Score = score;\n}\n\n// ---- STEP 6: Final Combined Output ----\nreturn [\n {\n json: {\n candidates,\n traits\n }\n }\n];\n"
},
"typeVersion": 2
},
{
"id": "b563b0b1-393d-48a3-8f8f-d8c55fa1d8b7",
"name": " Data Validation",
"type": "n8n-nodes-base.if",
"position": [
672,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "01f729d5-7169-4f69-89cc-90bc194d11b5",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.candidates.length }}",
"rightValue": 0
},
{
"id": "ef01e17b-4779-4819-a879-742246d8a3f4",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.traits.length }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.2
},
{
"id": "71be08e3-2031-416a-851f-9833953b1e25",
"name": " Error Handling Logic",
"type": "n8n-nodes-base.googleSheets",
"position": [
928,
304
],
"parameters": {
"columns": {
"value": {},
"schema": [
{
"id": "error_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "error_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "error",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "error",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"error_id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1338537721,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Uldk_4BxWbdZTDZxFUeohIfeBmGHHqVEl9Ogb0l6R8Y/edit#gid=1338537721",
"cachedResultName": "error log sheet"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1Uldk_4BxWbdZTDZxFUeohIfeBmGHHqVEl9Ogb0l6R8Y",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Uldk_4BxWbdZTDZxFUeohIfeBmGHHqVEl9Ogb0l6R8Y/edit?usp=drivesdk",
"cachedResultName": "Interviewer Brief Pack "
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.6
},
{
"id": "62e4914b-c97a-46b7-bd66-1fc1cbd4d0df",
"name": " Retention Digest Generator",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
1104,
-16
],
"parameters": {
"text": "=Here is the retention dataset:\n\n{{ JSON.stringify($json, null, 2) }}\n\nGenerate one **Retention Digest** email using this dataset. \nThe HTML should include the following sections:\n\n1. **TL;DR Summary** \u2013 one short paragraph summarizing retention insights. \n2. **Top Traits (Strong Retention + Positive Weights)** \u2013 list traits with Retention_% = 1 and Weight_Adjust > 0. \n3. **Weak Traits (Poor Retention + Negative Weights)** \u2013 list traits with Retention_% = 0 or Weight_Adjust < 0. \n4. **Candidate Highlights** \u2013 list each candidate, their traits, retention status, and Candidate_Score (positive/negative). \n5. **Actionable Tips** \u2013 provide 3 practical JD refinement recommendations based only on this dataset. \n\n\ud83d\udccc **Formatting requirements**: \n- Blue header bar (#0073e6) with white bold title \u201cRetention Insights Digest\u201d. \n- White card-style container with light shadow + rounded corners. \n- Section headings: \n \u2022 Blue (#0073e6) for TL;DR and Top Traits \n \u2022 Red (#d9534f) for Weak Traits \n \u2022 Green (#28a745) for Actionable Tips \n- Candidate list in a table (Name, Traits, Score, Retention_90). \n- Green CTA button (#28a745) at the bottom labeled \u201cView Full Report\u201d. \n- Output only valid HTML, no markdown or code fences. \n",
"batching": {},
"messages": {
"messageValues": [
{
"message": "You are an HR Analytics Assistant. STRICT RULES: - Use ONLY the traits and candidate data from the provided dataset. - Do not invent or hallucinate new traits, values, or candidates. - Always echo the exact Retention_% and Weight_Adjust values. - Show candidate scores exactly as calculated. - Output must be valid, production-ready HTML email with inline CSS styling (email-safe). - Do not include markdown or code fences (no ```html)."
}
]
},
"promptType": "define"
},
"typeVersion": 1.7
},
{
"id": "9fc86f99-9bc5-4161-9145-9cce505d808c",
"name": " AI Processing Backend ",
"type": "@n8n/n8n-nodes-langchain.lmChatAzureOpenAi",
"position": [
1072,
160
],
"parameters": {
"model": "gpt-4o-mini",
"options": {}
},
"credentials": {
"azureOpenAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "489d85e3-e51c-4db9-ab20-e466db61aa1e",
"name": "Email Delivery",
"type": "n8n-nodes-base.gmail",
"position": [
1552,
-16
],
"parameters": {
"toList": [
"user@example.com"
],
"message": " Weekly Update",
"subject": "=Retention Analysis Digest - Weekly Update\n",
"resource": "message",
"htmlMessage": "={{ $json.text }}",
"includeHtml": true,
"additionalFields": {
"ccList": []
}
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "1c0b5bac-8ce0-4e48-a2e1-b9bf0868d16a",
"connections": {
" Data Validation": {
"main": [
[
{
"node": " Retention Digest Generator",
"type": "main",
"index": 0
}
],
[
{
"node": " Error Handling Logic",
"type": "main",
"index": 0
}
]
]
},
"Candidate Data Fetch": {
"main": [
[
{
"node": "Merge Candidate + Trait Data",
"type": "main",
"index": 1
}
]
]
},
" Trait Summary Fetch ": {
"main": [
[
{
"node": "Merge Candidate + Trait Data",
"type": "main",
"index": 0
}
]
]
},
" AI Processing Backend ": {
"ai_languageModel": [
[
{
"node": " Retention Digest Generator",
"type": "ai_languageModel",
"index": 0
}
]
]
},
" Retention Digest Generator": {
"main": [
[
{
"node": "Email Delivery",
"type": "main",
"index": 0
}
]
]
},
"Merge Candidate + Trait Data": {
"main": [
[
{
"node": "Candidate Scoring & Data Normalization",
"type": "main",
"index": 0
}
]
]
},
"When clicking \u2018Execute workflow\u2019": {
"main": [
[
{
"node": "Candidate Data Fetch",
"type": "main",
"index": 0
},
{
"node": " Trait Summary Fetch ",
"type": "main",
"index": 0
}
]
]
},
"Candidate Scoring & Data Normalization": {
"main": [
[
{
"node": " Data Validation",
"type": "main",
"index": 0
}
]
]
}
}
}
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.
azureOpenAiApigmailOAuth2googleSheetsOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow automates employee retention analytics by combining candidate performance data with trait-level retention statistics. It scores candidates, validates data, and generates a polished Retention Digest HTML email using GPT (Azure OpenAI). Hiring managers receive…
Source: https://n8n.io/workflows/9236/ — 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 workflow automates the candidate rejection process after interviews. It retrieves interview feedback from Google Sheets, uses AI to structure and score candidate data, applies a scoring threshold
This workflow automates personalized candidate communication for both shortlisted and rejected applicants. It fetches candidate details, processes resumes, checks for errors, and uses GPT-4o to genera
This workflow automates Zendesk ticket handling with AI-powered auto-replies. It pulls in new support tickets, checks against a Google Sheets FAQ database, and generates accurate responses using GPT-4
Streamline interview feedback handling with this rejection-focused workflow. It processes raw interview feedback, applies an AI-powered scoring model, checks against a threshold, and automatically sen
This workflow automates the creation of comprehensive interviewer brief packs by extracting candidate resume data and generating professional HTML interview preparation materials. It saves recruiters