{"workflow":{"id":13555,"name":"Gamify Keephub form response times and email a ranked leaderboard via Gmail","views":2,"recentViews":0,"totalViews":2,"createdAt":"2026-02-20T17:48:07.831Z","description":"## Who is this for\n\nHR teams, internal comms managers, and operations leads using Keephub who want to turn form completions into a friendly competition and drive faster engagement across the organisation.\n\n## What it does\n\nEnter a Keephub Form ID — and optionally a competition start time — into a simple web form. The workflow will:\n\n📋 1. Fetch every submission for that form from the Keephub API.\n⏱️ 2. Calculate how quickly each person responded (from your custom start time, or from form creation if left blank).\n👤 3. Enrich each result with the submitter's name and org-unit via the Keephub orgchart.\n📬  4. Build a polished HTML leaderboard — complete with KPI cards, bar charts, and an org-unit breakdown — and email it to you.\n\n## How to set up\n\n📦 1. Install the **n8n-nodes-keephub** community node from Settings → Community nodes.\n🔑 2. Add your **Keephub Bearer token** or **Keephub Login** credential in any Keephub node.\n📧 3. Connect a Gmail (or SMTP) credential to the email node.\n▶️ 4. Activate the workflow and open the generated form URL.\n\n## Requirements\n\n- A Keephub account with API access.\n- A Gmail or SMTP email credential that will be used to send emails\n\n## How to customize\n\n- Replace Gmail with Outlook, SendGrid, or any email node.\n- Edit the Code node's HTML to match your company branding.\n- Swap the Form Trigger for a Cron trigger to run the competition on a schedule.\n\n---\n```⚠️ Requires n8n-nodes-keephub v1.5 or later. Update the community node before running.```","workflow":{"meta":{"instanceId":"","templateCredsSetupCompleted":true},"nodes":[{"id":"63171447-5c6f-4608-b65d-1a987271af89","name":"📧 Send Competition Report","type":"n8n-nodes-base.gmail","position":[2416,1296],"webhookId":"79b6be18-0d61-4f92-8cbc-9020f3a3b3ad","parameters":{"sendTo":"={{ $('🚀 Start here!').item.json['Your email address please'] }}","message":"={{ $json.html }}","options":{},"subject":"={{ $json.subject }}"},"typeVersion":2.1},{"id":"293d5e4a-abb4-4657-8291-62def169bdd0","name":"Get Orgchart Info","type":"n8n-nodes-keephub.keephub","onError":"continueRegularOutput","position":[1744,1296],"parameters":{"nodeId":"={{ $json.orgunits[0] }}","resource":"orgchart"},"credentials":{"keephubBearerApi":{"id":"","name":"Keephub Bearer account"}},"typeVersion":1},{"id":"7da3b2b3-4ed6-45fc-b449-f89db55f6255","name":"Find form submissions by form","type":"n8n-nodes-keephub.keephub","position":[848,1296],"parameters":{"options":{},"resource":"formSubmission","operation":"findByForm","contentRef":"={{ $json['Form ID (copy from the form URL)'] }}","authentication":"loginCredentials"},"credentials":{"keephubLoginApi":{"id":"","name":"Keephub Login account"}},"typeVersion":1},{"id":"dffed0d2-c867-47e4-b485-7ad7c90cdf99","name":"📊 Aggregate Stats & Build Report","type":"n8n-nodes-base.code","position":[2192,1296],"parameters":{"jsCode":"const allItems = $input.all();\n\n// Guard: no submissions — return a helpful empty report\nif (!allItems.length) {\n  const d = new Date().toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' });\n  return [{ json: { html: '<p>No submissions found for this form. Verify the Form ID and ensure the form has at least one response.</p>', subject: '📊 Competition Report — ' + d + ' — No submissions found' } }];\n}\n\nconst safeNum = v => (v == null || isNaN(Number(v))) ? null : Number(v);\n\nfunction formatDuration(s) {\n  if (s == null) return '—';\n  s = Math.round(Number(s));\n  const d = Math.floor(s / 86400);\n  const h = Math.floor((s % 86400) / 3600);\n  const m = Math.floor((s % 3600) / 60);\n  const sc = s % 60;\n  if (d > 0) return `${d}d ${h}h ${m}m`;\n  if (h > 0) return `${h}h ${m}m ${sc}s`;\n  if (m > 0) return `${m}m ${sc}s`;\n  return `${sc}s`;\n}\n\nfunction esc(str) {\n  return String(str ?? '')\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;');\n}\n\nfunction fmtDate(dt) {\n  if (!dt) return '—';\n  const d = new Date(dt);\n  if (isNaN(d.getTime())) return '—';\n  return d.toLocaleString('en-GB', {\n    day: '2-digit', month: 'short', year: 'numeric',\n    hour: '2-digit', minute: '2-digit',\n  });\n}\n\nfunction median(nums) {\n  const a = nums.filter(n => n != null).map(n => Number(n)).sort((x, y) => x - y);\n  if (!a.length) return null;\n  const mid = Math.floor(a.length / 2);\n  return a.length % 2 ? a[mid] : Math.round((a[mid - 1] + a[mid]) / 2);\n}\n\nfunction avg(nums) {\n  const a = nums.filter(n => n != null).map(n => Number(n));\n  if (!a.length) return null;\n  return Math.round(a.reduce((s, v) => s + v, 0) / a.length);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Normalize submissions\n// userOrgunit is already a resolved name from 🔗 Enrich with User Data\n// ─────────────────────────────────────────────────────────────────────────────\nconst submissions = allItems.map(i => {\n  const j = i.json || {};\n  const seconds = safeNum(j.durationSeconds);\n  return {\n    submissionId: j.submissionId || j._id || null,\n    userName: j.userName || 'Unknown',\n    userEmail: j.userEmail || '',\n    userOrgunit: j.userOrgunit || 'Unknown',\n    submittedAt: j.submittedAt || null,\n    seconds,\n    durationFormatted: j.durationFormatted || formatDuration(seconds),\n  };\n});\n\nconst totalSubs = submissions.length;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Aggregate per participant\n// ─────────────────────────────────────────────────────────────────────────────\nconst participantKey = s =>\n  (s.userEmail ? s.userEmail.toLowerCase() : `${s.userName}::${s.userOrgunit}`).toLowerCase();\n\nconst pMap = new Map();\nfor (const s of submissions) {\n  const key = participantKey(s);\n  if (!pMap.has(key)) {\n    pMap.set(key, {\n      key,\n      name: s.userName,\n      email: s.userEmail,\n      orgunit: s.userOrgunit,\n      submissionsCount: 0,\n      bestSeconds: null,\n      bestSubmittedAt: null,\n      _sum: 0,\n      _count: 0,\n    });\n  }\n  const p = pMap.get(key);\n  p.submissionsCount++;\n  if (s.seconds != null) {\n    p._sum += s.seconds;\n    p._count += 1;\n    if (p.bestSeconds == null || s.seconds < p.bestSeconds) {\n      p.bestSeconds = s.seconds;\n      p.bestSubmittedAt = s.submittedAt;\n    }\n  }\n  p.name = s.userName || p.name;\n  p.orgunit = s.userOrgunit || p.orgunit;\n  p.email = s.userEmail || p.email;\n}\n\nconst participants = Array.from(pMap.values()).map(p => ({\n  ...p,\n  avgSeconds: p._count ? Math.round(p._sum / p._count) : null,\n  bestFormatted: formatDuration(p.bestSeconds),\n}));\n\nparticipants.sort((a, b) => {\n  if (a.bestSeconds == null && b.bestSeconds == null) return 0;\n  if (a.bestSeconds == null) return 1;\n  if (b.bestSeconds == null) return -1;\n  return a.bestSeconds - b.bestSeconds;\n});\n\nconst participantsCount = participants.length;\nconst fastest = participants[0] || null;\nconst slowest = participants[participants.length - 1] || null;\nconst bestTimes = participants.map(p => p.bestSeconds).filter(v => v != null);\nconst avgBestSeconds = avg(bestTimes);\nconst medianBestSeconds = median(bestTimes);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Org unit aggregation\n// ─────────────────────────────────────────────────────────────────────────────\nconst ouMap = new Map();\nfor (const p of participants) {\n  if (p.bestSeconds == null) continue;\n  const ou = p.orgunit || 'Unknown';\n  if (!ouMap.has(ou)) ouMap.set(ou, { name: ou, total: 0, count: 0 });\n  const d = ouMap.get(ou);\n  d.total += p.bestSeconds;\n  d.count += 1;\n}\n\nconst orgunits = Array.from(ouMap.values())\n  .map(x => ({\n    name: x.name,\n    avgSeconds: x.count ? Math.round(x.total / x.count) : null,\n    avgFormatted: formatDuration(x.count ? Math.round(x.total / x.count) : null),\n    count: x.count,\n  }))\n  .sort((a, b) => (a.avgSeconds ?? 9e15) - (b.avgSeconds ?? 9e15))\n  .slice(0, 15);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Bar chart helpers\n// ─────────────────────────────────────────────────────────────────────────────\nfunction barRow({ left, right, pct, color, muted, emphasis }) {\n  const leftTxt = esc(left || '—');\n  const rightTxt = esc(right || '—');\n  const labelColor = muted ? '#6b7280' : '#111827';\n  const rightColor = emphasis ? '#4f46e5' : '#6b7280';\n  const weight = emphasis ? '800' : '600';\n  const bgFallback = color.includes('linear-gradient') ? 'rgba(99,102,241,0.85)' : color;\n  return `\n    <tr>\n      <td align=\"right\" valign=\"middle\" style=\"padding:6px 12px 6px 0;white-space:nowrap;color:${labelColor};font-size:13px;font-weight:${weight};width:180px\">${leftTxt}</td>\n      <td valign=\"middle\" style=\"padding:6px 0;width:100%\">\n        <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"border-collapse:separate\">\n          <tr>\n            <td style=\"background:#eef2f7;border-radius:999px;height:12px;line-height:12px;font-size:0;overflow:hidden\">\n              <div style=\"display:block;background-color:${bgFallback};background:${color};height:12px;width:${pct}%;min-width:2px;border-radius:999px\"></div>\n            </td>\n          </tr>\n        </table>\n      </td>\n      <td align=\"right\" valign=\"middle\" style=\"padding:6px 0 6px 12px;white-space:nowrap;color:${rightColor};font-size:13px;font-weight:800;width:80px\">${rightTxt}</td>\n    </tr>\n  `;\n}\n\nfunction buildParticipantsBarChart(rows) {\n  const max = Math.max(...rows.map(r => r.bestSeconds || 0), 1);\n  const body = rows.map((p, i) => barRow({\n    left: p.name.length > 22 ? p.name.slice(0, 22) + '…' : p.name,\n    right: p.bestFormatted,\n    pct: Math.round(((p.bestSeconds || 0) / max) * 100),\n    color: i < 3\n      ? 'linear-gradient(90deg, rgba(99,102,241,0.95), rgba(139,92,246,0.95))'\n      : 'rgba(99,102,241,0.75)',\n    muted: false,\n    emphasis: i < 3,\n  })).join('');\n  return `<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"border-collapse:collapse\">${body}</table>`;\n}\n\nfunction buildOrgunitBarChart(rows) {\n  const max = Math.max(...rows.map(r => r.avgSeconds || 0), 1);\n  const body = rows.map((ou, i) => barRow({\n    left: ou.name.length > 22 ? ou.name.slice(0, 22) + '…' : ou.name,\n    right: `${ou.avgFormatted} · ${ou.count}`,\n    pct: Math.round(((ou.avgSeconds || 0) / max) * 100),\n    color: i === 0\n      ? 'linear-gradient(90deg, rgba(16,185,129,0.95), rgba(52,211,153,0.95))'\n      : 'rgba(16,185,129,0.75)',\n    muted: true,\n    emphasis: i === 0,\n  })).join('');\n  return `<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"border-collapse:collapse\">${body}</table>`;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Leaderboard rows\n// ─────────────────────────────────────────────────────────────────────────────\nconst medals = ['🥇', '🥈', '🥉'];\nconst top10 = participants.slice(0, 10);\n\nconst leaderboardRows = top10.map((p, idx) => {\n  const bg = idx % 2 ? '#f9fafb' : '#ffffff';\n  const medal = idx < 3 ? medals[idx] : String(idx + 1);\n  return `\n    <tr style=\"background:${bg}\">\n      <td align=\"center\" valign=\"middle\" style=\"padding:10px 12px;color:#6b7280;font-size:13px;font-weight:800;width:40px\">${medal}</td>\n      <td valign=\"middle\" style=\"padding:10px 12px;color:#111827;font-size:14px;font-weight:700\">${esc(p.name)}</td>\n      <td valign=\"middle\" style=\"padding:10px 12px;color:#6b7280;font-size:13px\">${esc(p.orgunit)}</td>\n      <td align=\"right\" valign=\"middle\" style=\"padding:10px 12px\">\n        <span style=\"display:inline-block;background:#eef2ff;color:#4f46e5;font-weight:800;border-radius:999px;padding:4px 10px;font-size:13px;white-space:nowrap\">${esc(p.bestFormatted)}</span>\n      </td>\n      <td align=\"right\" valign=\"middle\" style=\"padding:10px 12px;color:#9ca3af;font-size:12px;white-space:nowrap\">${fmtDate(p.bestSubmittedAt)}</td>\n      <td align=\"right\" valign=\"middle\" style=\"padding:10px 12px;color:#9ca3af;font-size:12px;white-space:nowrap\">${p.submissionsCount}</td>\n    </tr>\n  `;\n}).join('');\n\n// ─────────────────────────────────────────────────────────────────────────────\n// HTML\n// ─────────────────────────────────────────────────────────────────────────────\n// Reference time label for the report header\nconst formData = $('🚀 Start here!').first().json;\nconst dateRaw = formData['Competition start date (optional)'] || '';\nconst timeRaw = formData['Competition start time (optional)'] || '';\nconst customStartRaw = dateRaw ? (dateRaw + (timeRaw ? 'T' + timeRaw : 'T00:00')) : '';\nconst customStartDt = customStartRaw ? new Date(customStartRaw) : null;\nconst refTimeLabel = (customStartDt && !isNaN(customStartDt.getTime()))\n  ? 'from ' + customStartDt.toLocaleString('en-GB', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' })\n  : 'from form creation (Keephub default)';\n\nconst weekStr = new Date().toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' });\nconst fastestNamePlain = fastest?.name || '—';\nconst fastestTimePlain = fastest?.bestSeconds != null ? formatDuration(fastest.bestSeconds) : '—';\nconst safeFastestNameHtml = esc(fastestNamePlain);\nconst safeFastestTimeHtml = esc(fastestTimePlain);\nconst kpiMinHeight = 138;\nconst kpiBottomSpacer = 18;\n\nconst html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <meta name=\"color-scheme\" content=\"light dark\">\n  <meta name=\"supported-color-schemes\" content=\"light dark\">\n  <title>Form Response Competition</title>\n</head>\n<body style=\"margin:0;padding:0;background:#f3f4f6;\">\n  <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"background:#f3f4f6;border-collapse:collapse\">\n    <tr>\n      <td align=\"center\" valign=\"top\" style=\"padding:24px 12px;\">\n        <table role=\"presentation\" width=\"700\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"width:100%;max-width:700px;border-collapse:collapse\">\n\n          <!-- Header -->\n          <tr>\n            <td style=\"padding:0;\">\n              <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"border-collapse:separate;background:linear-gradient(135deg,#4f46e5 0%,#7c3aed 50%,#9333ea 100%);border-radius:16px;\">\n                <tr>\n                  <td align=\"center\" style=\"padding:34px 24px;\">\n                    <div style=\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:rgba(255,255,255,0.8);font-size:12px;letter-spacing:2px;text-transform:uppercase;font-weight:700;margin-bottom:10px;\">Competition Report</div>\n                    <div style=\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#ffffff;font-size:28px;font-weight:900;letter-spacing:-0.5px;line-height:1.15;\">Form Response Competition</div>\n                    <div style=\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:rgba(255,255,255,0.8);font-size:14px;font-weight:600;margin-top:10px;\">${esc(weekStr)}</div>\n                    <div style=\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:rgba(255,255,255,0.6);font-size:12px;font-weight:400;margin-top:6px;\">Response times measured ${refTimeLabel}</div>\n                  </td>\n                </tr>\n              </table>\n            </td>\n          </tr>\n\n          <tr><td style=\"height:16px;line-height:16px;font-size:0;\">&nbsp;</td></tr>\n\n          <!-- KPI Cards -->\n          <tr>\n            <td>\n              <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"border-collapse:separate\">\n                <tr>\n                  <td valign=\"top\" width=\"33.33%\" style=\"padding:0 6px 0 0;\">\n                    <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"background:#ffffff;border-radius:14px;border-top:4px solid #f59e0b;box-shadow:0 2px 10px rgba(17,24,39,0.06);\">\n                      <tr>\n                        <td style=\"padding:18px 16px;height:${kpiMinHeight}px;vertical-align:top;\">\n                          <div style=\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#9ca3af;font-size:10px;letter-spacing:1.6px;text-transform:uppercase;font-weight:800;\">Fastest</div>\n                          <div style=\"height:6px;line-height:6px;font-size:0;\">&nbsp;</div>\n                          <div style=\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#111827;font-size:16px;font-weight:900;line-height:1.2;\">${safeFastestNameHtml}</div>\n                          <div style=\"height:6px;line-height:6px;font-size:0;\">&nbsp;</div>\n                          <div style=\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#f59e0b;font-size:26px;font-weight:900;line-height:1;\">${safeFastestTimeHtml}</div>\n                          <div style=\"margin-top:6px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#9ca3af;font-size:11px;\">Best time</div>\n                          <div style=\"height:${kpiBottomSpacer}px;line-height:${kpiBottomSpacer}px;font-size:0;\">&nbsp;</div>\n                        </td>\n                      </tr>\n                    </table>\n                  </td>\n                  <td valign=\"top\" width=\"33.33%\" style=\"padding:0 3px;\">\n                    <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"background:#ffffff;border-radius:14px;border-top:4px solid #10b981;box-shadow:0 2px 10px rgba(17,24,39,0.06);\">\n                      <tr>\n                        <td style=\"padding:18px 16px;height:${kpiMinHeight}px;vertical-align:top;\">\n                          <div style=\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#9ca3af;font-size:10px;letter-spacing:1.6px;text-transform:uppercase;font-weight:800;\">Participants</div>\n                          <div style=\"height:6px;line-height:6px;font-size:0;\">&nbsp;</div>\n                          <div style=\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#111827;font-size:34px;font-weight:900;line-height:1;\">${participantsCount}</div>\n                          <div style=\"margin-top:6px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#6b7280;font-size:12px;\">from ${orgunits.length || 1} org unit${(orgunits.length || 1) === 1 ? '' : 's'}</div>\n                          <div style=\"margin-top:6px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#9ca3af;font-size:11px;\">${totalSubs} total submission${totalSubs === 1 ? '' : 's'}</div>\n                          <div style=\"height:${kpiBottomSpacer}px;line-height:${kpiBottomSpacer}px;font-size:0;\">&nbsp;</div>\n                        </td>\n                      </tr>\n                    </table>\n                  </td>\n                  <td valign=\"top\" width=\"33.33%\" style=\"padding:0 0 0 6px;\">\n                    <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"background:#ffffff;border-radius:14px;border-top:4px solid #6366f1;box-shadow:0 2px 10px rgba(17,24,39,0.06);\">\n                      <tr>\n                        <td style=\"padding:18px 16px;height:${kpiMinHeight}px;vertical-align:top;\">\n                          <div style=\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#9ca3af;font-size:10px;letter-spacing:1.6px;text-transform:uppercase;font-weight:800;\">Typical best time</div>\n                          <div style=\"height:6px;line-height:6px;font-size:0;\">&nbsp;</div>\n                          <div style=\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#111827;font-size:26px;font-weight:900;line-height:1;\">${esc(formatDuration(avgBestSeconds))}</div>\n                          <div style=\"margin-top:6px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#9ca3af;font-size:11px;\">Median: <span style=\"color:#6366f1;font-weight:900;\">${esc(formatDuration(medianBestSeconds))}</span></div>\n                          <div style=\"margin-top:2px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#9ca3af;font-size:11px;\">Slowest: <span style=\"color:#ef4444;font-weight:900;\">${esc(formatDuration(slowest?.bestSeconds))}</span></div>\n                          <div style=\"height:${kpiBottomSpacer}px;line-height:${kpiBottomSpacer}px;font-size:0;\">&nbsp;</div>\n                        </td>\n                      </tr>\n                    </table>\n                  </td>\n                </tr>\n              </table>\n            </td>\n          </tr>\n\n          <tr><td style=\"height:18px;line-height:18px;font-size:0;\">&nbsp;</td></tr>\n\n          <!-- Top performers -->\n          <tr>\n            <td>\n              <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"background:#ffffff;border-radius:14px;overflow:hidden;box-shadow:0 2px 10px rgba(17,24,39,0.06);\">\n                <tr>\n                  <td style=\"padding:20px 20px 10px;\">\n                    <div style=\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#111827;font-size:16px;font-weight:900;\">Top performers</div>\n                    <div style=\"margin-top:4px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#9ca3af;font-size:13px;\">Ranked by each participant's best response time</div>\n                  </td>\n                </tr>\n                <tr>\n                  <td style=\"padding:6px 20px 14px;\">${buildParticipantsBarChart(top10)}</td>\n                </tr>\n                <tr>\n                  <td style=\"padding:0;\">\n                    <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"border-collapse:collapse\">\n                      <tr style=\"background:#f9fafb;border-top:1px solid #e5e7eb;border-bottom:1px solid #e5e7eb\">\n                        <td style=\"padding:10px 12px;color:#6b7280;font-size:11px;font-weight:900;text-transform:uppercase;letter-spacing:1px;\" align=\"center\">#</td>\n                        <td style=\"padding:10px 12px;color:#6b7280;font-size:11px;font-weight:900;text-transform:uppercase;letter-spacing:1px;\">Participant</td>\n                        <td style=\"padding:10px 12px;color:#6b7280;font-size:11px;font-weight:900;text-transform:uppercase;letter-spacing:1px;\">Org unit</td>\n                        <td style=\"padding:10px 12px;color:#6b7280;font-size:11px;font-weight:900;text-transform:uppercase;letter-spacing:1px;\" align=\"right\">Best</td>\n                        <td style=\"padding:10px 12px;color:#6b7280;font-size:11px;font-weight:900;text-transform:uppercase;letter-spacing:1px;\" align=\"right\">Submitted</td>\n                        <td style=\"padding:10px 12px;color:#6b7280;font-size:11px;font-weight:900;text-transform:uppercase;letter-spacing:1px;\" align=\"right\">Subs</td>\n                      </tr>\n                      ${leaderboardRows}\n                    </table>\n                  </td>\n                </tr>\n              </table>\n            </td>\n          </tr>\n\n          <tr><td style=\"height:18px;line-height:18px;font-size:0;\">&nbsp;</td></tr>\n\n          <!-- Org units -->\n          ${orgunits.length ? `\n          <tr>\n            <td>\n              <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"background:#ffffff;border-radius:14px;overflow:hidden;box-shadow:0 2px 10px rgba(17,24,39,0.06);\">\n                <tr>\n                  <td style=\"padding:20px 20px 10px;\">\n                    <div style=\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#111827;font-size:16px;font-weight:900;\">Org unit performance</div>\n                    <div style=\"margin-top:4px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#9ca3af;font-size:13px;\">Average of participant best times per org unit</div>\n                  </td>\n                </tr>\n                <tr>\n                  <td style=\"padding:6px 20px 18px;\">${buildOrgunitBarChart(orgunits)}</td>\n                </tr>\n              </table>\n            </td>\n          </tr>\n          <tr><td style=\"height:18px;line-height:18px;font-size:0;\">&nbsp;</td></tr>\n          ` : ''}\n\n          <!-- Footer -->\n          <tr>\n            <td align=\"center\" style=\"padding:8px 6px 20px;\">\n              <div style=\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;color:#cbd5e1;font-size:11px;\">\n                Generated automatically by <span style=\"color:#94a3b8;font-weight:800;\">n8n</span> · Powered by <span style=\"color:#94a3b8;font-weight:800;\">Keephub</span>\n              </div>\n            </td>\n          </tr>\n\n        </table>\n      </td>\n    </tr>\n  </table>\n</body>\n</html>`;\n\n const subject = `📊 Competition Report — ${weekStr} · 🏆 ${fastestNamePlain} in ${fastestTimePlain} · ${participantsCount} participant${participantsCount === 1 ? '' : 's'}`; \n\n return [{ json: { html, subject } }];"},"typeVersion":2},{"id":"79c8f4c6-2185-4c0f-824a-1a4f4d07f4cd","name":"🔗 Enrich with User Data","type":"n8n-nodes-base.code","position":[1968,1296],"parameters":{"jsCode":"// Guard: bail out early if no enrichment data available\nconst orgItems = $input.all();\nif (!orgItems.length) return [];\n\nconst userItems = $('Get submitter details for a form submission').all();\nconst durationItems = $('Calculate response duration for a form submission').all();\nconst splitItems = $('✂️ Split into Items').all();\n\n// Check if a custom competition start date/time was provided on the form\nconst formData = $('🚀 Start here!').first().json;\nconst dateRaw = formData['Competition start date (optional)'] || '';\nconst timeRaw = formData['Competition start time (optional)'] || '';\nconst customStartRaw = dateRaw ? (dateRaw + (timeRaw ? 'T' + timeRaw : 'T00:00')) : '';\nconst customStartDate = customStartRaw ? new Date(customStartRaw) : null;\nconst useCustomStart = customStartDate && !isNaN(customStartDate.getTime());\n\n// Direct id -> name map from all orgchart nodes\nconst orgById = new Map();\nfor (const o of orgItems) {\n  const n = o.json;\n  if (!n?._id) continue;\n  const id = String(n._id);\n  if (!orgById.has(id)) {\n    orgById.set(id, String(n.name || id).trim());\n  }\n}\n\nfunction resolveOrgunit(orgunits) {\n  if (!Array.isArray(orgunits) || orgunits.length === 0) return 'Unknown';\n  // Try each orgunit the user belongs to — return the first one we have a name for\n  // Prefer non-root nodes (more specific), so skip root#### if a better match exists\n  const nonRoot = orgunits.find(id => !String(id).startsWith('root') && orgById.has(String(id)));\n  if (nonRoot) return orgById.get(String(nonRoot));\n  const any = orgunits.find(id => orgById.has(String(id)));\n  if (any) return orgById.get(String(any));\n  return String(orgunits[0]) || 'Unknown';\n}\n\nfunction parseDuration(str) {\n  if (!str) return null;\n  const d = parseInt(str.match(/(\\d+)d/)?.[1] || 0);\n  const h = parseInt(str.match(/(\\d+)h/)?.[1] || 0);\n  const m = parseInt(str.match(/(\\d+)m/)?.[1] || 0);\n  const s = parseInt(str.match(/(\\d+)s/)?.[1] || 0);\n  return d * 86400 + h * 3600 + m * 60 + s;\n}\n\nfunction formatDuration(totalSeconds) {\n  if (totalSeconds == null) return '—';\n  totalSeconds = Math.round(totalSeconds);\n  const d = Math.floor(totalSeconds / 86400);\n  const h = Math.floor((totalSeconds % 86400) / 3600);\n  const m = Math.floor((totalSeconds % 3600) / 60);\n  const s = totalSeconds % 60;\n  if (d > 0) return `${d}d ${h}h ${m}m`;\n  if (h > 0) return `${h}h ${m}m ${s}s`;\n  if (m > 0) return `${m}m ${s}s`;\n  return `${s}s`;\n}\n\nconst count = Math.max(userItems.length, durationItems.length, splitItems.length);\nconst results = [];\n\nfor (let i = 0; i < count; i++) {\n  const user = userItems[i]?.json || {};\n  const dur = durationItems[i]?.json || {};\n  const raw = splitItems[i]?.json || {};\n\n  const submittedAt = dur.submittedAt || raw.createdAt || null;\n\n  let durationSeconds = null;\n  let durationStr = null;\n  let durationFormatted = null;\n\n  if (useCustomStart && submittedAt) {\n    // Custom start provided — measure from that moment\n    const subDate = new Date(submittedAt);\n    if (!isNaN(subDate.getTime())) {\n      durationSeconds = Math.max(0, Math.round((subDate.getTime() - customStartDate.getTime()) / 1000));\n    }\n  } else {\n    // No custom start — fall back to Keephub API duration\n    durationStr = dur.duration?.timeSinceFormCreated || null;\n    durationSeconds = parseDuration(durationStr);\n  }\n  durationFormatted = formatDuration(durationSeconds);\n\n  const fullName =\n    user.name ||\n    [user.firstName, user.insertion, user.lastName].filter(Boolean).join(' ').trim() ||\n    user.loginName ||\n    raw.createdBy ||\n    'Unknown';\n\n  const userOrgunit = resolveOrgunit(user.orgunits);\n\n  results.push({\n    json: {\n      submissionId: raw._id || null,\n      contentRef: raw.contentRef || null,\n      submittedAt,\n      durationSeconds,\n      durationStr,\n      durationFormatted,\n      userName: fullName,\n      userEmail: user.email || '',\n      userPosition: user.position || '',\n      userLevel: user.level || '',\n      userOrgunit,\n    }\n  });\n}\n\nreturn results;\n"},"typeVersion":2},{"id":"6e2f85d6-bd25-42ed-ade6-0ed783989bc4","name":"Get submitter details for a form submission","type":"n8n-nodes-keephub.keephub","onError":"continueRegularOutput","position":[1520,1296],"parameters":{"resource":"formSubmission","operation":"getSubmitterDetails","formSubmissionId":"={{ $('✂️ Split into Items').item.json._id }}"},"credentials":{"keephubBearerApi":{"id":"","name":"Keephub Bearer account"}},"typeVersion":1},{"id":"9010c1ef-e482-4fb3-9bbe-5b8cc530644c","name":"Calculate response duration for a form submission","type":"n8n-nodes-keephub.keephub","onError":"continueRegularOutput","position":[1296,1296],"parameters":{"resource":"formSubmission","operation":"calculateResponseDuration","formSubmissionId":"={{ $json._id }}"},"credentials":{"keephubBearerApi":{"id":"","name":"Keephub Bearer account"}},"typeVersion":1},{"id":"eacc15c8-c5f6-4d0f-a294-e8a8e2c4b764","name":"✂️ Split into Items","type":"n8n-nodes-base.code","position":[1072,1296],"parameters":{"jsCode":"return $input.all().map(item => ({ json: item.json }));"},"typeVersion":2},{"id":"9f74c3f3-6639-4bee-9837-3a302429f095","name":"Overview","type":"n8n-nodes-base.stickyNote","position":[576,688],"parameters":{"color":1,"width":1988,"height":788,"content":"## 🏆 Gamify form response times and email a ranked leaderboard with Keephub\n\nTurn any Keephub form into a timed competition. Paste in a Form ID, and this workflow fetches all submissions, ranks participants by response speed, enriches results with org-unit data, and emails you a polished HTML leaderboard — fully automated.\n\n**Who it's for:** HR teams, L&D managers, or anyone running timed Keephub form challenges who wants an instant, shareable results report.\n\n## How it works\n1. 📋 Someone submits the start form with their email, a Keephub Form ID, and an optional competition start date and time\n2. ⏱️ The workflow fetches all submissions and calculates each response time (from the custom start if provided, otherwise Keephub's default)\n3. 👤 Each result is enriched with the user's name and org unit from the Keephub orgchart\n4. 📧 A ranked leaderboard (top 10 + org-unit breakdown) is built and emailed as a beautiful HTML report\n\n## Setup steps\n1. 📦 Install **n8n-nodes-keephub** verified node (v1.5+) from Settings\n2. 🔑 Connect your Keephub Bearer token or Keephub Login credential in any Keephub node\n3. 📬 Connect your Gmail (or SMTP) account in the Send Competition Report node\n4. 🔗 Find your Form ID from the Keephub form URL\n5. ▶️ Click **Test workflow**, fill in the n8n form, and check your inbox"},"typeVersion":1},{"id":"016cb277-b787-40ad-babc-0ea907b830bb","name":"Fetch submissions","type":"n8n-nodes-base.stickyNote","position":[576,1168],"parameters":{"color":7,"width":640,"height":310,"content":"## 📥 Fetch submissions\nCollects all form submissions from Keephub for the given Form ID and splits them into individual items for per-user processing."},"typeVersion":1},{"id":"667886ce-d343-4875-82b5-ac63ba054b71","name":"Process and enrich","type":"n8n-nodes-base.stickyNote","position":[1232,1168],"parameters":{"color":7,"width":886,"height":310,"content":"## ⚙️ Process & enrich\nCalculates response duration per submission — using the custom start time when provided, or Keephub’s default (time since form creation). Fetches submitter details and resolves org-unit names from the orgchart. Nodes continue on error so one bad record won’t stop the report."},"typeVersion":1},{"id":"ac3f949a-fc38-47aa-b72b-3c80923648d7","name":"Build and send report","type":"n8n-nodes-base.stickyNote","position":[2144,1168],"parameters":{"color":7,"width":420,"height":310,"content":"## 📊 Build & send\nAggregates stats, builds a ranked HTML leaderboard with org-unit breakdown, and emails the competition report to the requester."},"typeVersion":1},{"id":"06dca842-339b-48e8-b8b3-d4e32133ffe6","name":"🚀 Start here!","type":"n8n-nodes-base.formTrigger","position":[624,1296],"webhookId":"0e1a60b3-63ce-4241-9042-692b997113f3","parameters":{"options":{"customCss":"@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap');\n\n* {\n  font-family: 'Open Sans', sans-serif;\n  box-sizing: border-box;\n}\n\nbody {\n  background: #f0eeff;\n  margin: 0;\n  padding: 0;\n}\n\n.container {\n  min-height: 100vh;\n  padding: 32px 16px;\n  display: flex;\n  justify-content: center;\n  align-items: flex-start;\n}\n\nsection {\n  width: 100%;\n  max-width: 448px;\n}\n\n.test-notice {\n  background: #fefaf6;\n  border: 1px solid #f6dcb7;\n  border-radius: 8px;\n  padding: 10px 16px;\n  margin-bottom: 12px;\n  text-align: center;\n}\n\n.test-notice p {\n  color: #e6a23d;\n  font-size: 12px;\n  font-weight: 600;\n  margin: 0;\n}\n\nhr {\n  border: none;\n  border-top: 1px solid #dbdfe7;\n  margin: 12px 0;\n}\n\n.card {\n  background: #ffffff;\n  border-radius: 12px;\n  box-shadow: 0px 4px 24px 0px rgba(99, 77, 255, 0.12);\n  overflow: hidden;\n  margin-bottom: 16px;\n  border: none;\n  padding: 0;\n}\n\n.form-header {\n  background: linear-gradient(135deg, #634dff 0%, #7c3aed 100%);\n  padding: 28px 24px;\n  margin-bottom: 0;\n  border-bottom: none;\n}\n\n.form-header h1 {\n  font-size: 22px;\n  font-weight: 600;\n  color: #ffffff;\n  margin: 0 0 8px 0;\n  line-height: 1.3;\n}\n\n.form-header p {\n  font-size: 13px;\n  font-weight: 400;\n  color: rgba(255, 255, 255, 0.8);\n  margin: 0;\n  line-height: 1.6;\n}\n\n.inputs-wrapper {\n  padding: 24px 24px 8px 24px;\n}\n\n.form-group {\n  margin-bottom: 18px;\n}\n\n.form-label {\n  display: block;\n  font-size: 13px;\n  font-weight: 600;\n  color: #525356;\n  margin-bottom: 6px;\n}\n\n.form-label.form-required::after {\n  content: ' *';\n  color: #ff6d5a;\n}\n\n.form-input {\n  display: block;\n  width: 100%;\n  padding: 11px 14px;\n  font-size: 14px;\n  font-weight: 400;\n  color: #525356;\n  background: #fbfcfe;\n  border: 1px solid #dbdfe7;\n  border-radius: 8px;\n  outline: none;\n  transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n\n.form-input:focus {\n  border-color: #634dff;\n  background: #ffffff;\n  box-shadow: 0 0 0 3px rgba(99, 77, 255, 0.1);\n}\n\n.form-input::placeholder {\n  color: #a0a3a8;\n}\n\n[class^=\"error-\"] {\n  font-size: 12px;\n  color: #ea1f30;\n  margin: 4px 0 0 0;\n  display: none;\n}\n\n[class^=\"error-\"].error-show {\n  display: block;\n}\n\n#submit-btn {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 8px;\n  width: calc(100% - 48px);\n  margin: 0 24px 24px 24px;\n  height: 48px;\n  background: #ff6d5a;\n  color: #ffffff;\n  border: none;\n  border-radius: 8px;\n  font-size: 14px;\n  font-weight: 600;\n  cursor: pointer;\n  transition: opacity 0.15s ease, transform 0.1s ease;\n}\n\n#submit-btn:hover {\n  opacity: 0.9;\n  transform: translateY(-1px);\n}\n\n#submit-btn:active {\n  opacity: 1;\n  transform: translateY(0);\n}\n\n#submit-btn:disabled {\n  opacity: 0.6;\n  cursor: not-allowed;\n  transform: none;\n}\n\n#submit-btn span {\n  display: none;\n}\n\n#submit-btn svg {\n  fill: #ffffff;\n}\n\n.n8n-link {\n  text-align: center;\n  margin-top: 16px;\n}\n\n.n8n-link a {\n  font-size: 12px;\n  color: #7e8186;\n  text-decoration: none;\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n}\n","respondWithOptions":{"values":{"formSubmittedText":"Processing... Please check your email shortly!"}}},"formTitle":"Run a form response time competition with Keephub","formFields":{"values":[{"fieldLabel":"Your email address please","placeholder":"your@email.com","requiredField":true},{"fieldLabel":"Form ID (copy from the form URL)","placeholder":"699848533ab62d9d50409890","requiredField":true},{"fieldType":"date","fieldLabel":"Competition start date (optional)"},{"fieldLabel":"Competition start time (optional)","placeholder":"12:35"}]},"responseMode":"lastNode","formDescription":"Enter your email and a Keephub Form ID to receive a detailed competition report.\n\nOptionally provide a competition start date & time — response durations will be measured from that moment. Leave blank to use Keephub’s default (time since form creation).\n\nThe workflow fetches all submissions, calculates response times, enriches each result with org-unit data, and emails you a ranked leaderboard."},"notesInFlow":false,"typeVersion":2.5}],"pinData":{},"connections":{"🚀 Start here!":{"main":[[{"node":"Find form submissions by form","type":"main","index":0}]]},"Get Orgchart Info":{"main":[[{"node":"🔗 Enrich with User Data","type":"main","index":0}]]},"✂️ Split into Items":{"main":[[{"node":"Calculate response duration for a form submission","type":"main","index":0}]]},"🔗 Enrich with User Data":{"main":[[{"node":"📊 Aggregate Stats & Build Report","type":"main","index":0}]]},"Find form submissions by form":{"main":[[{"node":"✂️ Split into Items","type":"main","index":0}]]},"📊 Aggregate Stats & Build Report":{"main":[[{"node":"📧 Send Competition Report","type":"main","index":0}]]},"Get submitter details for a form submission":{"main":[[{"node":"Get Orgchart Info","type":"main","index":0}]]},"Calculate response duration for a form submission":{"main":[[{"node":"Get submitter details for a form submission","type":"main","index":0}]]}}},"lastUpdatedBy":1,"workflowInfo":{"nodeCount":13,"nodeTypes":{"n8n-nodes-base.code":{"count":3},"n8n-nodes-base.gmail":{"count":1},"n8n-nodes-base.stickyNote":{"count":4},"n8n-nodes-keephub.keephub":{"count":4},"n8n-nodes-base.formTrigger":{"count":1}}},"status":"published","readyToDemo":null,"user":{"name":"Niksa Perovic","username":"niksa90","bio":"Obsessed with product, automation, and everything AI. Jack of all trades, master of getting things done.","verified":true,"links":[""],"avatar":"https://gravatar.com/avatar/8116b70e0134b4ff5de5d9e1d87bd1f3ae7df875fb146d4c9e301a454fdceb5a?r=pg&d=retro&size=200"},"nodes":[{"id":356,"icon":"file:gmail.svg","name":"n8n-nodes-base.gmail","codex":{"data":{"alias":["email","human","form","wait","hitl","approval"],"resources":{"generic":[{"url":"https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/","icon":"🧬","label":"Why business process automation with n8n can change your daily life"},{"url":"https://n8n.io/blog/supercharging-your-conference-registration-process-with-n8n/","icon":"🎫","label":"Supercharging your conference registration process with n8n"},{"url":"https://n8n.io/blog/no-code-ecommerce-workflow-automations/","icon":"store","label":"6 e-commerce workflows to power up your Shopify s"},{"url":"https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/","icon":"👥","label":"How to get started with CRM automation (with 3 no-code workflow ideas"},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/your-business-doesnt-need-you-to-operate/","icon":" 🖥️","label":"Hey founders! Your business doesn't need you to operate"},{"url":"https://n8n.io/blog/using-automation-to-boost-productivity-in-the-workplace/","icon":"💪","label":"Using Automation to Boost Productivity in the Workplace"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.gmail/"}],"credentialDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/credentials/google/oauth-single-service/"}]},"categories":["Communication","HITL"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"HITL":["Human in the Loop"]}}},"group":"[\"transform\"]","defaults":{"name":"Gmail"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNTYiIGhlaWdodD0iMTkzIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZmlsbD0iIzQyODVGNCIgZD0iTTU4LjE4MiAxOTIuMDVWOTMuMTRMMjcuNTA3IDY1LjA3NyAwIDQ5LjUwNHYxMjUuMDkxYzAgOS42NTggNy44MjUgMTcuNDU1IDE3LjQ1NSAxNy40NTV6Ii8+PHBhdGggZmlsbD0iIzM0QTg1MyIgZD0iTTE5Ny44MTggMTkyLjA1aDQwLjcyN2M5LjY1OSAwIDE3LjQ1NS03LjgyNiAxNy40NTUtMTcuNDU1VjQ5LjUwNWwtMzEuMTU2IDE3LjgzNy0yNy4wMjYgMjUuNzk4eiIvPjxwYXRoIGZpbGw9IiNFQTQzMzUiIGQ9Im01OC4xODIgOTMuMTQtNC4xNzQtMzguNjQ3IDQuMTc0LTM2Ljk4OUwxMjggNjkuODY4bDY5LjgxOC01Mi4zNjQgNC42NyAzNC45OTItNC42NyA0MC42NDRMMTI4IDE0NS41MDR6Ii8+PHBhdGggZmlsbD0iI0ZCQkMwNCIgZD0iTTE5Ny44MTggMTcuNTA0VjkzLjE0TDI1NiA0OS41MDRWMjYuMjMxYzAtMjEuNTg1LTI0LjY0LTMzLjg5LTQxLjg5LTIwLjk0NXoiLz48cGF0aCBmaWxsPSIjQzUyMjFGIiBkPSJtMCA0OS41MDQgMjYuNzU5IDIwLjA3TDU4LjE4MiA5My4xNFYxNy41MDRMNDEuODkgNS4yODZDMjQuNjEtNy42NiAwIDQuNjQ2IDAgMjYuMjN6Ii8+PC9zdmc+"},"displayName":"Gmail","typeVersion":2,"nodeCategories":[{"id":6,"name":"Communication"},{"id":28,"name":"HITL"}]},{"id":565,"icon":"fa:sticky-note","name":"n8n-nodes-base.stickyNote","codex":{"data":{"alias":["Comments","Notes","Sticky"],"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"input\"]","defaults":{"name":"Sticky Note","color":"#FFD233"},"iconData":{"icon":"sticky-note","type":"icon"},"displayName":"Sticky Note","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":834,"icon":"file:code.svg","name":"n8n-nodes-base.code","codex":{"data":{"alias":["cpde","Javascript","JS","Python","Script","Custom Code","Function"],"details":"The Code node allows you to execute JavaScript in your workflow.","resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/"}]},"categories":["Development","Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers","Data Transformation"]}}},"group":"[\"transform\"]","defaults":{"name":"Code"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xMTcxXzQ0MSkiPgo8cGF0aCBkPSJNMTcwLjI4MyA0OEgxOTYuNUMyMDMuMTI3IDQ4IDIwOC41IDQyLjYyNzQgMjA4LjUgMzZWMTJDMjA4LjUgNS4zNzI1OCAyMDMuMTI3IDAgMTk2LjUgMEgxNzAuMjgzQzEyNi4xIDAgOTAuMjgzIDM1LjgxNzIgOTAuMjgzIDgwVjE3NkM5MC4yODMgMjA2LjkyOCA2NS4yMTA5IDIzMiAzNC4yODMgMjMySDIzQzE2LjM3MjYgMjMyIDExIDIzNy4zNzIgMTEgMjQ0VjI2OEMxMSAyNzQuNjI3IDE2LjM3MjQgMjgwIDIyLjk5OTYgMjgwTDM0LjI4MyAyODBDNjUuMjEwOSAyODAgOTAuMjgzIDMwNS4wNzIgOTAuMjgzIDMzNlY0NDBDOTAuMjgzIDQ3OS43NjQgMTIyLjUxOCA1MTIgMTYyLjI4MyA1MTJIMTk2LjVDMjAzLjEyNyA1MTIgMjA4LjUgNTA2LjYyNyAyMDguNSA1MDBWNDc2QzIwOC41IDQ2OS4zNzMgMjAzLjEyNyA0NjQgMTk2LjUgNDY0SDE2Mi4yODNDMTQ5LjAyOCA0NjQgMTM4LjI4MyA0NTMuMjU1IDEzOC4yODMgNDQwVjMzNkMxMzguMjgzIDMwOS4wMjIgMTI4LjAxMSAyODQuNDQzIDExMS4xNjQgMjY1Ljk2MUMxMDYuMTA5IDI2MC40MTYgMTA2LjEwOSAyNTEuNTg0IDExMS4xNjQgMjQ2LjAzOUMxMjguMDExIDIyNy41NTcgMTM4LjI4MyAyMDIuOTc4IDEzOC4yODMgMTc2VjgwQzEzOC4yODMgNjIuMzI2OSAxNTIuNjEgNDggMTcwLjI4MyA0OFoiIGZpbGw9IiNGRjk5MjIiLz4KPHBhdGggZD0iTTMwNSAzNkMzMDUgNDIuNjI3NCAzMTAuMzczIDQ4IDMxNyA0OEgzNDIuOTc5QzM2MC42NTIgNDggMzc0Ljk3OCA2Mi4zMjY5IDM3NC45NzggODBWMTc2QzM3NC45NzggMjAyLjk3OCAzODUuMjUxIDIyNy41NTcgNDAyLjA5OCAyNDYuMDM5QzQwNy4xNTMgMjUxLjU4NCA0MDcuMTUzIDI2MC40MTYgNDAyLjA5OCAyNjUuOTYxQzM4NS4yNTEgMjg0LjQ0MyAzNzQuOTc4IDMwOS4wMjIgMzc0Ljk3OCAzMzZWNDMyQzM3NC45NzggNDQ5LjY3MyAzNjAuNjUyIDQ2NCAzNDIuOTc5IDQ2NEgzMTdDMzEwLjM3MyA0NjQgMzA1IDQ2OS4zNzMgMzA1IDQ3NlY1MDBDMzA1IDUwNi42MjcgMzEwLjM3MyA1MTIgMzE3IDUxMkgzNDIuOTc5QzM4Ny4xNjEgNTEyIDQyMi45NzggNDc2LjE4MyA0MjIuOTc4IDQzMlYzMzZDNDIyLjk3OCAzMDUuMDcyIDQ0OC4wNTEgMjgwIDQ3OC45NzkgMjgwSDQ5MEM0OTYuNjI3IDI4MCA1MDIgMjc0LjYyOCA1MDIgMjY4VjI0NEM1MDIgMjM3LjM3MyA0OTYuNjI4IDIzMiA0OTAgMjMyTDQ3OC45NzkgMjMyQzQ0OC4wNTEgMjMyIDQyMi45NzggMjA2LjkyOCA0MjIuOTc4IDE3NlY4MEM0MjIuOTc4IDM1LjgxNzIgMzg3LjE2MSAwIDM0Mi45NzkgMEgzMTdDMzEwLjM3MyAwIDMwNSA1LjM3MjU4IDMwNSAxMlYzNloiIGZpbGw9IiNGRjk5MjIiLz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMF8xMTcxXzQ0MSI+CjxyZWN0IHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo="},"displayName":"Code","typeVersion":2,"nodeCategories":[{"id":5,"name":"Development"},{"id":9,"name":"Core Nodes"}]},{"id":1225,"icon":"file:form.svg","name":"n8n-nodes-base.formTrigger","codex":{"data":{"alias":["table","submit","post"],"resources":{"generic":[],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.formtrigger/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Other Trigger Nodes"]}}},"group":"[\"trigger\"]","defaults":{"name":"On form submission"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NiIgaGVpZ2h0PSI0MCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwQjdCQyIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzQuOTc4IDM3LjczMmExLjU2IDEuNTYgMCAwIDEtMS41NjIgMS41NjNINi4yNmExLjU2IDEuNTYgMCAwIDEtMS41NjMtMS41NjNWOS42MDdjMC0uNDA1LjE1Ny0uNzk0LjQzOC0xLjA4Nmw2LjMwNC02LjUzMXY1LjM0NEg4LjIxM2ExLjE3MiAxLjE3MiAwIDEgMCAwIDIuMzQzaDQuNDNhMS4xNyAxLjE3IDAgMCAwIDEuMTcxLTEuMTcxVi4yMzJoMTkuNjAyYTEuNTYgMS41NiAwIDAgMSAxLjU2MiAxLjU2M3YxMC4zMjdsLTIuODYgMi44Ni04LjI1MiA4LjI3NmE0MTMuMDA2IDQxMy4wMDYgMCAwIDEtMS42NTQgMS42NjJsLS4zMzcuMzM3YTIgMiAwIDAgMC0uNTU3IDEuMDhMMjAuMyAzMS45MjJjLS4xMDguNjM4LS4yMTUgMS4wNzkuMjExIDEuNDE4LjQwMy4zMi45LjE3NCAxLjU0LjA2Nmw1LjQwOC0uOTI4YTIgMiAwIDAgMCAxLjA4LS41NTZsNi40NC02LjQyOXptLTI0LjAzLTIxLjI2NWExLjE4IDEuMTggMCAwIDAgMS4xNzEgMS4xNzJoMTMuMTYzYTEuMTcyIDEuMTcyIDAgMSAwIDAtMi4zNDRIMTIuMTE5YTEuMTcgMS4xNyAwIDAgMC0xLjE3MiAxLjE3Mm03LjI5NCAxNC43NjZhMS4xNyAxLjE3IDAgMCAwLTEuMTcyLTEuMTcySDEyLjEyYTEuMTcyIDEuMTcyIDAgMSAwIDAgMi4zNDNoNC45NTFhMS4xNyAxLjE3IDAgMCAwIDEuMTcyLTEuMTcybS44Ni03LjM5MWExLjE3IDEuMTcgMCAwIDAtMS4xNzItMS4xNzJoLTUuODExYTEuMTcyIDEuMTcyIDAgMSAwIDAgMi4zNDNoNS44MWExLjE2NCAxLjE2NCAwIDAgMCAxLjE3My0xLjE3MSIgY2xpcC1ydWxlPSJldmVub2RkIi8+PHBhdGggZmlsbD0iIzAwQjdCQyIgZD0ibTMzLjUzMiAxNi4zOTcgNC4yODktNC4yODkgMy43NTggMy43MSAxLjYxNy0xLjYxNiAyLjI1OCAyLjI1N2MuMjE4LjIxOC4zNC41MTMuMzQzLjgyLS4wMDIuMzExLS4xMjUuNjA4LS4zNDQuODNsLTYuODA0IDYuNzk2YTEuMTMgMS4xMyAwIDAgMS0uODI4LjM0MyAxLjE1IDEuMTUgMCAwIDEtLjgyOC0uMzQzIDEuMTggMS4xOCAwIDAgMSAwLTEuNjU3bDUuOTc2LTUuOTY4LTEuMzEyLTEuMzEzLTEuMzgzIDEuNDE0LTEzLjE0OSAxMy4xMjUtNC42MTcuNzgyLjc4Mi00LjYxNy4zMzYtLjMzNyAyLjU2MiAyLjU1NWExLjEgMS4xIDAgMCAwIC44MjguMzQ0Yy4zMTIuMDA1LjYxMi0uMTIuODI4LS4zNDRhMS4xOCAxLjE4IDAgMCAwIDAtMS42NTZsLTIuNTYyLTIuNTYyek00NC43MzYgMTIuMjRjMCAuNDE0LS4xNjMuODEtLjQ1NCAxLjEwMmwtLjkyMi45MTQtMy44NTItMy44MjguOTMtLjkzYTEuNTYzIDEuNTYzIDAgMCAxIDIuMjAzIDBsMS42NCAxLjY0MWMuMjkxLjI5My40NTUuNjkuNDU1IDEuMTAyIi8+PC9zdmc+"},"displayName":"n8n Form Trigger","typeVersion":3,"nodeCategories":[{"id":9,"name":"Core Nodes"}]}],"categories":[{"id":17,"name":"HR"},{"id":49,"name":"AI Summarization"}],"image":[]}}