私密圣诞老人分配与Gmail通知系统及验证
中级
这是一个自动化工作流,包含 14 个节点。主要使用 Set, Code, Gmail, ManualTrigger, SplitInBatches 等节点。 私密圣诞老人分配与Gmail通知系统及验证
前置要求
- •Google 账号和 Gmail API 凭证
分类
-
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"meta": {
"instanceId": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "e12ee8c9-9805-473f-bb14-24c6a9067c16",
"name": "运行",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-560,
0
],
"parameters": {},
"typeVersion": 1
},
{
"id": "5a5dd6b4-c144-428d-8884-2e4eac5eaf42",
"name": "发送消息",
"type": "n8n-nodes-base.gmail",
"position": [
848,
96
],
"webhookId": "05f2826d-da1c-4276-b739-9e64cc90916e",
"parameters": {
"sendTo": "={{ $json.emisor }}",
"message": "=<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width\">\n <title>Secret Santa</title>\n <style>\n body { font-family: Georgia, 'Times New Roman', Times, serif; background: #fcf8ee; color: #2c2a28; text-align: center; padding: 40px; }\n .name { font-size: 20px; font-weight: bold; margin-top: 12px; }\n </style>\n </head>\n <body>\n <p>{{ $json.nombreEmisor }} → {{ $json.nombreReceptor }}</p>\n </body>\n</html>\n",
"options": {
"appendAttribution": false
},
"subject": "Secret Santa "
},
"credentials": {
"gmailOAuth2": {
"id": "a8B1Lydmlh203F5C",
"name": "Gmail account"
}
},
"retryOnFail": true,
"typeVersion": 2.1,
"waitBetweenTries": 5000
},
{
"id": "4451fa67-489d-4072-955e-4b36c3402ff7",
"name": "删除消息",
"type": "n8n-nodes-base.gmail",
"position": [
1152,
96
],
"webhookId": "935be7db-8f05-4eea-8cca-15eacb06f7e5",
"parameters": {
"messageId": "={{ $json.id }}",
"operation": "delete"
},
"credentials": {
"gmailOAuth2": {
"id": "a8B1Lydmlh203F5C",
"name": "Gmail account"
}
},
"retryOnFail": true,
"typeVersion": 2.1,
"waitBetweenTries": 5000
},
{
"id": "78fa5c29-dab2-4555-9677-30a99c4732d1",
"name": "随机",
"type": "n8n-nodes-base.code",
"position": [
16,
0
],
"parameters": {
"jsCode": "// Function node en n8n\n// Lee pares nombre->email desde las claves del $json\n// Genera emparejamientos sin repetición y sin autoasignación\n// Salida por item: { emisor, nombreEmisor, receptor, nombreReceptor }\n\nfunction shuffle(arr) {\n const a = arr.slice();\n for (let i = a.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [a[i], a[j]] = [a[j], a[i]];\n }\n return a;\n}\n\nfunction isEmail(s) {\n return /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$/i.test(String(s).trim());\n}\n\n// 1) Construir lista tomando CLAVES como nombres y VALORES como emails\nconst entries = Object.entries($json); // [[nombre, email], ...]\nif (entries.length < 2) {\n throw new Error('Se requieren al menos 2 participantes (claves nombre -> valor email).');\n}\n\nlet participants = [];\nconst emailToName = new Map();\nconst seenEmails = new Set();\n\nfor (const [nameRaw, emailRaw] of entries) {\n const name = String(nameRaw); // usar EXACTAMENTE la clave (sin modificar)\n const email = String(emailRaw).trim();\n\n if (!isEmail(email)) continue; // ignorar valores no-email\n if (seenEmails.has(email.toLowerCase())) continue; // evitar duplicados\n\n seenEmails.add(email.toLowerCase());\n participants.push({ name, email });\n emailToName.set(email.toLowerCase(), name);\n}\n\nif (participants.length < 2) {\n throw new Error('No hay suficientes emails válidos (mínimo 2).');\n}\n\n// 2) Derangement (nadie se asigna a sí mismo)\nconst n = participants.length;\nconst givers = participants.slice();\nlet receivers = shuffle(participants);\n\n// Romper puntos fijos\nfor (let i = 0; i < n; i++) {\n if (givers[i].email === receivers[i].email) {\n const j = (i + 1) % n;\n [receivers[i], receivers[j]] = [receivers[j], receivers[i]];\n }\n}\n\n// Reintentos defensivos\nlet attempts = 0;\nwhile (attempts < 20) {\n let ok = true;\n for (let i = 0; i < n; i++) {\n if (givers[i].email === receivers[i].email) { ok = false; break; }\n }\n if (ok) break;\n\n receivers = shuffle(participants);\n for (let i = 0; i < n; i++) {\n if (givers[i].email === receivers[i].email) {\n const j = (i + 1) % n;\n [receivers[i], receivers[j]] = [receivers[j], receivers[i]];\n }\n }\n attempts++;\n}\n\nfor (let i = 0; i < n; i++) {\n if (givers[i].email === receivers[i].email) {\n throw new Error('No se pudo generar una asignación válida. Intenta de nuevo.');\n }\n}\n\n// 3) Salida EXACTA solicitada\nconst items = givers.map((g, i) => {\n const r = receivers[i];\n return {\n json: {\n emisor: g.email,\n nombreEmisor: g.name, // EXACTAMENTE la clave\n receptor: r.email,\n nombreReceptor: emailToName.get(r.email.toLowerCase()) || r.name\n }\n };\n});\n\nreturn items;\n"
},
"typeVersion": 2
},
{
"id": "1fa91ca3-2c66-4aa4-a9b5-8c492731ea08",
"name": "姓名转数字",
"type": "n8n-nodes-base.code",
"position": [
736,
-160
],
"parameters": {
"jsCode": "// Function node en n8n (procesa múltiples items)\n// Entrada (items): [{json:{info:\"a@x.com envió a b@y.com\"}}, ...]\n// Salida (un solo item): { json: { info: \"1 envió a 2\\n2 envió a 1\\n...\" } }\n\nconst emailRegex = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/gi;\n\nfunction extractTwoEmails(s) {\n if (typeof s !== 'string') return null;\n const found = s.match(emailRegex);\n if (!found || found.length < 2) return null;\n // Tomar los dos primeros emails encontrados\n return [found[0], found[1]];\n}\n\nconst emailToId = new Map();\nlet nextId = 1;\n\nfunction getId(email) {\n const key = String(email).toLowerCase().trim();\n if (!emailToId.has(key)) {\n emailToId.set(key, nextId++);\n }\n return emailToId.get(key);\n}\n\nconst lines = [];\n\nfor (const item of items) {\n const info = item?.json?.info;\n const pair = extractTwoEmails(info);\n if (!pair) continue; // o lanza error si lo prefieres\n const [emisorEmail, receptorEmail] = pair;\n const idEmisor = getId(emisorEmail);\n const idReceptor = getId(receptorEmail);\n lines.push(`${idEmisor} envió a ${idReceptor}`);\n}\n\nif (lines.length === 0) {\n throw new Error('No se encontraron líneas válidas con \"email envió a email\" en los items de entrada.');\n}\n\n// Una sola salida con todas las líneas\nreturn [{ json: { info: lines.join('<br>') } }];\n"
},
"typeVersion": 2
},
{
"id": "dc7a3870-69ea-4f77-9e56-9bed21cda23f",
"name": "谁?",
"type": "n8n-nodes-base.set",
"position": [
1488,
96
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "3bc75295-92ec-4f10-8915-4fd5252c7266",
"name": "info",
"type": "string",
"value": "={{ $('loop mails').item.json.emisor }} envió a {{ $('loop mails').item.json.receptor }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "bfd0a287-6f6f-4726-9346-edb5f8e1f3c9",
"name": "邮箱和姓名",
"type": "n8n-nodes-base.set",
"position": [
-288,
0
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "a021f1e8-66a2-429f-a13e-ed677defd969",
"name": "Jesus",
"type": "string",
"value": "example@gmail.com"
},
{
"id": "0870c028-4134-4fab-89a7-688452652e73",
"name": "John",
"type": "string",
"value": "example2@gmail.com"
},
{
"id": "956714b2-0a95-436a-8fff-518fb61483e0",
"name": "Jan",
"type": "string",
"value": "example3@gmail.com"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "c752fe47-a074-4b56-a797-a1f6c0201f3f",
"name": "循环邮件",
"type": "n8n-nodes-base.splitInBatches",
"notes": "Iterate one by one for each pairing (batch per item).",
"position": [
368,
0
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "c2620662-9491-4bf4-ade1-d25d3588430b",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
-560,
176
],
"parameters": {
"color": 7,
"width": 672,
"height": 288,
"content": "运行(手动触发器)"
},
"typeVersion": 1
},
{
"id": "ba594d5b-d910-4324-a1ee-9afb09261209",
"name": "便签1",
"type": "n8n-nodes-base.stickyNote",
"position": [
848,
320
],
"parameters": {
"color": 7,
"width": 784,
"height": 240,
"content": "发送消息结果"
},
"typeVersion": 1
},
{
"id": "624e6947-b664-4dbc-8f91-3b6200c06e80",
"name": "姓名转数字(代码)",
"type": "n8n-nodes-base.gmail",
"maxTries": 2,
"position": [
1040,
-160
],
"webhookId": "3c1f591b-2e4f-4780-9097-c1484e90221a",
"parameters": {
"sendTo": "youremail@gmail.com",
"message": "=Hello ---,\nAll invisible friends have been completed successfully\nShipping information:<br>\n\n{{ $json.info }}",
"options": {},
"subject": "Amic invisible"
},
"credentials": {
"gmailOAuth2": {
"id": "a8B1Lydmlh203F5C",
"name": "Gmail account"
}
},
"retryOnFail": true,
"typeVersion": 2.1,
"waitBetweenTries": 5000
},
{
"id": "198eb429-74d6-4cac-9c6a-a9489cbb4483",
"name": "便签2",
"type": "n8n-nodes-base.stickyNote",
"position": [
736,
-480
],
"parameters": {
"color": 7,
"width": 400,
"height": 288,
"content": ""
},
"typeVersion": 1
},
{
"id": "c7b4714e-a551-4c41-817b-5c0e96726ad5",
"name": "便签3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1696,
-64
],
"parameters": {
"color": 4,
"width": 672,
"height": 624,
"content": ""
},
"typeVersion": 1
},
{
"id": "5c8ce420-70aa-49e0-a271-e738b347cdf9",
"name": "便签4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-560,
-336
],
"parameters": {
"color": 3,
"width": 672,
"height": 272,
"content": "合并PDF"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Run": {
"main": [
[
{
"node": "Emails and name",
"type": "main",
"index": 0
}
]
]
},
"Who?": {
"main": [
[
{
"node": "loop mails",
"type": "main",
"index": 0
}
]
]
},
"Random": {
"main": [
[
{
"node": "loop mails",
"type": "main",
"index": 0
}
]
]
},
"loop mails": {
"main": [
[
{
"node": "Name to INT",
"type": "main",
"index": 0
}
],
[
{
"node": "Send a message",
"type": "main",
"index": 0
}
]
]
},
"Name to INT": {
"main": [
[
{
"node": "Send a message results",
"type": "main",
"index": 0
}
]
]
},
"Send a message": {
"main": [
[
{
"node": "Delete a message",
"type": "main",
"index": 0
}
]
]
},
"Emails and name": {
"main": [
[
{
"node": "Random",
"type": "main",
"index": 0
}
]
]
},
"Delete a message": {
"main": [
[
{
"node": "Who?",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
中级
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
AI驱动YouTube产品评论自动分析
基于Apify和GPT的AI驱动YouTube产品评论自动分析
Set
Code
Gmail
+10
25 节点Oriol Seguí
市场调研
使用GPT-5 nano和Yoast SEO自动化WordPress SEO优化
使用GPT-5 nano和Yoast SEO自动化WordPress SEO优化
Set
Code
Gmail
+11
35 节点Oriol Seguí
杂项
多URL自动监控与宕机警报
多URL自动监控与宕机警报
Set
Code
Gmail
+8
18 节点Oriol Seguí
人工智能
基于 YouTube 视频的自主博客发布
使用 ChatGPT、Sheets、Apify、Pexels 和 WordPress 从 YouTube 视频自主发布博客
If
Set
Code
+18
80 节点Oriol Seguí
内容创作
使用GPT-4o AI分析和多格式报告运行完整技术SEO审计
使用GPT-4o AI分析和多格式报告运行完整技术SEO审计
Set
Xml
Code
+14
45 节点Oriol Seguí Rotllant
人工智能
使用GPT 5 Nano和Google Sheets的网站SEO常见问题生成器
使用GPT 5 Nano和Google Sheets的网站SEO常见问题生成器
Set
Xml
Code
+11
35 节点Oriol Seguí
内容创作