Перейти до основного вмісту
megapromotingПоговорімо

Експертиза · Обслуговування

Обслуговування означає знати, що форма доставляє сьогодні, а не що сервер відповідає 200.

Моніторинг, який перевіряє повний шлях запиту до людини, оновлення з воротами перед продакшеном і перевірка приймання, що запускається після кожної публікації. Усі три працюють на цьому сайті і їх можна відкрити ззовні.

Вже збудованоТри власні реалізації, усі в репозиторії цього сайту й усі перевіряються сьогодні: `src/app/api/health/contact/route.ts` (сонда доставки, відповідає 200 на live просто зараз), `src/lib/lead-store.ts` (append-only журнал із retention, застосованим під час запису) і `scripts/verify-redesign.ts` (перевірка приймання, запущена після публікації). Ми написали їх тому, що 06.09.2026 власна форма тихо впала: токен бота був відкликаний, endpoint повертав 500, а запити зникали без сліду. Це не продажна історія — це коміт, який створив файли вище.

«Сайт працює» — це твердження про головну сторінку. Відвідувач, який заповнив форму і отримав помилку, був зупинений не сторінкою, що впала, а каналом доставки, який тихо закінчився. Різниця між цими двома речами — це й є все, що означає серйозне обслуговування: ми не стежимо, чи відповідає сервер, а чи доходить запит від людини до людини.

6 вересня 2026 року саме це сталося на цьому сайті. Токен бота, який передавав запити з форми до команди, був відкликаний; інтерфейс Telegram відповідав `401 Unauthorized`, наш маршрут повертав 500, а відвідувач бачив «повідомлення не вдалося надіслати». Запит ніде не записувався. У журналі помилок процесу було три реальні збої за приблизно сім годин, що минули від попередньої публікації. Ніхто не дивився.

З цього вийшли три частини, які ми тепер монтуємо в кожному проєкті, який обслуговуємо. Журнал append-only, записаний *до* спроби доставки, щоб зламаний канал деградував у «треба подивитися у файл», а не в «запиту ніколи не існувало». Спроба перевірки стану, яка відповідає на один запит, чи може лідів зараз дійти до людини. І перевірка приймання, що запускається після кожної публікації та падає, якщо зник канонічний адрес, якір питань або якщо в карті сайту з’явилися адреси, які не відкриваються.

Інше — це нудна й перевірна дисципліна: оновлення з аудиторськими воротами перед тим, як торкнутися production, автоматичний перезапуск із порогом пам’яті та лімітом нестабільних перезапусків, ретенція, застосована кодом, а не оголошена в політиці, і IP-адреси, обрізані до мережевого префікса, перш ніж десь записуватися.

Що входить

Робота, за складовими

Датчик, що перевіряє доставку, а не доступність

`GET /api/health/contact` відповідає 200, якщо лід може зараз дійти до людини, і 503, якщо ні. Він окремо перевіряє дві речі: що токен каналу сповіщень досі дійсний (реальний запит до постачальника, із timeout 8 секунд) і що журнал запитів придатний до запису. Відповідь складається лише з логічних значень — ніколи не з токена, ідентифікатора каналу чи назви бота. Результат зберігається в пам’яті 60 секунд, щоб зовнішня, часто натискана перевірка не ставала сама собою трафіком. Коли постачальник недоступний, поле стає `null`, а не `false`: «не знаю» і «недійсний» — це різні стани.

Журнал, записаний до доставки, а не після

Кожен запит отримує ідентифікатор і записується в журнал append-only, один рядок JSON на подію, *до* спроби сповіщення. Другий рядок, з тим самим ідентифікатором, описує, що сталося фактично: `delivered` або `failed`, із причиною, обрізаною до 300 символів. Каталог створюється з правами `0700`, файли — з `0600`, а шлях навмисно виноситься за межі каталогу версій, щоб історія пережила публікацію.

Перевірка приймання, запущена після публікації

