BGV跟踪器
中级
这是一个HR、Multimodal AI领域的自动化工作流,包含 8 个节点。主要使用 Code、Gmail、GoogleSheets、ScheduleTrigger 等节点。 BGV状态每日摘要:使用Google表格跟踪验证状态并发送Gmail提醒
前置要求
- •Google 账号和 Gmail API 凭证
- •Google Sheets API 凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "RAGbASSkkdJed4ur",
"meta": {
"instanceId": "14e4c77104722ab186539dfea5182e419aecc83d85963fe13f6de862c875ebfa",
"templateCredsSetupCompleted": true
},
"name": "BGV跟踪器",
"tags": [],
"nodes": [
{
"id": "92190c51-3f14-4bfe-aa35-2cbbd7bde78b",
"name": "计划触发器",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
0,
0
],
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"typeVersion": 1.2
},
{
"id": "4e28e4ce-ca96-40a1-b8da-7d2d2cb55d17",
"name": "Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
220,
0
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/132inVcr60cNg5AUY51aKG-4R8UgQ6xlGgLgC3ZwKxuU/edit#gid=0",
"cachedResultName": "BGV Tracker"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "132inVcr60cNg5AUY51aKG-4R8UgQ6xlGgLgC3ZwKxuU",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/132inVcr60cNg5AUY51aKG-4R8UgQ6xlGgLgC3ZwKxuU/edit?usp=drivesdk",
"cachedResultName": "BGV Tracker"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "AuKRQmTf8cbXf3oC",
"name": "Google Sheets account 13"
}
},
"typeVersion": 4.6
},
{
"id": "98b5fc3d-4ea0-4386-8032-568d269b518c",
"name": "Gmail",
"type": "n8n-nodes-base.gmail",
"position": [
1100,
0
],
"webhookId": "df9b384a-84a3-4233-90fa-d8a8581d6f3d",
"parameters": {
"sendTo": "={{ $json.to }}",
"message": "={{ $json.html }}",
"options": {
"appendAttribution": false
},
"subject": "={{ $json.subject }}"
},
"credentials": {
"gmailOAuth2": {
"id": "WZ9I0DxvDMdi0ZtY",
"name": "Gmail account 13"
}
},
"typeVersion": 2.1
},
{
"id": "98b2007a-618d-4af5-89cc-2fb3a704844c",
"name": "标准化和解析",
"type": "n8n-nodes-base.code",
"position": [
440,
0
],
"parameters": {
"jsCode": "// Helper to parse various local date formats to UTC Date object in Asia/Kolkata timezone\n// Supports formats like DD/MM/YYYY, DD-MM-YYYY, MM/DD/YYYY, and ISO strings\nfunction parseDateToIST(dateStr) {\n if (!dateStr) return null;\n\n // Remove extra spaces and check if ISO first\n dateStr = dateStr.trim();\n const isoMatch = dateStr.match(/^\\d{4}-\\d{2}-\\d{2}/);\n if (isoMatch) {\n // ISO string, parse directly\n return new Date(dateStr);\n }\n\n // Handle DD/MM/YYYY or DD-MM-YYYY or MM/DD/YYYY formats by heuristics\n // We assume Indian locale DD/MM/YYYY by default\n // We'll try parsing with a few patterns; fallback to null if fails\n const parts1 = dateStr.split(/[\\/\\-]/);\n if (parts1.length !== 3) return null;\n\n let dd, mm, yyyy;\n // Heuristic to detect if third part is year or not\n if (parts1[2].length === 4) {\n // Probably DD/MM/YYYY or MM/DD/YYYY\n // Let's assume DD/MM/YYYY (Indian standard)\n dd = parseInt(parts1[0], 10);\n mm = parseInt(parts1[1], 10) - 1; // month is zero-based in JS Date\n yyyy = parseInt(parts1[2], 10);\n } else {\n return null;\n }\n\n // Build Date in IST timezone by creating UTC equivalent adjusted for IST offset\n // IST is UTC+05:30, so build Date in UTC and add offset\n // To avoid confusion, create Date in UTC and adjust by offset\n const date = new Date(Date.UTC(yyyy, mm, dd));\n return date;\n}\n\n// Get today's date in IST (without time, for comparison)\nfunction getTodayIST() {\n const nowUTC = new Date();\n // IST offset in minutes\n const offsetMinutes = 5 * 60 + 30;\n // convert to IST\n const nowIST = new Date(nowUTC.getTime() + offsetMinutes * 60 * 1000);\n // Zero out time fields for date-only comparison\n return new Date(nowIST.getFullYear(), nowIST.getMonth(), nowIST.getDate());\n}\n\n// Calculate difference in full days between two dates\nfunction daysBetween(date1, date2) {\n if (!(date1 instanceof Date) || !(date2 instanceof Date)) return null;\n const msPerDay = 24 * 60 * 60 * 1000;\n const utc1 = Date.UTC(date1.getFullYear(), date1.getMonth(), date1.getDate());\n const utc2 = Date.UTC(date2.getFullYear(), date2.getMonth(), date2.getDate());\n return Math.floor((utc2 - utc1) / msPerDay);\n}\n\nconst todayIST = getTodayIST();\nconst staleThresholdDays = 3; // Adjust as per config if needed\n\nreturn items.map(item => {\n // Normalize keys to lowercase for consistency:\n const normalized = {};\n for (const key in item.json) {\n normalized[key.toLowerCase()] = item.json[key];\n }\n\n // Parse dates\n const completionDateRaw = normalized['bgv_completion_date'];\n const lastFollowUpRaw = normalized['last_follow_up'];\n\n const completionDate = parseDateToIST(completionDateRaw);\n const lastFollowUpDate = parseDateToIST(lastFollowUpRaw);\n\n // Determine if Completed Today (completed and completionDate == today IST)\n const isCompleted = normalized['bgv_status'] === 'Completed';\n const isCompletedToday = isCompleted && completionDate &&\n daysBetween(completionDate, todayIST) === 0;\n\n // Calculate days since last follow up (null if no date)\n const daysSinceFollowUp = lastFollowUpDate ? daysBetween(lastFollowUpDate, todayIST) : null;\n\n // Is stale if last follow up was >= staleThresholdDays ago (and status Pending)\n const isPending = normalized['bgv_status'] === 'Pending';\n const isStale = isPending && daysSinceFollowUp !== null && daysSinceFollowUp >= staleThresholdDays;\n\n return {\n json: {\n ...normalized,\n bgv_completion_date_parsed: completionDate ? completionDate.toISOString().slice(0,10) : null,\n last_follow_up_parsed: lastFollowUpDate ? lastFollowUpDate.toISOString().slice(0,10) : null,\n isCompletedToday,\n daysSinceFollowUp,\n isStale,\n }\n };\n});\n"
},
"typeVersion": 2
},
{
"id": "08b5c0e9-212d-483c-abe7-08aef59c62bc",
"name": "分组和筛选",
"type": "n8n-nodes-base.code",
"position": [
660,
0
],
"parameters": {
"jsCode": "// Input items assumed to have these properties already calculated in the previous node:\n// - bgv_exe_email (string)\n// - isCompletedToday (boolean)\n// - bgv_status (string)\n// - isStale (boolean)\n\n// Create a map keyed by bgv_exe_email\nconst grouped = {};\n\n// Iterate over each item from previous node\nitems.forEach(item => {\n const json = item.json;\n const exeEmail = json['bgv_exe_email'];\n\n if (!exeEmail) {\n // Skip rows without executive email (or could collect in a \"no-exe\" group if desired)\n return;\n }\n\n if (!grouped[exeEmail]) {\n grouped[exeEmail] = {\n bgv_exe_email: exeEmail,\n completedToday: [],\n pending: []\n };\n }\n\n // Check conditions\n if (json.isCompletedToday) {\n grouped[exeEmail].completedToday.push(json);\n } \n else if (json.bgv_status === 'Pending' && json.bgv_status !== 'To be Sent') {\n grouped[exeEmail].pending.push(json);\n }\n});\n\n// For each group, add the stale flags (already on rows), so no need to add extra here\n\n// Prepare output array, one item per executive with grouped data\nconst output = Object.values(grouped).map(group => {\n return {\n json: group\n };\n});\n\nreturn output;\n"
},
"typeVersion": 2
},
{
"id": "454cc004-085c-4805-9276-3c4b3bb20d58",
"name": "格式化摘要",
"type": "n8n-nodes-base.code",
"position": [
880,
0
],
"parameters": {
"jsCode": "// Helper: Escape HTML to avoid injection issues\nfunction escapeHtml(text) {\n if (!text) return '';\n return text.toString()\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n\n// Template configuration (can be replaced with variables or data from config node)\nconst subjectTemplate = (date, completedCount, pendingCount) =>\n `BGV digest for ${date} — ${completedCount} completed, ${pendingCount} pending`;\n\n// For date display in subject and intro - IST date as YYYY-MM-DD\nconst todayIST = new Date();\ntodayIST.setHours(todayIST.getHours() + 5);\ntodayIST.setMinutes(todayIST.getMinutes() + 30);\nconst yyyy = todayIST.getFullYear();\nconst mm = String(todayIST.getMonth() + 1).padStart(2, '0');\nconst dd = String(todayIST.getDate()).padStart(2, '0');\nconst todayStr = `${yyyy}-${mm}-${dd}`;\n\n// Function to build a HTML table for a list of candidates\nfunction buildTable(rows, includeStale=false) {\n if (!rows || rows.length === 0) {\n return '<p><i>None</i></p>';\n }\n let html = '<table border=\"1\" cellpadding=\"5\" cellspacing=\"0\" style=\"border-collapse: collapse;\">';\n html += `\n <thead>\n <tr>\n <th>Candidate Name</th>\n <th>Previous Company</th>\n <th>Previous HR</th>\n <th>Previous HR Email</th>\n ${includeStale ? '<th>Last Follow-up</th><th>Status</th>' : ''}\n </tr>\n </thead>\n <tbody>\n `;\n\n rows.forEach(row => {\n html += '<tr>';\n html += `<td>${escapeHtml(row.candidate_name)}</td>`;\n html += `<td>${escapeHtml(row.previous_company)}</td>`;\n html += `<td>${escapeHtml(row.prevco_hr_name)}</td>`;\n html += `<td><a href=\"mailto:${escapeHtml(row.prevco_hr_email)}\">${escapeHtml(row.prevco_hr_email)}</a></td>`;\n if (includeStale) {\n const lastFollowUpDisplay = row.last_follow_up_parsed || '';\n const statusDisplay = escapeHtml(row.bgv_status) + (row.isStale ? ' ⚠️' : '');\n html += `<td>${lastFollowUpDisplay}</td>`;\n html += `<td>${statusDisplay}</td>`;\n }\n html += '</tr>';\n });\n\n html += '</tbody></table>';\n return html;\n}\n\n// Array to hold output per executive for SMTP node\nreturn items.map(item => {\n const execEmail = item.json.bgv_exe_email;\n const completed = item.json.completedToday || [];\n const pending = item.json.pending || [];\n\n const completedCount = completed.length;\n const pendingCount = pending.length;\n\n // Compose email subject\n const subject = subjectTemplate(todayStr, completedCount, pendingCount);\n\n // Compose the HTML body content\n const intro = `<p>Dear Executive,</p><p>Here is your daily background verification digest for <b>${todayStr}</b>.</p>`;\n\n const completedSection = `\n <h2>Completed Today (${completedCount})</h2>\n ${buildTable(completed, false)}\n `;\n\n const pendingSection = `\n <h2>Pending (${pendingCount})</h2>\n ${buildTable(pending, true)}\n <p><small>⚠️ indicates stale pending items (no follow-up in last 3 days)</small></p>\n `;\n\n const outro = `<p>Regards,<br/>BGV Operations Team</p>`;\n\n const htmlBody = `\n <html>\n <body>\n ${intro}\n ${completedSection}\n ${pendingSection}\n ${outro}\n </body>\n </html>\n `;\n\n return {\n json: {\n to: execEmail,\n subject: subject,\n html: htmlBody\n }\n };\n});\n"
},
"typeVersion": 2
},
{
"id": "1a75d763-8f88-4e2e-819e-f9a6ec8ccc87",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
-40,
-120
],
"parameters": {
"width": 1340,
"height": 360,
"content": "##BGV高管摘要自动化:通过电子邮件跟踪已完成和待处理的验证(来自Google Sheets)"
},
"typeVersion": 1
},
{
"id": "6da3d9e8-d1d4-4c49-8d26-7e0b1bf6f03c",
"name": "便签1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-40,
280
],
"parameters": {
"width": 1340,
"height": 1080,
"content": "### **节点列表和描述**"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "139bdfc3-3aa7-4b5b-b0a7-4ce504bbec00",
"connections": {
"Format Digest": {
"main": [
[
{
"node": "Gmail",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets": {
"main": [
[
{
"node": "Normalize & Parse",
"type": "main",
"index": 0
}
]
]
},
"Group & Filter": {
"main": [
[
{
"node": "Format Digest",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"Normalize & Parse": {
"main": [
[
{
"node": "Group & Filter",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
这是一个中级难度的工作流,适用于HR、Multimodal AI等场景。适合有一定经验的用户,包含 6-15 个节点的中等复杂度工作流
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
使用 HTTP Last-Modified 检查从 Google Sheets 获取职位发布过期和刷新提醒
通过 Google Sheets、HTTP 检查和 Gmail 实现职位发布过期提醒的自动化
If
Set
Code
+6
19 节点WeblineIndia
HR
从Google Sheets向Slack发送面试后反馈提醒(含邮件备用方案)
使用Google Sheets、Slack和Gmail自动化面试后反馈提醒
If
Gmail
Slack
+3
9 节点WeblineIndia
HR
使用Google Sheets和Gmail在每日结束时自动发送招聘拒绝邮件
在每日结束时自动发送招聘拒绝邮件(Google Sheets | Gmail)
If
Set
Code
+4
15 节点WeblineIndia
HR
太阳能发电监测预警工作流
监控太阳能发电并通过Gmail、Google表格和Slack发送警报
If
Code
Gmail
+5
9 节点WeblineIndia
Engineering
维护计划程序-通过ICS邮件
使用Google Sheets和Gmail ICS邀请发送预防性维护计划
Code
Gmail
Google Sheets
+3
7 节点WeblineIndia
Project Management
客户反馈循环分析器
使用AI、Google表格和Slack提醒自动分类客户反馈
Code
Gmail
Slack
+7
11 节点WeblineIndia
Miscellaneous
工作流信息
难度等级
中级
节点数量8
分类2
节点类型5
作者
WeblineIndia
@weblineindiaA Leading Software Engineering, Consulting & Outsourcing Services Company in USA & India serving Clients Globally since 1999.
外部链接
在 n8n.io 上查看 →
分享此工作流