{
  "name": "School 3 · Webinar: registration, reminders, no-shows, stuck at payment",
  "nodes": [
    {
      "id": "929a4bc4-f1ff-427d-bf03-3b5963adf237",
      "name": "About",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -640,
        -520
      ],
      "parameters": {
        "content": "## Webinar: registration → reminders → no-shows → stuck at payment\nDemo recipe for the portfolio, not a client project.\n\n**RU.** Четыре ветки: регистрация, напоминания и сообщение тем, кто не пришёл (проверка каждые пять минут), отметка входа по ссылке, список тех, кто создал заказ и не оплатил.\n**EN.** Four branches: registration, reminders and a no-show message (checked every five minutes), attendance via the link, and a list of people who created an order but did not pay.\n\n**Setup**\n1. Google Sheet with two tabs. \"Участники\": id, name, email, tg_chat_id, source, registered_at, confirm_sent, r24_sent, r1h_sent, r10m_sent, attended, noshow_sent. \"Заказы\": order, email, name, offer, status, created_at, updated_at.\n2. Set the start time, room and recording links in both \"Settings\" nodes and the room link in \"Open the room\".\n3. Form or bot builder posts to /webhook/webinar-register: name, email, tg_chat_id (if the person came from the bot), source.\n4. GetCourse processes for Orders (created, and status \"Completed\") call /webhook/webinar-order?order={object.number}&email={object.user.email}&name={object.user.first_name}&offer={object.positions}&status={object.status}\n5. Connect Telegram, SMTP and Google Sheets.\n\nMax Igoshev, igoshevmax@gmail.com, igoshevmax.com",
        "width": 560,
        "height": 600
      }
    },
    {
      "id": "a2b67d5b-2704-49fd-8527-f01e363d94de",
      "name": "Registration",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "webhookId": "c68cf6f8-115f-431c-b059-2bfa79d827b6",
      "parameters": {
        "httpMethod": "POST",
        "path": "webinar-register",
        "responseMode": "onReceived",
        "options": {}
      }
    },
    {
      "id": "0937cb95-ba31-48d3-9c55-54159b7b0709",
      "name": "Clean registration",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        0
      ],
      "parameters": {
        "jsCode": "const b = $json.body ?? $json;\nconst pick = (...k) => k.map(x => b[x]).find(v => v !== undefined && String(v).trim())?.toString().trim() ?? '';\nconst email = pick('email', 'Email').toLowerCase();\nconst tg = pick('tg_chat_id', 'chat_id', 'telegram_id');\nif (!email && !tg) return [];\nreturn [{ json: {\n  id: (tg || email).replace(/[^a-z0-9@._-]/gi, ''),\n  name: pick('name', 'Name') || 'участник',\n  email, tg_chat_id: tg,\n  source: pick('source', 'utm_source', 'start_param') || 'direct',\n  registered_at: new Date().toISOString(),\n  confirm_sent: new Date().toISOString(),\n} }];"
      }
    },
    {
      "id": "39473884-a041-4dfc-b89d-78922bc34651",
      "name": "Add participant",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        480,
        0
      ],
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [
            "id"
          ]
        },
        "options": {}
      }
    },
    {
      "id": "b774c7b7-39f8-4d6f-96ab-1f3e3c7b0546",
      "name": "Settings (registration)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        720,
        0
      ],
      "parameters": {
        "jsCode": "// One place for the webinar settings.\nreturn [{ json: {\n  start: '2026-11-20T19:00:00+03:00',   // webinar start, with time zone\n  durationMin: 120,\n  room: 'https://ROOM_LINK',             // the webinar room\n  go: 'https://YOUR_N8N/webhook/webinar-go', // attendance link (branch 3)\n  recording: 'https://RECORDING_LINK',\n  title: 'Вебинар школы',\n} }];"
      }
    },
    {
      "id": "7360294c-636b-4998-8f1f-0d893820ac0f",
      "name": "From the bot?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        960,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "caaf56b9-f124-4316-ab66-fb232722efd8",
              "leftValue": "={{ Boolean($('Clean registration').first().json.tg_chat_id) }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "206c1f61-6c28-476e-b1a7-368d00dca695",
      "name": "Confirm in Telegram",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1200,
        -100
      ],
      "webhookId": "06a2d741-55e2-41d9-a87c-5cd7f4758c23",
      "parameters": {
        "chatId": "={{ $('Clean registration').first().json.tg_chat_id }}",
        "text": "={{ $('Clean registration').first().json.name }}, вы записаны на «{{ $json.title }}».\nНачало: {{ new Date($json.start).toLocaleString('ru-RU', { timeZone: 'Europe/Moscow', day: 'numeric', month: 'long', hour: '2-digit', minute: '2-digit' }) }} по Москве.\nНапомню за сутки, за час и за десять минут до начала.",
        "additionalFields": {
          "appendAttribution": false
        }
      }
    },
    {
      "id": "b46c8705-052e-41c9-b807-4e844e44bf45",
      "name": "Confirm by email",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        1200,
        100
      ],
      "webhookId": "fb34404a-1c39-41de-87b7-7c108b57e714",
      "parameters": {
        "fromEmail": "school@example.com",
        "toEmail": "={{ $('Clean registration').first().json.email }}",
        "subject": "=Вы записаны: {{ $json.title }}",
        "emailFormat": "text",
        "text": "={{ $('Clean registration').first().json.name }}, вы записаны на «{{ $json.title }}».\nНачало: {{ new Date($json.start).toLocaleString('ru-RU', { timeZone: 'Europe/Moscow', day: 'numeric', month: 'long', hour: '2-digit', minute: '2-digit' }) }} по Москве.\nСсылка придёт в напоминаниях.",
        "options": {}
      }
    },
    {
      "id": "05d2b389-2315-4078-91d1-e8fc4427a9b6",
      "name": "Every 5 minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        400
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 5
            }
          ]
        }
      }
    },
    {
      "id": "c648a811-6449-403a-9f8e-774fa70bd0a0",
      "name": "Settings (reminders)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        400
      ],
      "parameters": {
        "jsCode": "// One place for the webinar settings.\nreturn [{ json: {\n  start: '2026-11-20T19:00:00+03:00',   // webinar start, with time zone\n  durationMin: 120,\n  room: 'https://ROOM_LINK',             // the webinar room\n  go: 'https://YOUR_N8N/webhook/webinar-go', // attendance link (branch 3)\n  recording: 'https://RECORDING_LINK',\n  title: 'Вебинар школы',\n} }];"
      }
    },
    {
      "id": "def0d464-b9a0-465d-a5ef-3959e4ac817d",
      "name": "Read participants",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        480,
        400
      ],
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "options": {}
      }
    },
    {
      "id": "5f457f91-9c42-4cde-a010-7332cc41d59b",
      "name": "Due messages",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        720,
        400
      ],
      "parameters": {
        "jsCode": "// Decides who gets which message now. Each message is sent once: its column gets a time stamp.\nconst s = $('Settings (reminders)').first().json;\nconst start = new Date(s.start).getTime();\nconst end = start + s.durationMin * 60e3;\nconst now = Date.now();\nconst H = 3600e3;\nconst out = [];\nfor (const { json: p } of $input.all()) {\n  if (!p.id) continue;\n  const link = s.go + '?id=' + encodeURIComponent(p.id);\n  let mark = null, text = '';\n  if (!p.r24_sent && now >= start - 24 * H && now < start - 20 * H) {\n    mark = 'r24_sent'; text = p.name + ', завтра в это время начинается «' + s.title + '». Ссылка для входа: ' + link;\n  } else if (!p.r1h_sent && now >= start - H && now < start - 10 * 60e3) {\n    mark = 'r1h_sent'; text = 'Через час начинаем «' + s.title + '». Вход: ' + link;\n  } else if (!p.r10m_sent && now >= start - 10 * 60e3 && now < start + 15 * 60e3) {\n    mark = 'r10m_sent'; text = 'Начинаем через десять минут. Вход: ' + link;\n  } else if (!p.attended && !p.noshow_sent && now >= end + H && now < end + 48 * H) {\n    mark = 'noshow_sent'; text = p.name + ', вы не смогли прийти на «' + s.title + '». Запись: ' + s.recording;\n  }\n  if (mark) out.push({ json: { id: p.id, email: p.email, tg_chat_id: p.tg_chat_id, mark, text, subject: s.title } });\n}\nreturn out;"
      }
    },
    {
      "id": "b58278d7-1c38-4603-8960-0cb6d6003a6c",
      "name": "Has Telegram?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        960,
        400
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "82389b00-5d79-4ecf-a524-100a43d26bc5",
              "leftValue": "={{ Boolean($json.tg_chat_id) }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "4f4c3ee6-75b4-4923-862e-e236a8325730",
      "name": "Send in Telegram",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1200,
        300
      ],
      "webhookId": "f7cc8f99-6912-410f-a13d-65864e70f791",
      "parameters": {
        "chatId": "={{ $json.tg_chat_id }}",
        "text": "={{ $json.text }}",
        "additionalFields": {
          "appendAttribution": false
        }
      }
    },
    {
      "id": "8ef5641b-9f78-41c7-b239-ddb8bd53d88a",
      "name": "Send by email",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        1200,
        500
      ],
      "webhookId": "5123377c-88da-4e7a-b1da-0425184e1f79",
      "parameters": {
        "fromEmail": "school@example.com",
        "toEmail": "={{ $json.email }}",
        "subject": "={{ $json.subject }}",
        "emailFormat": "text",
        "text": "={{ $json.text }}",
        "options": {}
      }
    },
    {
      "id": "3fa37a9d-802c-4a15-bc2e-cee161ef0f17",
      "name": "Stamp sent",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1440,
        400
      ],
      "parameters": {
        "jsCode": "return $input.all().map((item, i) => {\n  const src = $('Due messages').itemMatching(i).json;\n  return { json: { id: src.id, [src.mark]: new Date().toISOString() } };\n});"
      }
    },
    {
      "id": "84b30cd4-1919-4fc0-9046-ed20d03baafa",
      "name": "Save stamp",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1680,
        400
      ],
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [
            "id"
          ]
        },
        "options": {}
      }
    },
    {
      "id": "e6dd87b2-af65-45ea-9772-ed719aca5fde",
      "name": "Entry link",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        800
      ],
      "webhookId": "57c9c869-bafd-4d09-8edf-4c21f197190e",
      "parameters": {
        "httpMethod": "GET",
        "path": "webinar-go",
        "responseMode": "responseNode",
        "options": {}
      }
    },
    {
      "id": "3a6d2306-3881-4c63-b779-7df10fd73fbd",
      "name": "Mark attended",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        800
      ],
      "parameters": {
        "jsCode": "const id = String($json.query?.id ?? '').trim();\nreturn [{ json: { id, attended: new Date().toISOString() } }];"
      }
    },
    {
      "id": "7769dea3-2a38-48d8-a5d3-9b237975707f",
      "name": "Save attendance",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        480,
        800
      ],
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [
            "id"
          ]
        },
        "options": {}
      },
      "continueOnFail": true
    },
    {
      "id": "2549e97f-d3ab-4e75-816d-b18fbd7744e8",
      "name": "Open the room",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        720,
        800
      ],
      "parameters": {
        "respondWith": "redirect",
        "redirectURL": "https://ROOM_LINK",
        "options": {}
      }
    },
    {
      "id": "bcbbd3c3-0271-4741-b4fa-29d29cc12d33",
      "name": "Order event",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        1150
      ],
      "webhookId": "ef9d52a2-6d70-452f-a05a-9c4ac9811939",
      "parameters": {
        "httpMethod": "GET",
        "path": "webinar-order",
        "responseMode": "onReceived",
        "options": {}
      }
    },
    {
      "id": "af6e5c42-8145-4d53-8e50-bf1283e06aa4",
      "name": "Clean order",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        1150
      ],
      "parameters": {
        "jsCode": "const q = $json.query ?? {};\nconst order = String(q.order ?? '').trim();\nif (!order) return [];\nconst raw = String(q.status ?? '').toLowerCase();\nconst paid = raw === 'payed' || /заверш|оплачен/.test(raw);\nconst row = { order, email: String(q.email ?? '').toLowerCase(), name: q.name ?? '', offer: q.offer ?? '',\n  status: paid ? 'paid' : 'waiting', updated_at: new Date().toISOString() };\nif (!paid) row.created_at = row.updated_at;\nreturn [{ json: row }];"
      }
    },
    {
      "id": "db28b92c-cad5-44d4-9b15-59bd03acb7d8",
      "name": "Save order",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        480,
        1150
      ],
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [
            "order"
          ]
        },
        "options": {}
      }
    },
    {
      "id": "a646c5fe-000e-4bdf-a9b9-dc4cf0828478",
      "name": "Every morning 10:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        1450
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "triggerAtHour": 10
            }
          ]
        }
      }
    },
    {
      "id": "ca2291be-f2ed-4140-8440-223d0949d698",
      "name": "Read orders",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        240,
        1450
      ],
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "options": {}
      }
    },
    {
      "id": "4bbac7e6-5460-437e-b646-fa99b1286e4c",
      "name": "Stuck at payment",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        1450
      ],
      "parameters": {
        "jsCode": "// Orders created more than two hours and less than three days ago, still not paid.\nconst now = Date.now(), H = 3600e3;\nconst rows = $input.all().map(i => i.json).filter(r => r.order && r.status !== 'paid' && r.created_at)\n  .filter(r => { const t = new Date(r.created_at).getTime(); return now - t > 2 * H && now - t < 72 * H; });\nif (!rows.length) return [];\nconst lines = rows.map(r => '• ' + (r.name || 'без имени') + ', ' + r.email + ', ' + r.offer + ', заказ ' + r.order);\nreturn [{ json: { text: 'Дошли до оплаты и остановились: ' + rows.length + '\\n' + lines.join('\\n') } }];"
      }
    },
    {
      "id": "5e21841e-63df-4c81-950a-22e8f6339bda",
      "name": "List to managers",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        720,
        1450
      ],
      "webhookId": "1e6eddf1-aeff-41d6-be95-74d409aeae4c",
      "parameters": {
        "chatId": "YOUR_MANAGERS_CHAT_ID",
        "text": "={{ $json.text }}",
        "additionalFields": {
          "appendAttribution": false
        }
      }
    }
  ],
  "connections": {
    "Registration": {
      "main": [
        [
          {
            "node": "Clean registration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean registration": {
      "main": [
        [
          {
            "node": "Add participant",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add participant": {
      "main": [
        [
          {
            "node": "Settings (registration)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings (registration)": {
      "main": [
        [
          {
            "node": "From the bot?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "From the bot?": {
      "main": [
        [
          {
            "node": "Confirm in Telegram",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Confirm by email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every 5 minutes": {
      "main": [
        [
          {
            "node": "Settings (reminders)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings (reminders)": {
      "main": [
        [
          {
            "node": "Read participants",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read participants": {
      "main": [
        [
          {
            "node": "Due messages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Due messages": {
      "main": [
        [
          {
            "node": "Has Telegram?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Telegram?": {
      "main": [
        [
          {
            "node": "Send in Telegram",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send by email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send in Telegram": {
      "main": [
        [
          {
            "node": "Stamp sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send by email": {
      "main": [
        [
          {
            "node": "Stamp sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Stamp sent": {
      "main": [
        [
          {
            "node": "Save stamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Entry link": {
      "main": [
        [
          {
            "node": "Mark attended",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark attended": {
      "main": [
        [
          {
            "node": "Save attendance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save attendance": {
      "main": [
        [
          {
            "node": "Open the room",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Order event": {
      "main": [
        [
          {
            "node": "Clean order",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean order": {
      "main": [
        [
          {
            "node": "Save order",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every morning 10:00": {
      "main": [
        [
          {
            "node": "Read orders",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read orders": {
      "main": [
        [
          {
            "node": "Stuck at payment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Stuck at payment": {
      "main": [
        [
          {
            "node": "List to managers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "active": false,
  "tags": []
}