Скрипт перевірки відкриває кожну сторінку продукту, групами по три, і падає, якщо бракує коду 200, якоря запитань, якоря прикладу або канонічної адреси. Потім він перевіряє карту сайту — щоб вона не містила мовних варіантів, які не можна відкрити, і містила кожен продукт — та `robots.txt`, щоб він не блокував ресурси, потрібні для рендерингу. Він також падає, якщо на сторінці знову з’являється вміст зі старого клієнта. Це список речей, які вже одного разу зламалися.

Оновлення з воротами, а не з надією

Скрипт публікації відмовляється запускатися, якщо на сервері менше 500 MB вільної пам’яті, запускає `npm audit --audit-level=high` і зупиняється на вразливостях високого рівня, якщо ви явно не підтвердите, потім збирає з чистого — `.next` і `node_modules` видалені, встановлення з файла блокування. Якщо процес не з’являється як `online` після запуску, скрипт показує останні 50 рядків журналу і виходить з помилкою, замість того щоб повідомляти про успіх.

Перезапуск із порогами, а не навмання

Процес автоматично перезапускається понад 500 MB пам’яті, із затримкою 4 секунди між спробами, експоненційним зростанням затримки та зупинкою після 10 нестабільних перезапусків — щоб петля падіння стала видимою замість того, щоб мовчки споживати сервер. Час пільги під час зупинки: 5 секунд, потім примусове завершення. Журнали мають дату і часовий пояс, в одному потоці.

Дублі обробляються, а не рахуються двічі

Та сама людина, те саме повідомлення, двічі — подвійний клік, перезавантаження сторінки — створювало два однакові сповіщення для одного запиту. Тепер відбиток `sha256` пари адреса + повідомлення зберігається 10 хвилин у пам’яті; друга відправка отримує той самий ідентифікатор запиту та позначку `duplicate`, а сповіщення не повторюється. Приховуються лише дублікати успішної доставки; невдала має право пройти знову.

Коди помилок, які кажуть, що зламалося

400 для недійсних даних, 405 при неправильному методі, 500 для зіпсованого тіла запиту, 502 коли канал доставки відповів погано, 503 коли бракує облікових даних. Різниця має значення о 3 ночі: 502 означає «постачальник», 503 означає «наша конфігурація». Відвідувачу окремо кажуть: «ми отримали запит, але не змогли повідомити» — не хибний успіх.

Зберігання застосовується кодом, а не обіцяється в політиці

Опублікована примітка про конфіденційність каже, що запити з форми зберігаються 24 місяці. Код застосовує точно те саме число: `LEAD_RETENTION_DAYS` за замовчуванням 730, а файли старші за поріг видаляються під час кожного запису, без планувальника, про який можна забути. IP-адреса обрізається до мережевого префікса — `/24` для IPv4, `/48` для IPv6 — і читається останній хоп із заголовка, а не перший, бо перший надсилає клієнт.

Знаходимо й те, на що ніхто не скаржився

Той самий прохід через код виявив дві речі, про які не повідомив жоден користувач: ліміт у 10 запитів на хвилину можна було повністю обійти, бо читався перший елемент із заголовка перенаправлених адрес — той, що контролюється клієнтом — а маршрут ініціювання телефонних дзвінків був відкритий анонімно, за наш рахунок і з нашого номера, хоча компонент, який його використовував, уже ніде не був змонтований. Обидва виправлено і перевірено на виробництві.

Як це виглядає

Шлях, крок за кроком.

01

Інвентар шляхів, якими запит доходить до людини

Перша доставка — це не інструмент, а список: через що проходить запит від натискання кнопки до людини, яка його читає, що ламається на кожному кроці і що має статися, коли це ламається. На цьому сайті список мав три ланки, і одна була невидима.

02

Сонду й журнал, змонтовані перед усім іншим

