This workflow corresponds to n8n.io template #9609 — we link there as the canonical source.
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 →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "e12ee8c9-9805-473f-bb14-24c6a9067c16",
"name": "Run",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-560,
0
],
"parameters": {},
"typeVersion": 1
},
{
"id": "5a5dd6b4-c144-428d-8884-2e4eac5eaf42",
"name": "Send a message",
"type": "n8n-nodes-base.gmail",
"position": [
848,
96
],
"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 }} \u2192 {{ $json.nombreReceptor }}</p>\n </body>\n</html>\n",
"options": {
"appendAttribution": false
},
"subject": "Secret Santa "
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 2.1,
"waitBetweenTries": 5000
},
{
"id": "4451fa67-489d-4072-955e-4b36c3402ff7",
"name": "Delete a message",
"type": "n8n-nodes-base.gmail",
"position": [
1152,
96
],
"parameters": {
"messageId": "={{ $json.id }}",
"operation": "delete"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 2.1,
"waitBetweenTries": 5000
},
{
"id": "78fa5c29-dab2-4555-9677-30a99c4732d1",
"name": "Random",
"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\u00f3n y sin autoasignaci\u00f3n\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\u00e1lidos (m\u00ednimo 2).');\n}\n\n// 2) Derangement (nadie se asigna a s\u00ed 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\u00f3n v\u00e1lida. 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": "Name to INT",
"type": "n8n-nodes-base.code",
"position": [
736,
-160
],
"parameters": {
"jsCode": "// Function node en n8n (procesa m\u00faltiples items)\n// Entrada (items): [{json:{info:\"user@example.com envi\u00f3 a user@example.com\"}}, ...]\n// Salida (un solo item): { json: { info: \"1 envi\u00f3 a 2\\n2 envi\u00f3 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\u00f3 a ${idReceptor}`);\n}\n\nif (lines.length === 0) {\n throw new Error('No se encontraron l\u00edneas v\u00e1lidas con \"email envi\u00f3 a email\" en los items de entrada.');\n}\n\n// Una sola salida con todas las l\u00edneas\nreturn [{ json: { info: lines.join('<br>') } }];\n"
},
"typeVersion": 2
},
{
"id": "dc7a3870-69ea-4f77-9e56-9bed21cda23f",
"name": "Who?",
"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\u00f3 a {{ $('loop mails').item.json.receptor }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "bfd0a287-6f6f-4726-9346-edb5f8e1f3c9",
"name": "Emails and name",
"type": "n8n-nodes-base.set",
"position": [
-288,
0
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "a021f1e8-66a2-429f-a13e-ed677defd969",
"name": "Jesus",
"type": "string",
"value": "user@example.com"
},
{
"id": "0870c028-4134-4fab-89a7-688452652e73",
"name": "John",
"type": "string",
"value": "user@example.com"
},
{
"id": "956714b2-0a95-436a-8fff-518fb61483e0",
"name": "Jan",
"type": "string",
"value": "user@example.com"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "c752fe47-a074-4b56-a797-a1f6c0201f3f",
"name": "loop mails",
"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": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-560,
176
],
"parameters": {
"color": 7,
"width": 672,
"height": 288,
"content": "Run (Manual Trigger)\nStarts the workflow manually in n8n. Used only to trigger the execution.\n\nEmails and name (Set)\nDefines the Secret Santa participants as name \u2192 email pairs, for example: {\"Jesus\":\"[example@gmail.com](mailto:example@gmail.com)\", \"John\":\"[example2@gmail.com](mailto:example2@gmail.com)\"}.\n\nRandom (Code)\nReads those names and emails, generates random pairings with no repeats or self-assignments, and outputs items like: {\"emisor\":\"[example@gmail.com](mailto:example@gmail.com)\",\"nombreEmisor\":\"Jesus\",\"receptor\":\"[example2@gmail.com](mailto:example2@gmail.com)\",\"nombreReceptor\":\"John\"}.\n"
},
"typeVersion": 1
},
{
"id": "ba594d5b-d910-4324-a1ee-9afb09261209",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
848,
320
],
"parameters": {
"color": 7,
"width": 784,
"height": 240,
"content": "Send a message (Gmail)\nSends an email via Gmail to $json.emisor for the current item and returns message metadata (including id) for downstream use; retries on failure (5s) using the configured OAuth2 account.\n\nDelete a message (Gmail)\nDeletes the sent message using messageId = {{$json.id}} to keep it out of the Sent folder, ensuring privacy. Uses the same Gmail credentials and retries with a delay between attempts.\n\nWho? (Set)\nCreates a text field \u201cinfo\u201d with the pattern \u201c<emisor> sent to <receptor>\u201d using the current loop item values. Used to log a trace line for each delivery.\n"
},
"typeVersion": 1
},
{
"id": "624e6947-b664-4dbc-8f91-3b6200c06e80",
"name": "Send a message results",
"type": "n8n-nodes-base.gmail",
"maxTries": 2,
"position": [
1040,
-160
],
"parameters": {
"sendTo": "user@example.com",
"message": "=Hello ---,\nAll invisible friends have been completed successfully\nShipping information:<br>\n\n{{ $json.info }}",
"options": {},
"subject": "Amic invisible"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 2.1,
"waitBetweenTries": 5000
},
{
"id": "198eb429-74d6-4cac-9c6a-a9489cbb4483",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
736,
-480
],
"parameters": {
"color": 7,
"width": 400,
"height": 288,
"content": "Name to INT (Code)\nProcesses multiple input items, extracts the two email addresses from each \u201cinfo\u201d line, assigns numeric IDs to each unique address, and builds a compact summary like \u201c1 sent to 2<br>2 sent to 3\u201d. Outputs a single consolidated item with all pairings joined in HTML format.\n\nSend a message results (Gmail)\nSends a final summary email to youemail with the subject \u201cAmic invisible\u201d, containing the aggregated info from the previous node. "
},
"typeVersion": 1
},
{
"id": "c7b4714e-a551-4c41-817b-5c0e96726ad5",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1696,
-64
],
"parameters": {
"color": 4,
"width": 672,
"height": 624,
"content": "\n\n\n\n### **Non-Commercial Use and Modification License**\n\n**Copyright \u00a9 [2025/6] - Oseguir**\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated files, to **use, copy, modify, and redistribute** the software under the following conditions:\n\n1. **Use and modification:**\n You may modify the source code, adapt it, or integrate it into other personal or educational projects.\n\n2. **Distribution:**\n You may share copies or modified versions **only for free**, provided that this license notice remains intact.\n\n3. **Non-commercial restriction:**\n Selling, renting, licensing, sublicensing, or otherwise gaining direct or indirect commercial profit from this software or its derivatives is strictly prohibited.\n\n4. **Attribution:**\n Credit to the original author must be retained in any public redistribution or modification.\n\n5. **No warranty:**\n This software is provided \u201cas is\u201d, without any warranty of any kind, express or implied. The author is not liable for any damages arising from its use.\n\n"
},
"typeVersion": 1
},
{
"id": "5c8ce420-70aa-49e0-a271-e738b347cdf9",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-560,
-336
],
"parameters": {
"color": 3,
"width": 672,
"height": 272,
"content": "\n\n### **Welcome!**\n\nThanks for using this workflow \ud83d\ude4c\nIf you enjoyed it and want to discover **more creative and original automations**, visit my n8n Creator profile:\n\ud83d\udc49 **[https://n8n.io/creators/oxsr11/](https://n8n.io/creators/oxsr11/)**\n\nThere you\u2019ll find more **unique, AI-powered, and real-world inspired workflows** designed to make automation smarter and more fun.\n\n\ud83d\udc49 **[https://github.com/OXSR/](https://github.com/OXSR)**\n"
},
"typeVersion": 1
}
],
"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
}
]
]
}
}
}
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.
gmailOAuth2
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow fully automates a Secret Santa (Amic Invisible) event — perfect for friends, families, or office teams who want to make their gift exchange fun, fair, and completely private.
Source: https://n8n.io/workflows/9609/ — 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.
Loan eligibility workflow. Uses formTrigger, googleSheets, gmail. Event-driven trigger; 53 nodes.
Splitout Code. Uses manualTrigger, httpRequest, stickyNote, splitOut. Event-driven trigger; 46 nodes.
Automate CSV imports into HubSpot without the mess. Powered by n8n. Supercharged by Pollup AI.
Echo Brand Voice Analysis (Processor) - TASK-074 Dec 10 Fix. Uses formTrigger, httpRequest, executeWorkflowTrigger, moveBinaryData. Event-driven trigger; 40 nodes.
Payment Recovery. Uses stripeTrigger, highLevel, dataTable, googleCalendar. Event-driven trigger; 40 nodes.