This workflow corresponds to n8n.io template #9309 — we link there as the canonical source.
This workflow follows the Agent → 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": "ONqZuWnwkE64Mm4F",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Attrition Risk Alert Workflow (Azure OpenAI + n8n)",
"tags": [],
"nodes": [
{
"id": "c48bef67-a0d0-4a4b-a374-3df1a7ed4c19",
"name": "Azure OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatAzureOpenAi",
"position": [
688,
128
],
"parameters": {
"model": "gpt-4o-mini",
"options": {}
},
"credentials": {
"azureOpenAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "a44062d6-1e37-443b-b146-cf32a9ac74cc",
"name": "Structured Output Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
816,
128
],
"parameters": {
"jsonSchemaExample": "{\n\t\"average\": \"18\"\n}"
},
"typeVersion": 1.3
},
{
"id": "2ab2bd76-1809-4015-9d99-a278561287da",
"name": "Trigger for new resume",
"type": "n8n-nodes-base.googleDriveTrigger",
"position": [
0,
-96
],
"parameters": {
"event": "fileCreated",
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"triggerOn": "specificFolder",
"folderToWatch": {
"__rl": true,
"mode": "list",
"value": "1KyX5RGqeF7v0sAvvaoBnJPTQoq4aOHLz",
"cachedResultUrl": "https://drive.google.com/drive/folders/1KyX5RGqeF7v0sAvvaoBnJPTQoq4aOHLz",
"cachedResultName": "HR auto"
}
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "dabfc101-cfa3-43e6-b568-320c1c2d3e92",
"name": "Download resume",
"type": "n8n-nodes-base.googleDrive",
"position": [
224,
-96
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "url",
"value": "={{ $json.webViewLink }}"
},
"options": {},
"operation": "download"
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 3
},
{
"id": "821c7e31-be41-4e6c-b58b-19a2cf880648",
"name": "Extract text",
"type": "n8n-nodes-base.extractFromFile",
"position": [
448,
-96
],
"parameters": {
"options": {},
"operation": "pdf"
},
"typeVersion": 1
},
{
"id": "78f8966f-dbeb-4773-b198-9dfb13defd35",
"name": "Calculate avg span",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
672,
-96
],
"parameters": {
"text": "={{ $json.text }}",
"options": {
"systemMessage": "You are given resume text as input. Your task is to extract employment experiences and return ONLY the average tenure (in months) across those experiences as a single number.\n\nInstructions:\n- Scope: Consider only professional employment entries under Experience (exclude Education, Projects, Achievements, Certifications, Skills, Languages).\n- Grouping: Treat each distinct job/employer entry as one experience. If multiple roles at the same employer have separate date ranges, treat each as separate experiences.\n- Dates:\n - Parse start and end dates in formats like \"MMM YYYY\", \"Month YYYY\", \"YYYY\", or date ranges using -, \u2013, \u2014.\n - If the end date is \"Present\"/current, use today's date.\n - If a date has only a year, assume the month as January.\n - If a start or end month/day is missing, default to the first day of that month.\n- Duration Calculation:\n - Compute the difference in full months for each experience (start inclusive, end exclusive).\n - Do not deduct overlaps; each listed experience is counted independently.\n - Exclude entries without any valid start date.\n- Averaging:\n - Compute the arithmetic mean of the months across all valid experiences.\n - Round to the nearest whole month (standard rounding).\n\n"
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2.2
},
{
"id": "5e697388-e418-495e-b91f-5eba9deb0427",
"name": "Logic",
"type": "n8n-nodes-base.if",
"position": [
1024,
-96
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "21c98617-ba35-4906-afd0-38a82211dbe7",
"operator": {
"type": "number",
"operation": "lt"
},
"leftValue": "={{ $json.output.average.toNumber() }}",
"rightValue": 12
}
]
}
},
"typeVersion": 2.2
},
{
"id": "93b32316-47a7-4769-8a82-289a4c66c73f",
"name": "Create email",
"type": "n8n-nodes-base.code",
"position": [
1248,
-96
],
"parameters": {
"jsCode": "// filename: n8n-code-node-attrition-email.js\n// This Code node builds an email for each item indicating high attrition risk.\n// Output fields:\n// - emailSubject\n// - emailBody\n// - emailTo (optional if you want to set here)\n// - emailCc (optional)\n// - emailMetadata (structured data for downstream logging)\n\nfunction formatDate(dateStr) {\n if (!dateStr) return \"N/A\";\n const d = new Date(dateStr);\n if (isNaN(d.getTime())) return dateStr;\n // Format as YYYY-MM-DD for clarity\n return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, \"0\")}-${String(d.getDate()).padStart(2, \"0\")}`;\n}\n\nfunction clampScore(score) {\n const n = Number(score);\n if (Number.isNaN(n)) return null;\n return Math.max(0, Math.min(100, n));\n}\n\nfunction classifyRisk(score) {\n if (score == null) return \"Unknown\";\n if (score >= 80) return \"High\";\n if (score >= 50) return \"Medium\";\n return \"Low\";\n}\n\nfunction bulletList(items) {\n if (!Array.isArray(items) || items.length === 0) return \"- None\";\n return items.map(s => `- ${String(s).trim() || \"N/A\"}`).join(\"\\n\");\n}\n\nconst outputs = [];\n\nfor (const item of $input.all()) {\n const j = item.json || {};\n\n const personName = j.personName || j.name || \"The employee\";\n const role = j.role || \"N/A\";\n const department = j.department || null;\n const managerName = j.managerName || \"HR/People Ops\";\n const riskScoreRaw = j.riskScore ?? j.attritionRiskScore;\n const riskScore = clampScore(riskScoreRaw);\n const riskLevel = classifyRisk(riskScore);\n const signals = Array.isArray(j.signals) ? j.signals : [];\n const lastEngagementDate = formatDate(j.lastEngagementDate || j.lastCheckInDate);\n const recommendedActions = Array.isArray(j.recommendedActions) ? j.recommendedActions : [\n \"Schedule a 1:1 check\u2011in within the next 3\u20135 days\",\n \"Offer growth or role\u2011clarity conversation\",\n \"Review workload and compensation alignment\",\n ];\n\n // Email routing (optional; you can also set these in the Email node)\n const emailTo = j.emailTo || j.managerEmail || j.hrEmail || \"\";\n const emailCc = j.emailCc || \"\";\n\n // Compose subject\n const subjectParts = [\n \"[Attrition Alert]\",\n personName !== \"The employee\" ? personName : \"Employee\",\n riskLevel !== \"Unknown\" ? `\u2013 ${riskLevel} Risk (${riskScore}%)` : \"\u2013 Risk Review\",\n ];\n const emailSubject = subjectParts.filter(Boolean).join(\" \");\n\n // Compose body (plain text; you can convert to HTML if preferred)\n const headerLine = `${personName} ${role !== \"N/A\" ? `(${role})` : \"\"}${department ? `, ${department}` : \"\"}`;\n const riskLine = `Attrition Risk: ${riskLevel}${riskScore != null ? ` (${riskScore}%)` : \"\"}`;\n const signalsBlock = bulletList(signals);\n const actionsBlock = bulletList(recommendedActions);\n\n const emailBody =\n`Hi ${managerName},\n\nThis is a heads\u2011up that ${headerLine} may be at risk of leaving soon. ${riskLine}.\n\nKey signals observed:\n${signalsBlock}\n\nLast engagement/check\u2011in: ${lastEngagementDate}\n\nSuggested next steps:\n${actionsBlock}\n\nPlease prioritize a supportive outreach. If helpful, we can prepare a retention plan (growth discussion, workload review, recognition, and role clarity).\n\nThanks,\nPeople Analytics\n`;\n\n outputs.push({\n json: {\n ...j,\n emailSubject,\n emailBody,\n emailTo,\n emailCc,\n emailMetadata: {\n personName,\n role,\n department,\n managerName,\n riskScore,\n riskLevel,\n signals,\n lastEngagementDate,\n recommendedActions,\n generatedAt: new Date().toISOString(),\n },\n },\n });\n}\n\nreturn outputs;\n"
},
"typeVersion": 2
},
{
"id": "cc3e9d3e-4a29-4125-83bd-c43770219fca",
"name": "Send email to hr",
"type": "n8n-nodes-base.gmail",
"position": [
1472,
-96
],
"parameters": {
"sendTo": "user@example.com",
"message": "={{ $json.emailBody }}",
"options": {},
"subject": "={{ $json.emailSubject }}"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "860057d2-af64-4ea5-8df5-1945f27b52d2",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-336,
-128
],
"parameters": {
"content": "## Trigger for new resume\u00a0 \nStarts the workflow when a new resume file is added (e.g., to storage or inbox)."
},
"typeVersion": 1
},
{
"id": "ce4d511d-b14d-4a13-ac97-a319e90b2d2a",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
128,
-304
],
"parameters": {
"content": "## Download resume\u00a0 \nFetches the resume file from the source and makes it available for processing."
},
"typeVersion": 1
},
{
"id": "dcf63ce0-b0f0-4ede-9c95-c7c37814b028",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
384,
80
],
"parameters": {
"content": "## Extract text\u00a0 \nPulls readable text from the downloaded resume using a PDF extraction step."
},
"typeVersion": 1
},
{
"id": "114d70ee-1e49-41f4-937b-6b8e183d787b",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
656,
-304
],
"parameters": {
"content": "## Chat Model\u00a0 \nUses Azure OpenAI Chat to analyze or summarize the extracted resume content."
},
"typeVersion": 1
},
{
"id": "b5ee6f06-3ac8-402a-9051-f5ee0aaae094",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
960,
96
],
"parameters": {
"content": "## Logic\u00a0 \nApplies conditional checks and routing (true/false) based on parsed results."
},
"typeVersion": 1
},
{
"id": "e7fcd240-e008-419f-ac1b-e45593f3c037",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1168,
-320
],
"parameters": {
"content": "## Create email\u00a0 \nGenerates a tailored email draft to the candidate or HR using the parsed data."
},
"typeVersion": 1
},
{
"id": "e8aae9bd-426b-4020-8b47-f48bc35b1ae3",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
1408,
96
],
"parameters": {
"content": "## Send email to hr\u00a0 \nSends the composed message to HR via the configured email service."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "a93f6021-cc9d-4249-b529-62a19a79292a",
"connections": {
"Logic": {
"main": [
[
{
"node": "Create email",
"type": "main",
"index": 0
}
]
]
},
"Create email": {
"main": [
[
{
"node": "Send email to hr",
"type": "main",
"index": 0
}
]
]
},
"Extract text": {
"main": [
[
{
"node": "Calculate avg span",
"type": "main",
"index": 0
}
]
]
},
"Download resume": {
"main": [
[
{
"node": "Extract text",
"type": "main",
"index": 0
}
]
]
},
"Calculate avg span": {
"main": [
[
{
"node": "Logic",
"type": "main",
"index": 0
}
]
]
},
"Trigger for new resume": {
"main": [
[
{
"node": "Download resume",
"type": "main",
"index": 0
}
]
]
},
"Azure OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Calculate avg span",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "Calculate avg span",
"type": "ai_outputParser",
"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.
azureOpenAiApigmailOAuth2googleDriveOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Automatically ingests new employee data, extracts relevant signals, scores attrition risk, and notifies HR/managers with structured insights and recommended actions. Built on Azure OpenAI Chat with Structured Output Parser and true/false routing for escalation. Trigger for new…
Source: https://n8n.io/workflows/9309/ — 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 is designed to evaluate newly added CVs for Diversity, Equity, and Inclusion (DEI) eligibility. It automatically ingests CVs from Google Drive, extracts key fields, analyzes them with Az
Who is this for? Agencies, consultants, and service providers who conduct discovery calls and need to quickly turn conversations into professional proposals.
Transcript Evalu8r V2 is a robust browser-based transcript analysis tool powered by Deepgram’s speech-to-text API and built into an n8n workflow template. This release introduces full in-browser audio
Transcript Evalu8r is an AI-powered transcript analysis workflow that automates the processing, visualization, and evaluation of transcribed conversations. This n8n workflow template is designed to he
This workflow automates end-to-end validation, assessment, and reporting of n8n workflow JSON templates using Google Drive, Azure OpenAI GPT-4o, Gmail, and Slack. It retrieves workflows from a Drive f