Ми постачаємо адресу здоров’я, яка перевіряє повний шлях, а не процес, і журнал, який записує до доставки. Відтепер падіння — це питання з відповіддю, а не риття в журналах. Адресу можна запитувати з будь-якого зовнішнього сервісу моніторингу, бо вона не повертає нічого чутливого.

03

Перевірка приймання, написана з реальних дефектів

Кожна річ, що одного разу зламалася, потрапляє до сценарію перевірки, який запускається після публікації. Ми не пишемо тести для гіпотетичних випадків; ми пишемо для тих, що вже коштували нам. Ми передаємо сценарій, а не лише його результат — ви можете запустити його й самі.

04

Ритм оновлення і ворота перед продакшеном

Ми визначаємо, що оновлюється автоматично, що проходить через ручну перевірку, а до чого не торкаються без анонсованого вікна. Ворота включають аудит безпеки залежностей і перевірку ресурсів сервера, обидві виконуються до того, як торкнутися продакшену.

05

Передання, зі списком відсутнього

Наприкінці ми передаємо процедуру публікації, процедуру повернення, адреси здоров’я та письмовий список того, що не покрито. На цьому сайті, наприклад, гілка закінчення терміну запиту до постачальника реалізована і потрапляє до тієї самої обробки, що й мережева помилка, але сам abort не був викликаний у тесті — так написано в журналі виконання, а не у внутрішній нотатці.

1Sonda care verificălivrarea până la unom2jurnalul scrisînainte de încercare3verificarea deacceptare rulată dupăfiecare publicareTrei piese, toate în depozitul acestui site.
Шлях, у 3 кроки

Дані

Чого торкаємося, де воно лежить і скільки лишається

Запитання, які ставить кожен, у кого є відповідальний за захист даних, — поставлені тут раніше, ніж їх поставить він.

Журнал запитів
Ім’я, адреса електронної пошти, телефон, компанія, обрана послуга, повідомлення, сторінка, з якої надіслано, хост сторінки походження (лише хост, не повна адреса) і мережевий префікс відвідувача. JSON-файли щодня, на власному сервері, поза директорією версії. Строк: 24 місяці, застосовується під час запису.
Журнали процесу і сервера
Стандартний вивід і помилки процесу, з датою і часовим поясом, плюс журнали вебсервера. Тут видно тихий збій: три невдалі спроби доставки з еталонного інциденту були в журналі помилок, а не в якомусь сповіщенні. Ротацію і строк визначають для кожного сервера; це технічні дані, а не вміст облікового запису.
Що ніколи не виходить із сервера
Токени, ідентифікатори каналу та ключі постачальника. Сондa здоров’я відповідає виключно логічними значеннями, саме щоб її міг викликати зовнішній інструмент, не розкриваючи нічого. Те саме правило в сповіщенні: повідомлення містить ідентифікатор запиту та сторінку, а не облікові дані.
Statistici de trafic
Вимірювання трафіку проходить через інструмент аналітики, налаштований на проєкті, і активується після згоди. Реальне налаштування збереження в обліковому записі аналітики — це значення, яке ми читаємо з облікового запису, а не те, яке припускаємо — реєстр обробок цього сайту має його позначеним явно як елемент для уточнення, а не як встановлений факт.
Реєстр обробок
Кожен тип даних, якого торкається сайт, має запис із метою, підставою, категоріями та строком у версіонованому документі поруч із кодом. Коли код змінює строк, документ змінюється в тому самому коміті — інакше політика та графік кажуть різні речі, а помиляється зазвичай документ.

Випадок

Форма, яка відправляла 500 замість лідів, сім годин

Ситуація

Сайт-презентація з контактною формою, нещодавно опублікований. Усі сторінки відповідають 200, панель керування зелена, ніхто нічого не скаржиться. Єдиним каналом, через який запит доходив до команди, було сповіщення в застосунку для обміну повідомленнями.

Що ми збудували

