This workflow corresponds to n8n.io template #15689 — we link there as the canonical source.
This workflow follows the Airtable → 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": "OC2N2tibPWfkDS8q",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "IT Asset Allocation Agent Q \u2014 New Hire Automation",
"tags": [],
"nodes": [
{
"id": "7730ac3e-9a55-41a8-a275-8cd5f20b457d",
"name": "\ud83d\udccb Trigger \u2014 New Employee Row Added",
"type": "n8n-nodes-base.googleSheetsTrigger",
"position": [
-1232,
1040
],
"parameters": {
"event": "rowAdded",
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Employees"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_GOOGLE_SHEET_ID"
}
},
"credentials": {
"googleSheetsTriggerOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "706f2689-03b9-430c-a1ae-a3051eedca7b",
"name": "\ud83d\udd0d Parse Employee & Determine Role Spec",
"type": "n8n-nodes-base.code",
"position": [
-992,
1040
],
"parameters": {
"jsCode": "const row = $input.first().json;\n\nif (!row.name || !row.role || !row.department || !row.start_date || !row.email) {\n throw new Error('Missing required employee fields: name, role, department, start_date, email');\n}\n\nconst roleSpecMap = {\n developer: { laptop_spec: 'high', monitor: true, peripherals: ['keyboard', 'mouse', 'headset'], priority: 'high' },\n engineer: { laptop_spec: 'high', monitor: true, peripherals: ['keyboard', 'mouse', 'headset'], priority: 'high' },\n designer: { laptop_spec: 'high', monitor: true, peripherals: ['keyboard', 'mouse', 'drawing_tablet'], priority: 'high' },\n manager: { laptop_spec: 'standard', monitor: true, peripherals: ['keyboard', 'mouse'], priority: 'medium' },\n operations: { laptop_spec: 'standard', monitor: false, peripherals: ['keyboard', 'mouse'], priority: 'medium' },\n sales: { laptop_spec: 'standard', monitor: false, peripherals: ['keyboard', 'mouse', 'headset'], priority: 'medium' },\n hr: { laptop_spec: 'standard', monitor: false, peripherals: ['keyboard', 'mouse'], priority: 'low' },\n finance: { laptop_spec: 'standard', monitor: false, peripherals: ['keyboard', 'mouse'], priority: 'low' },\n intern: { laptop_spec: 'basic', monitor: false, peripherals: ['keyboard', 'mouse'], priority: 'low' }\n};\n\nconst roleLower = (row.role || '').toLowerCase();\nlet matchedSpec = roleSpecMap.operations;\n\nfor (const [key, spec] of Object.entries(roleSpecMap)) {\n if (roleLower.includes(key)) {\n matchedSpec = spec;\n break;\n }\n}\n\nreturn [{\n json: {\n employee: {\n name: row.name,\n role: row.role,\n department: row.department,\n start_date: row.start_date,\n email: row.email,\n manager_email: row.manager_email || '',\n employee_id: row.employee_id || `EMP-${Date.now()}`\n },\n required_spec: matchedSpec,\n allocation_request_time: new Date().toISOString()\n }\n}];"
},
"typeVersion": 2
},
{
"id": "3ae5d510-a8b3-479c-8f3a-e3a1d8b00d0f",
"name": "\ud83d\uddc4\ufe0f Airtable \u2014 Fetch Available Assets",
"type": "n8n-nodes-base.airtable",
"position": [
-752,
1040
],
"parameters": {
"id": "={{ $json }}",
"base": {
"__rl": true,
"mode": "list",
"value": "appThkE83jPHaYqy8",
"cachedResultUrl": "https://airtable.com/appThkE83jPHaYqy8",
"cachedResultName": "Login DataBase"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblY7e9DizWMbSiKk",
"cachedResultUrl": "https://airtable.com/appThkE83jPHaYqy8/tblY7e9DizWMbSiKk",
"cachedResultName": "Table 1"
},
"options": {}
},
"credentials": {
"airtableTokenApi": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "85eb6b0c-3e7a-4840-8bec-fea2dc57787f",
"name": "\ud83e\udde0 MCTS \u2014 Plan Best Asset Allocation",
"type": "n8n-nodes-base.code",
"position": [
-512,
1040
],
"parameters": {
"jsCode": "const employeeData = $('Parse Employee & Determine Role Spec').first().json;\nconst allAssets = $input.all().map(item => item.json);\n\nconst employee = employeeData.employee;\nconst requiredSpec = employeeData.required_spec;\n\n// MCTS-inspired allocation logic\n// Phase 1: Categorise assets\nconst laptops = allAssets.filter(a => a.fields?.asset_type === 'laptop' && a.fields?.Status === 'Available');\nconst monitors = allAssets.filter(a => a.fields?.asset_type === 'monitor' && a.fields?.Status === 'Available');\nconst peripherals = allAssets.filter(a => ['keyboard','mouse','headset','drawing_tablet'].includes(a.fields?.asset_type) && a.fields?.Status === 'Available');\n\n// Phase 2: Score laptops via MCTS simulation\nconst specScore = (laptop, requiredSpec) => {\n let score = 0;\n const spec = laptop.fields?.spec_tier || 'standard';\n \n if (requiredSpec.laptop_spec === 'high') {\n if (spec === 'high') score += 100;\n else if (spec === 'standard') score += 40;\n else score += 10;\n } else if (requiredSpec.laptop_spec === 'standard') {\n if (spec === 'standard') score += 100;\n else if (spec === 'high') score += 70; // overspec OK\n else score += 20;\n } else {\n if (spec === 'basic') score += 100;\n else if (spec === 'standard') score += 80;\n else score += 60;\n }\n\n // Warranty bonus\n const warrantyExpiry = new Date(laptop.fields?.warranty_expiry || '2020-01-01');\n const today = new Date();\n const monthsLeft = (warrantyExpiry - today) / (1000 * 60 * 60 * 24 * 30);\n if (monthsLeft > 24) score += 20;\n else if (monthsLeft > 12) score += 10;\n else if (monthsLeft < 0) score -= 50; // expired warranty\n\n // Condition bonus\n const condition = laptop.fields?.Condition || '';\n if (condition === 'Excellent') score += 15;\n else if (condition === 'Good') score += 10;\n else if (condition === 'Fair') score += 0;\n\n return score;\n};\n\n// Phase 3: Pick best laptop\nconst scoredLaptops = laptops\n .map(l => ({ ...l, mcts_score: specScore(l, requiredSpec) }))\n .sort((a, b) => b.mcts_score - a.mcts_score);\n\nconst selectedLaptop = scoredLaptops[0] || null;\n\n// Phase 4: Pick monitor if required\nlet selectedMonitor = null;\nif (requiredSpec.monitor && monitors.length > 0) {\n selectedMonitor = monitors.sort((a, b) => {\n const aScore = a.fields?.Condition === 'Excellent' ? 2 : a.fields?.Condition === 'Good' ? 1 : 0;\n const bScore = b.fields?.Condition === 'Excellent' ? 2 : b.fields?.Condition === 'Good' ? 1 : 0;\n return bScore - aScore;\n })[0];\n}\n\n// Phase 5: Pick peripherals\nconst selectedPeripherals = [];\nfor (const pType of requiredSpec.peripherals) {\n const available = peripherals.filter(p => p.fields?.asset_type === pType && !selectedPeripherals.find(s => s.id === p.id));\n if (available.length > 0) selectedPeripherals.push(available[0]);\n}\n\n// Phase 6: Build allocation plan\nconst allSelected = [\n ...(selectedLaptop ? [selectedLaptop] : []),\n ...(selectedMonitor ? [selectedMonitor] : []),\n ...selectedPeripherals\n];\n\nif (!selectedLaptop) {\n throw new Error('MCTS Allocation Failed: No suitable laptop available for this role.');\n}\n\nreturn [{\n json: {\n employee,\n required_spec: requiredSpec,\n allocation: {\n laptop: selectedLaptop ? {\n id: selectedLaptop.id,\n asset_id: selectedLaptop.fields?.asset_id,\n serial_number: selectedLaptop.fields?.serial_number,\n model: selectedLaptop.fields?.model,\n spec_tier: selectedLaptop.fields?.spec_tier,\n condition: selectedLaptop.fields?.Condition,\n warranty_expiry: selectedLaptop.fields?.warranty_expiry,\n mcts_score: selectedLaptop.mcts_score\n } : null,\n monitor: selectedMonitor ? {\n id: selectedMonitor.id,\n asset_id: selectedMonitor.fields?.asset_id,\n serial_number: selectedMonitor.fields?.serial_number,\n model: selectedMonitor.fields?.model,\n condition: selectedMonitor.fields?.Condition\n } : null,\n peripherals: selectedPeripherals.map(p => ({\n id: p.id,\n asset_id: p.fields?.asset_id,\n serial_number: p.fields?.serial_number,\n asset_type: p.fields?.asset_type,\n model: p.fields?.model\n }))\n },\n all_selected_ids: allSelected.map(a => a.id),\n allocation_date: new Date().toISOString().split('T')[0]\n }\n}];"
},
"typeVersion": 2
},
{
"id": "1b6ae4e0-e64b-4a67-87cd-100837fc8cfe",
"name": "\u2705 Self-Critique \u2014 Validate Against Policy",
"type": "n8n-nodes-base.code",
"position": [
-272,
1040
],
"parameters": {
"jsCode": "const data = $input.first().json;\nconst { employee, allocation, required_spec } = data;\n\nconst critiques = [];\nlet approved = true;\n\n// Rule 1: Laptop must be allocated\nif (!allocation.laptop) {\n critiques.push('FAIL: No laptop allocated \u2014 cannot proceed.');\n approved = false;\n} else {\n // Rule 2: Spec must match role priority\n const laptopSpec = allocation.laptop.spec_tier || 'standard';\n if (required_spec.laptop_spec === 'high' && laptopSpec === 'basic') {\n critiques.push(`FAIL: Laptop spec too low (basic) for a high-spec role (${employee.role}).`);\n approved = false;\n } else if (required_spec.laptop_spec === 'high' && laptopSpec === 'standard') {\n critiques.push(`WARN: Standard laptop assigned to high-spec role (${employee.role}). Acceptable if high-spec unavailable.`);\n } else {\n critiques.push(`PASS: Laptop spec (${laptopSpec}) is appropriate for role (${employee.role}).`);\n }\n\n // Rule 3: Warranty check\n if (allocation.laptop.warranty_expiry) {\n const expiry = new Date(allocation.laptop.warranty_expiry);\n const today = new Date();\n const monthsLeft = (expiry - today) / (1000 * 60 * 60 * 24 * 30);\n if (monthsLeft < 0) {\n critiques.push(`FAIL: Laptop ${allocation.laptop.asset_id} warranty has expired (${allocation.laptop.warranty_expiry}).`);\n approved = false;\n } else if (monthsLeft < 6) {\n critiques.push(`WARN: Laptop warranty expires soon (${allocation.laptop.warranty_expiry}). Consider flagging for renewal.`);\n } else {\n critiques.push(`PASS: Laptop warranty valid until ${allocation.laptop.warranty_expiry}.`);\n }\n }\n\n // Rule 4: Condition check\n const condition = allocation.laptop.condition || '';\n if (condition === 'Damaged') {\n critiques.push(`FAIL: Laptop ${allocation.laptop.asset_id} is marked Damaged and must not be allocated.`);\n approved = false;\n } else {\n critiques.push(`PASS: Laptop condition is ${condition || 'not specified'}.`);\n }\n}\n\n// Rule 5: Monitor check\nif (required_spec.monitor && !allocation.monitor) {\n critiques.push('WARN: Monitor required for this role but none available. IT must source one manually.');\n} else if (required_spec.monitor && allocation.monitor) {\n critiques.push(`PASS: Monitor allocated (${allocation.monitor.asset_id}).`);\n}\n\n// Rule 6: Employee info validation\nif (!employee.email || !employee.email.includes('@')) {\n critiques.push('FAIL: Employee email is missing or invalid.');\n approved = false;\n} else {\n critiques.push(`PASS: Employee email valid (${employee.email}).`);\n}\n\nif (approved) {\n critiques.push('\u2705 Self-Critique Complete: Allocation approved and meets all policy requirements.');\n} else {\n critiques.push('\u274c Self-Critique Complete: Allocation REJECTED. Review failures above.');\n}\n\nreturn [{\n json: {\n ...data,\n self_critique: {\n approved,\n critiques,\n validated_at: new Date().toISOString()\n }\n }\n}];"
},
"typeVersion": 2
},
{
"id": "4b7cb855-b51b-44a4-9cbd-8eef5f28510f",
"name": "\u2753 Allocation Approved?",
"type": "n8n-nodes-base.if",
"position": [
-32,
1040
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cond-approved",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.self_critique.approved }}",
"rightValue": true
}
]
}
},
"typeVersion": 2
},
{
"id": "9a1d8ed4-ceeb-4a4b-b075-5359bce51ef8",
"name": "\u2699\ufe0f Prepare Asset Update Items",
"type": "n8n-nodes-base.code",
"position": [
208,
928
],
"parameters": {
"jsCode": "const data = $input.first().json;\nconst { allocation, employee } = data;\nconst allIds = data.all_selected_ids || [];\n\n// Return one item per asset to update\nconst updates = allIds.map(id => ({\n json: {\n airtable_id: id,\n employee_name: employee.name,\n employee_id: employee.employee_id,\n allocation_date: data.allocation_date,\n // Pass full data for downstream nodes\n _full_data: data\n }\n}));\n\nreturn updates;"
},
"typeVersion": 2
},
{
"id": "3138f14a-8e34-46ee-9fdb-f66fe0bfef2c",
"name": "\ud83d\udd12 Airtable \u2014 Mark Assets as Allocated",
"type": "n8n-nodes-base.airtable",
"position": [
448,
928
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "appThkE83jPHaYqy8",
"cachedResultUrl": "https://airtable.com/appThkE83jPHaYqy8",
"cachedResultName": "Login DataBase"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblY7e9DizWMbSiKk",
"cachedResultUrl": "https://airtable.com/appThkE83jPHaYqy8/tblY7e9DizWMbSiKk",
"cachedResultName": "Table 1"
},
"columns": {
"value": {},
"schema": [],
"mappingMode": "autoMapInputData",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update"
},
"credentials": {
"airtableTokenApi": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "32e0daca-675c-4e2e-9c84-a0ecaf044bad",
"name": "\ud83d\udd17 Aggregate All Asset Updates",
"type": "n8n-nodes-base.aggregate",
"position": [
688,
928
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData"
},
"typeVersion": 1
},
{
"id": "9892c90c-3ed8-45c3-9cde-67881f25e6bb",
"name": "\ud83d\udce6 Restore Full Allocation Data",
"type": "n8n-nodes-base.code",
"position": [
928,
928
],
"parameters": {
"jsCode": "// Recover full data from first item\nconst allItems = $input.all();\nconst fullData = allItems[0]?.json?.data?.[0]?.json?._full_data || \n allItems[0]?.json?._full_data ||\n $('Self-Critique \u2014 Validate Against Policy').first().json;\n\nreturn [{ json: fullData }];"
},
"typeVersion": 2
},
{
"id": "907a1796-a11e-4f24-a7f2-9f808c05585f",
"name": "\ud83d\udcc4 CraftMyPDF \u2014 Generate Allocation Letter",
"type": "n8n-nodes-base.httpRequest",
"position": [
1168,
928
],
"parameters": {
"url": "https://api.craftmypdf.com/v1/create",
"method": "POST",
"options": {},
"sendBody": true,
"sendHeaders": true,
"bodyParameters": {
"parameters": [
{
"value": "={\n \"template_id\": \"YOUR_CRAFTMYPDF_TEMPLATE_ID\",\n \"export_type\": \"json\",\n \"expiration\": 10,\n \"data\": {\n \"employee_name\": \"{{ $json.employee.name }}\",\n \"employee_id\": \"{{ $json.employee.employee_id }}\",\n \"role\": \"{{ $json.employee.role }}\",\n \"department\": \"{{ $json.employee.department }}\",\n \"start_date\": \"{{ $json.employee.start_date }}\",\n \"allocation_date\": \"{{ $json.allocation_date }}\",\n \"laptop_asset_id\": \"{{ $json.allocation.laptop.asset_id }}\",\n \"laptop_serial\": \"{{ $json.allocation.laptop.serial_number }}\",\n \"laptop_model\": \"{{ $json.allocation.laptop.model }}\",\n \"laptop_spec\": \"{{ $json.allocation.laptop.spec_tier }}\",\n \"laptop_warranty\": \"{{ $json.allocation.laptop.warranty_expiry }}\",\n \"monitor_asset_id\": \"{{ $json.allocation.monitor ? $json.allocation.monitor.asset_id : 'N/A' }}\",\n \"monitor_serial\": \"{{ $json.allocation.monitor ? $json.allocation.monitor.serial_number : 'N/A' }}\",\n \"monitor_model\": \"{{ $json.allocation.monitor ? $json.allocation.monitor.model : 'N/A' }}\",\n \"peripherals\": {{ JSON.stringify($json.allocation.peripherals) }},\n \"company_name\": \"YOUR_COMPANY_NAME\",\n \"it_contact_email\": \"it@yourcompany.com\"\n }\n}"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "X-API-KEY",
"value": "YOUR_CRAFTMYPDF_API_KEY"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "51e9d34e-8e3f-4d9b-8859-fe241b7ac0cd",
"name": "\ud83d\udd17 Merge PDF URL with Employee Data",
"type": "n8n-nodes-base.code",
"position": [
1408,
928
],
"parameters": {
"jsCode": "const pdfData = $input.first().json;\nconst employeeData = $('Restore Full Data').first().json;\n\nconst pdfUrl = pdfData.file || pdfData.url || pdfData.download_url || '';\n\nreturn [{\n json: {\n ...employeeData,\n pdf_url: pdfUrl,\n pdf_generated_at: new Date().toISOString()\n }\n}];"
},
"typeVersion": 2
},
{
"id": "25e606be-5dfc-4171-8d77-7caea565a431",
"name": "\ud83d\udce7 Gmail \u2014 Email Employee & Manager",
"type": "n8n-nodes-base.gmail",
"position": [
1648,
928
],
"parameters": {
"sendTo": "={{ $json.employee.email }}",
"message": "=<html>\n<body style=\"font-family: Arial, sans-serif; color: #333; max-width: 640px; margin: 0 auto;\">\n <div style=\"background: #1a1a2e; padding: 24px; border-radius: 8px 8px 0 0;\">\n <h1 style=\"color: #ffffff; margin: 0;\">\ud83d\udda5\ufe0f IT Asset Allocation Confirmed</h1>\n </div>\n <div style=\"background: #f9f9f9; padding: 24px; border-radius: 0 0 8px 8px; border: 1px solid #e0e0e0;\">\n <p>Hi <strong>{{ $json.employee.name }}</strong>,</p>\n <p>Welcome aboard! Your IT equipment has been allocated and will be ready for your first day on <strong>{{ $json.employee.start_date }}</strong>.</p>\n\n <h3 style=\"color: #1a1a2e;\">\ud83d\udce6 Your Allocated Assets</h3>\n <table style=\"width: 100%; border-collapse: collapse; font-size: 14px;\">\n <thead>\n <tr style=\"background: #1a1a2e; color: white;\">\n <th style=\"padding: 10px; text-align: left;\">Asset Type</th>\n <th style=\"padding: 10px; text-align: left;\">Asset ID</th>\n <th style=\"padding: 10px; text-align: left;\">Serial Number</th>\n <th style=\"padding: 10px; text-align: left;\">Model</th>\n </tr>\n </thead>\n <tbody>\n <tr style=\"background: #fff; border-bottom: 1px solid #eee;\">\n <td style=\"padding: 10px;\">\ud83d\udcbb Laptop</td>\n <td style=\"padding: 10px;\">{{ $json.allocation.laptop.asset_id }}</td>\n <td style=\"padding: 10px;\">{{ $json.allocation.laptop.serial_number }}</td>\n <td style=\"padding: 10px;\">{{ $json.allocation.laptop.model }}</td>\n </tr>\n {{ $json.allocation.monitor ? `<tr style=\"background: #f5f5f5; border-bottom: 1px solid #eee;\"><td style=\"padding: 10px;\">\ud83d\udda5\ufe0f Monitor</td><td style=\"padding: 10px;\">${$json.allocation.monitor.asset_id}</td><td style=\"padding: 10px;\">${$json.allocation.monitor.serial_number}</td><td style=\"padding: 10px;\">${$json.allocation.monitor.model}</td></tr>` : '' }}\n </tbody>\n </table>\n\n <h3 style=\"color: #1a1a2e;\">\ud83d\uddb1\ufe0f Peripherals</h3>\n <ul>\n {{ $json.allocation.peripherals.map(p => `<li>${p.asset_type} \u2014 ${p.model} (ID: ${p.asset_id})</li>`).join('') }}\n </ul>\n\n <p>\ud83d\udcc4 Your official asset allocation letter is attached to this email and also available at:</p>\n <p><a href=\"{{ $json.pdf_url }}\" style=\"color: #1a1a2e;\">Download Allocation Letter (PDF)</a></p>\n\n <p>Please acknowledge receipt of your equipment on Day 1 by replying to this email.</p>\n <p>If you have any questions, contact <a href=\"mailto:it@yourcompany.com\">it@yourcompany.com</a>.</p>\n\n <br>\n <p style=\"color: #888; font-size: 12px;\">This allocation was processed automatically by Agent Q \u2014 IT Asset Allocation System<br>Allocation Date: {{ $json.allocation_date }}</p>\n </div>\n</body>\n</html>",
"options": {
"ccList": "={{ $json.employee.manager_email }}"
},
"subject": "=Your IT Equipment Allocation \u2014 Welcome to the Team, {{ $json.employee.name }}!"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "0abdefcf-64dc-4a9d-993b-52650f338c1a",
"name": "\ud83d\udcca Google Sheets \u2014 Log Allocation Record",
"type": "n8n-nodes-base.googleSheets",
"position": [
1888,
928
],
"parameters": {
"columns": {
"value": {},
"schema": [],
"mappingMode": "autoMapInputData",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "IT_Allocations_Log"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_GOOGLE_SHEET_ID"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "705d59f9-ca35-4cf4-a562-fc94423f3688",
"name": "\ud83d\udea8 Gmail \u2014 Alert IT Team on Policy Failure",
"type": "n8n-nodes-base.gmail",
"position": [
208,
1168
],
"parameters": {
"sendTo": "user@example.com",
"message": "=<html><body style=\"font-family: Arial, sans-serif;\">\n<h2 style=\"color: #c0392b;\">\u274c IT Asset Allocation Failed</h2>\n<p><strong>Employee:</strong> {{ $json.employee.name }}<br>\n<strong>Role:</strong> {{ $json.employee.role }}<br>\n<strong>Department:</strong> {{ $json.employee.department }}<br>\n<strong>Start Date:</strong> {{ $json.employee.start_date }}</p>\n<h3>Self-Critique Report:</h3>\n<ul>{{ $json.self_critique.critiques.map(c => `<li>${c}</li>`).join('') }}</ul>\n<p>Please review inventory and manually allocate assets before the employee's start date.</p>\n<p style=\"color:#888; font-size:12px;\">Agent Q \u2014 IT Asset Allocation System</p>\n</body></html>",
"options": {},
"subject": "=\u26a0\ufe0f Agent Q: Allocation Failed for {{ $json.employee.name }} ({{ $json.employee.role }})"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "83ce2eba-1602-4619-9d3f-1c7ced29cb5c",
"name": "Sticky Note \u2014 Phase 1: New Hire Intake",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1472,
816
],
"parameters": {
"color": 3,
"width": 490,
"height": 185,
"content": "## \ud83d\udce5 Phase 1: New Hire Intake\nTriggered when a new employee row is added to Google Sheets.\nParses key details \u2014 name, department, role, and start date \u2014 then determines the IT asset specification profile required for that role (e.g. Developer, Designer, Executive)."
},
"typeVersion": 1
},
{
"id": "0798f68f-d376-4581-bf20-e7b51102975b",
"name": "Sticky Note \u2014 Phase 2: Asset Inventory Fetch",
"type": "n8n-nodes-base.stickyNote",
"position": [
-864,
784
],
"parameters": {
"color": 5,
"width": 450,
"height": 170,
"content": "## \ud83d\uddc4\ufe0f Phase 2: Fetch Available IT Assets\nQueries **Airtable** for all currently available (unallocated) assets.\nReturns laptops, monitors, peripherals, and software licences grouped by category and availability status."
},
"typeVersion": 1
},
{
"id": "e0c58f13-17a0-46da-a3c4-d2d35a50ed2c",
"name": "Sticky Note \u2014 Phase 3: MCTS Planning & Policy Validation",
"type": "n8n-nodes-base.stickyNote",
"position": [
-304,
704
],
"parameters": {
"color": 4,
"width": 490,
"height": 253,
"content": "## \ud83e\udde0 Phase 3: MCTS Planning & Self-Critique\n**MCTS** simulates multiple asset allocation combinations and selects the optimal set for the employee's role spec.\n\n**Self-Critique** then validates the plan against IT policy rules (budget caps, role-based entitlements, stock limits) before approval.\n- \u2705 Approved \u2192 proceed to allocation\n- \u274c Rejected \u2192 alert IT team"
},
"typeVersion": 1
},
{
"id": "9b0f9eae-7fd1-4c9e-9e6d-3678bf7c6a96",
"name": "Sticky Note \u2014 Phase 4: Asset Allocation & Lock",
"type": "n8n-nodes-base.stickyNote",
"position": [
272,
720
],
"parameters": {
"color": 6,
"width": 490,
"height": 175,
"content": "## \ud83d\udd12 Phase 4: Asset Allocation & Lock\nPrepares the list of assets to be assigned, then updates each record in **Airtable** to mark them as allocated to the new employee.\nAggregates all update results and restores the full allocation dataset before moving to document generation."
},
"typeVersion": 1
},
{
"id": "15942cfc-83eb-41de-8b6a-dd460506aa80",
"name": "Sticky Note \u2014 Phase 5: Document Generation & Notification",
"type": "n8n-nodes-base.stickyNote",
"position": [
1120,
640
],
"parameters": {
"color": 7,
"width": 490,
"height": 207,
"content": "## \ud83d\udcc4 Phase 5: Letter Generation & Notifications\n- **CraftMyPDF** generates a personalised IT Asset Allocation Letter as a PDF\n- PDF URL is merged with employee data\n- **Gmail** sends the letter to the employee and their manager\n- **Google Sheets** logs the full allocation record for audit and tracking"
},
"typeVersion": 1
},
{
"id": "acd9b448-cf19-4177-a83e-4ca1de8b5833",
"name": "Sticky Note \u2014 Phase 6: Policy Failure Escalation",
"type": "n8n-nodes-base.stickyNote",
"position": [
368,
1296
],
"parameters": {
"color": 2,
"width": 470,
"height": 202,
"content": "## \ud83d\udea8 Phase 6: Policy Failure \u2014 IT Alert\nIf the Self-Critique step rejects the allocation plan (policy violation or insufficient stock):\n- **Gmail** immediately alerts the IT team with full context\n- Includes employee details, role spec, and the reason for rejection\n- IT team manually reviews and resolves before re-triggering"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "8f35831b-2a4a-4b7f-b6cd-d193c14b36e5",
"connections": {
"\u2753 Allocation Approved?": {
"main": [
[
{
"node": "\u2699\ufe0f Prepare Asset Update Items",
"type": "main",
"index": 0
}
],
[
{
"node": "\ud83d\udea8 Gmail \u2014 Alert IT Team on Policy Failure",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd17 Aggregate All Asset Updates": {
"main": [
[
{
"node": "\ud83d\udce6 Restore Full Allocation Data",
"type": "main",
"index": 0
}
]
]
},
"\u2699\ufe0f Prepare Asset Update Items": {
"main": [
[
{
"node": "\ud83d\udd12 Airtable \u2014 Mark Assets as Allocated",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udce6 Restore Full Allocation Data": {
"main": [
[
{
"node": "\ud83d\udcc4 CraftMyPDF \u2014 Generate Allocation Letter",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd17 Merge PDF URL with Employee Data": {
"main": [
[
{
"node": "\ud83d\udce7 Gmail \u2014 Email Employee & Manager",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udccb Trigger \u2014 New Employee Row Added": {
"main": [
[
{
"node": "\ud83d\udd0d Parse Employee & Determine Role Spec",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udce7 Gmail \u2014 Email Employee & Manager": {
"main": [
[
{
"node": "\ud83d\udcca Google Sheets \u2014 Log Allocation Record",
"type": "main",
"index": 0
}
]
]
},
"\ud83e\udde0 MCTS \u2014 Plan Best Asset Allocation": {
"main": [
[
{
"node": "\u2705 Self-Critique \u2014 Validate Against Policy",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd0d Parse Employee & Determine Role Spec": {
"main": [
[
{
"node": "\ud83d\uddc4\ufe0f Airtable \u2014 Fetch Available Assets",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd12 Airtable \u2014 Mark Assets as Allocated": {
"main": [
[
{
"node": "\ud83d\udd17 Aggregate All Asset Updates",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\uddc4\ufe0f Airtable \u2014 Fetch Available Assets": {
"main": [
[
{
"node": "\ud83e\udde0 MCTS \u2014 Plan Best Asset Allocation",
"type": "main",
"index": 0
}
]
]
},
"\u2705 Self-Critique \u2014 Validate Against Policy": {
"main": [
[
{
"node": "\u2753 Allocation Approved?",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcc4 CraftMyPDF \u2014 Generate Allocation Letter": {
"main": [
[
{
"node": "\ud83d\udd17 Merge PDF URL with Employee Data",
"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.
airtableTokenApigmailOAuth2googleSheetsOAuth2ApigoogleSheetsTriggerOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Automate IT asset allocation for new hires with an intelligent, AI-powered workflow 🤖. This automation reads employee data from Google Sheets, determines role-based requirements, and intelligently assigns the best available assets using smart scoring logic. It validates…
Source: https://n8n.io/workflows/15689/ — original creator credit. Request a take-down →
More Data & Sheets workflows → · Browse all categories →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This template is ideal for solo store owners, eCommerce marketers, automation beginners, or anyone using Shopify and Gmail who wants to recover lost revenue without coding.
This guide will walk you through setting up your n8n workflow. By the end, you'll have a fully automated system for managing your recruitment pipeline.
This workflow starts whenever a new domain is added to a Google Sheet. It cleans the domain, fetches traffic insights from SimilarWeb, extracts the most relevant metrics, and updates the sheet with en
cdp_router. Uses gmailTrigger, telegramTrigger, googleSheets, httpRequest. Event-driven trigger; 53 nodes.
cdp_router. Uses gmailTrigger, telegramTrigger, googleSheets, httpRequest. Event-driven trigger; 53 nodes.