Токен бота сповіщення був відкликаний; інтерфейс постачальника відповідав `401 Unauthorized`. Маршрут форми повертав 500 і нічого не записував. Першим виправленням був не токен, а порядок операцій: запит тепер записується в append-only журнал з власним ідентифікатором *перед* спробою доставки, а результат доставки додається як другий рядок. Поверх цього встановили публічну сондy здоров’я, яка перевіряє токен і можливість запису, окремі коди помилок для «постачальник відповів погано» і «нам бракує облікових даних», timeout у 10 секунд на доставку та приглушення дублікатів у вікні 10 хвилин. Такий самий прохід по коду прибрав і ліміт запитів, який можна було обійти, і руту телефонних викликів, що залишилася відкритою анонімно.

Що вийшло

Сондa тепер відповідає 200 із валідним токеном і записуваним журналом; її можна запитувати з будь-якого зовнішнього сервісу моніторингу, не розкриваючи нічого. Тести на продакшені покрили кожен код відповіді — 400, 405, 500, 502, 503 — а два однакові надсилання повернули той самий ідентифікатор запиту, друге позначене як дублікат, з двома рядками в журналі, а не чотирма. Майбутній збій каналу сповіщень більше не стирає запит: він залишається в журналі, з написаною причиною.

Чого цей випадок не каже

Сондa каже, що доставка зараз можлива, а не що хтось читає сповіщення. Хто їх переглядає і за який час — це рішення команди, а не функція коду. А гілка закінчення терміну запиту до постачальника, хоч і реалізована, не була викликана в тесті — вона потрапляє до тієї самої обробки, що й мережева помилка, яку було протестовано; ми позначаємо її як не відпрацьовану, а не як перевірену.

Питання

Про що нас питають, перш ніж зателефонувати

Що саме ви моніторите?

Шлях запиту до людини, а не доступність сервера. `GET /api/health/contact` перевіряє одним запитом дві речі: що токен каналу сповіщень і далі дійсний — через реальний виклик до провайдера, з timeout 8 секунд — і що журнал запитів можна записати. Він відповідає 200, коли обидва твердження істинні, і 503, коли ні. Ви можете викликати його просто зараз на цьому сайті: він публічний саме тому, що повертає лише логічні значення.

Чому форма могла б упасти, і ніхто цього не помітить?

Тому що видима частина продовжує працювати. Сторінка завантажується, кнопка відповідає, сервер повертає 200 на всіх сторінках — ламається лише фінальна ланка, яка не має інтерфейсу. На цьому сайті токен бота сповіщень було відкликано: провайдер відповідав `401 Unauthorized`, маршрут повертав 500, а запит ніде не записувався. Три реальні збої приблизно за сім годин, видимі лише в журналі помилок процесу. Саме тому журнал тепер пишеться до доставки: навіть якщо сповіщення падає, запит існує.

Що відбувається, якщо сповіщення не вдається після того, як ви виправили?

Запит уже записаний у журнал із власним ідентифікатором, а другий рядок у журналі каже `failed` і причину. Відвідувачу відповідають окремо — «ми отримали запит, але не змогли сповістити» — а не хибним успіхом. Код відповіді розрізняє причину: 502 означає, що провайдер відповів погано, 503 — що в нас бракує креденціалів. О 3-й ночі різниця між ними вирішує, чи дзвонити комусь, чи ні.

Ви оновлюєте залежності автоматично?

Не без воріт. Публікація запускає `npm audit` на рівні `high` і зупиняється на вразливостях цього рівня, якщо продовження явно не підтверджено; спочатку також перевіряється доступна пам’ять сервера, з порогом 500 MB, бо збірка, запущена на тісному сервері, залишає застосунок зупиненим. Збірка робиться з чистого стану: каталог збірки та залежності видаляються, встановлення виконується з файла блокування. Що оновлюється автоматично, а що проходить через людську перевірку, визначається на проєкті, не за замовчуванням.

Як ви знаєте, що публікація не зламала щось інше?

За допомогою скрипта перевірки, запущеного після публікації, який відкриває кожну сторінку продукту й падає, якщо бракує коду 200, якоря запитань, якоря прикладу або канонічної адреси. Потім він перевіряє карту сайту — щоб вона містила кожен продукт і не містила мовних варіантів, які не можна відкрити — і `robots.txt`, щоб не блокував ресурси рендерингу. Кожна перевірка в списку відповідає чомусь, що вже ламалося раніше. Скрипт передається разом із проєктом.

Що ви робите, коли застосунок зависає або споживає пам’ять?

Процес перезапускається автоматично після порога 500 MB, з затримкою 4 секунди між спробами та експоненційним збільшенням, але зупиняється після 10 нестабільних перезапусків у короткому проміжку. Це важливо: цикл падіння, що перезапускається нескінченно, виглядає здоровим у панелі керування й тихо споживає сервер. Ми віддаємо перевагу тому, щоб процес залишався зупиненим і видимим.

Як довго ви зберігаєте дані з форми і хто може їх читати?

24 місяці, застосовані кодом, а не лише задекларовані: поріг — це змінна зі значенням за замовчуванням 730 днів, а старіші файли видаляються під час кожного нового запису, без планувальника, про який можна забути. Каталог має права `0700`, файли `0600`, а шлях лежить поза каталогом версії, щоб історія пережила публікацію. IP-адреса не зберігається повністю: вона обрізається до `/24` для IPv4 і `/48` для IPv6, читаючи останній hop із заголовка, а не перший — перший надсилає клієнт і він нічого не означає.

Ви знаходите й проблеми, на які ми не скаржилися?

Таке трапляється, і зазвичай саме вони найдорожчі. Та сама перевірка коду, що виправила форму, виявила дві речі, про які ніхто не повідомляв: ліміт запитів можна було повністю обійти, бо читався перший елемент із заголовка перенаправлених адрес — саме той, який надсилає клієнт; і маршрут, що ініціював телефонні виклики, був відкритий анонімно, за наш кошт, хоча компонент, який його використовував, уже було демонтовано. Обидва були виправлені й перевірені на продакшені. Що ми не можемо обіцяти — що знайдемо все; що можемо обіцяти — що повідомимо і про те, чого ви не просили.

Що не покриває обслуговування?

Це не сервіс безпеки з цілодобовим моніторингом і не центр операцій. Ми не гарантуємо відсоток доступності без вимірювання, яке його підтверджує — і, щоб було ясно, наразі ми не публікуємо таку цифру для власного сайту. Ми не беремо на себе відповідальність за сторонню платформу, до якої не маємо доступу. І ми не трактуємо сторінку, що відповідає 200, як доказ того, що все працює: саме це було проблемою, з якої почалося все написане вище.

На чому ґрунтуються твердження вище (13 джерел)
  1. Проба доставки відповідає 200 у production: `{"ok":true,"telegram":{"configured":true,"credentialsValid":true},"leadJournal":{"writable":true}}`https://www.megapromoting.com/api/health/contact · 2026-09-06
  2. Активні заголовки безпеки на production: HSTS `max-age=63072000; includeSubDomains; preload`, `X-Content-Type-Options: nosniff`, `X-Frame-Options: SAMEORIGIN`, `Referrer-Policy: strict-origin-when-cross-origin`, `Permissions-Policy` з `microphone=(self)`https://www.megapromoting.com/servicii · 2026-09-06

11 з них — це код і файли з наших репозиторіїв. Ми не публікуємо їхні назви чи номери рядків: разом, на одній сторінці, це надто точно описало б, як побудовані системи, які належать не лише нам. Ми проходимо їх разом із вами, у репозиторії, за запитом — перевірка залишається можливою, просто вона відбувається в розмові.

Що вам хотілося б налагодити?

Розкажіть про свій процес. Разом визначимо, що варто збудувати, що можна підключити і як перевіримо результат.

Поговорімо