Когда заменить внешний сервис своей инфраструктурой: кейс миграции WhatsApp-шлюза за один день

Я уволил сервис, который три года был в центре нашей операционки. wa-sms.com — внешний WhatsApp-шлюз, через который ходили все сообщения гостям и уборщикам 16 активных вилл: заселения, расписания уборок, отчёты по объектам, координация с персоналом. Каждый день, каждое бронирование. Однажды утром зашёл в настройки, удалил номер Solar Property из wa-sms — и всё немедленно сломалось. Исходящие гостям перестали уходить, входящие от уборщиков перестали приходить, цепочка из 4 скриптов начала молча падать.

Это было запланировано. Месяц мы строили замену — собственный WhatsApp-узел на Baileys, который уже работал у нас для модерации чатов аренды. Переключение прошло через 3 фазы, суммарно заняло один рабочий день и сохранило 1500 строк бизнес-логики без единого касания. Вот как именно это устроено и, главное, как понять когда такой переезд вообще стоит делать.

Важная оговорка: я не призываю строить всё самому. Внешние сервисы существуют именно для того, чтобы снижать порог входа и освобождать ресурсы от инфраструктурных задач. wa-sms был правильным решением в 2023 году — быстрый старт, готовый API, не нужно держать собственный WhatsApp-узел. Вопрос не «строить или платить», а «когда именно менять». И у этого вопроса есть конкретные ответы.

Этот день был нетипичным не тем, что мы что-то сломали — а тем, что за один день одновременно завершили инфраструктурную миграцию, запустили модераторский апгрейд для 4 чатов суммарно более 1770 участников, сделали ban-by-phone инструмент, открыли публичную community-страницу и переупаковали оффер для клиента. Такое количество завершённых задач за рабочий день — следствие именно архитектурных решений, которые разберём ниже: когда переход не требует переписывать тысячи строк, он перестаёт быть страшным.

Как зависимость от внешнего сервиса выглядит изнутри

wa-sms не был плохим продуктом. В 2023 году, когда мы начали автоматизировать операционку, это было правильным решением: быстрый старт, готовый API, платишь и не думаешь об инфраструктуре. Именно так и должны работать внешние сервисы на старте — снижать порог входа и давать возможность сосредоточиться на бизнес-логике, а не на техническом стеке.

Проблема появилась не в цене и не в надёжности. Проблема — в контроле. Три конкретных сигнала, по которым я понял что пора строить собственное:

Первый: невозможность добавить интерактивность. Baileys, наш собственный WhatsApp-узел для модерации чатов, умел то, чего wa-sms физически не мог — интерактивные сообщения с кнопками, листы меню, реакции на события в чате. Каждый раз, когда нужно было добавить диалог с ответными опциями для гостя или уборщика, упирались в ограничения внешнего API. Это не баг wa-sms, это архитектурный потолок продукта, который изменить нельзя.

Второй: два разных формата идентификаторов. Baileys идентифицирует пользователей через JID-строку вида [email protected]. wa-sms работает с обычным телефонным номером в формате +79001234567. Каждый скрипт, который касался обеих систем, нёс внутри логику конвертации — написанную по-разному, без единого стандарта, с разной обработкой крайних случаев. Это архитектурный мусор, который накапливается с каждой новой интеграцией и делает код всё сложнее сопровождать.

Третий: чужой баг — ваша проблема. Один раз wa-sms не доставил сообщение уборщику о заселении. Не наш баг, не наша ошибка — но наш гость встал перед закрытой виллой. Внешний сервис означает: если у них авария в 23:00 — вы просто ждёте. Для операционки с живыми гостями и реальными бронированиями это неприемлемый уровень зависимости от чужой инфраструктуры.

Когда все три сигнала появились одновременно — вопрос стал не «менять или нет», а «насколько сложно перейти именно сейчас». Ответ оказался: один день. Именно поэтому переехали.

Архитектура миграции: три фазы без касания бизнес-логики

Главный страх при замене глубоко встроенного сервиса — объём работы. В нашем случае 4 скрипта использовали wa-sms для исходящих сообщений. Входящие от уборщиков обрабатывались ещё через несколько обработчиков в разных частях системы. Суммарно — около 1500 строк бизнес-логики, которую в лобовом подходе пришлось бы полностью переписывать под новый формат Baileys.

Мы не тронули ни одной из этих строк. Вот как именно.

Фаза 1: расширение схемы базы данных

Первая проблема была не в коде, а в данных. В нашей базе телефонные номера хранились в формате +79001234567. Baileys для отправки требует JID-строку в формате [email protected]. Каждый из 4 скриптов конвертировал номер в JID на лету — своим способом, без единого стандарта, с разной обработкой форматов с плюсом и без плюса, с разными крайними случаями для нестандартных номеров.

Решение: добавить в таблицу contacts новый столбец whatsapp_jid. Заполнить его автоматически для всех существующих записей через migration-скрипт. Теперь любой скрипт получает готовый JID прямо из базы — не вычисляет сам, не рискует ошибиться в конвертации. Миграция заняла около 40 минут, включая проверку 312 контактов с нестандартными форматами и ручную валидацию крайних случаев.

Параллельно в схему добавили индекс по JID для быстрого поиска — поскольку входящие сообщения от Baileys приходят именно с JID, и нам нужно быстро матчить их к нашим контактам без полного сканирования таблицы.

Фаза 2: универсальный handler для исходящих

Четыре скрипта отправляли сообщения через wa-sms по-разному: один через requests напрямую к API, второй через обёртку в utils, третий через специальную функцию отправки, четвёртый — через HTTP-клиент из другого модуля. Каждый знал адрес wa-sms и формат запроса. При смене транспорта каждый нужно было бы обновить отдельно, тестировать отдельно, отлаживать отдельно.

Вместо того чтобы обходить каждый из 4 скриптов, написали один универсальный handler: send_wa_message(phone, text, media=None). Внутри — логика выбора транспорта через флаг в конфиге USE_BAILEYS. Когда флаг False — сообщение уходит через wa-sms. Когда True — через Baileys. Все 4 скрипта заменили свои вызовы на единый send_wa_message(). Тестирование нового транспорта без риска для production: просто переключаем флаг и смотрим на 10 тестовых сообщениях.

Побочный эффект, который оказался важнее самой миграции: теперь у нас единая точка логирования всех исходящих WhatsApp-сообщений. До этого каждый скрипт логировал по-своему — или не логировал вообще. Теперь каждое отправленное сообщение, его статус доставки, время отправки и получатель — в единой таблице wa_outgoing_log. Это и дебаг, и аудит, и метрики за один заход.

Фаза 3: адаптер для входящих

Входящие сообщения — сложнее исходящих. wa-sms присылал вебхуки в своём JSON-формате: конкретная структура полей, конкретные названия ключей, конкретный формат времени. Baileys присылает события в другом формате — другие имена полей, другая структура вложенности, другой способ передачи медиафайлов. Около 1500 строк обработчиков в нашей системе ожидали именно формат wa-sms.

Переписывать 1500 строк — это минимум неделя работы и высокий риск регрессий в production. Мы написали адаптер-прослойку в 80 строк. Baileys-событие поступает к адаптеру, адаптер преобразует его в формат wa-sms и передаёт дальше — в существующие обработчики. С их точки зрения ничего не изменилось: они по-прежнему получают сообщения в привычном JSON-формате с привычными именами полей. Адаптер — единственное новое, что появилось в системе.

Соотношение: 80 строк нового кода вместо потенциальных 1500 строк правок. Именно это соотношение делает миграцию «за один день» реальной, а не маркетинговым преувеличением.

Финальный переключатель — флаг USE_BAILEYS = True в конфиге — пока не дёрнут. WhatsApp разморозит бренд-номер после отвязки от wa-sms за 6-8 часов. Вся инфраструктура готова, протестирована на staging-окружении, ждёт одного изменения в конфигурационном файле.

Параллельно: ребрендинг и апгрейд модератора 4 чатов аренды

Пока шла миграция инфраструктуры, параллельно шёл ребрендинг и функциональный апгрейд наших WhatsApp-чатов аренды. У Solar Property 4 группы по бюджетам: 0-5 миллионов рупий в месяц, 5-10 миллионов, 11-20 миллионов и 21-99 миллионов. До этого дня каждый чат жил сам по себе: разные названия, разные аватарки, никакой перекрёстной навигации между бакетами.

Проблема очевидна: человек ищет виллу за 8 миллионов рупий в месяц, попадает в чат 5-10, размещает объявление — и не знает, что для 8 миллионов логичнее чат 11-20. Мы теряем качество объявлений и потенциально аудиторию только из-за отсутствия навигации между чатами.

Теперь все 4 чата переименованы под единый бренд «Bali Villas», получили единый визуальный стиль и закреплённые сообщения с явными ссылками на соседние бакеты. Закреп содержит: диапазон бюджета чата, ссылки на чаты меньшего и большего бюджета, правила модерации одной строкой на английском и индонезийском.

Модератор получил три новых функции, которые меняют логику взаимодействия с аудиторией:

Price detection с grace period. Если пост с объявлением виллы не содержит цены — бот публично отвечает автору с тегом и даёт 2 минуты на правку. Если автор делает edit и добавляет цену в течение этого времени — бот замечает изменение и не удаляет пост. Раньше такая логика требовала ручного контроля или молчаливого удаления без объяснений.

Smart redirect вместо молчаливого удаления. Если вилла за 25 миллионов попадает в чат 11-20 — бот не просто удаляет пост без объяснений. Бот пишет автору: «твой пост точнее зайдёт в чат 21-99, вот прямая ссылка». Это разница между модерацией как наказанием и модерацией как сервисом.

Кросс-чатовый бан. 3 предупреждения в любом из 4 чатов — авто-бан из всех четырёх сразу. До этого каждый чат был изолирован: можно было получить бан в одном и продолжить спамить в трёх других. Теперь черный список единый для всего бренда «Bali Villas».

Ban-by-phone: от ручного UI к одной команде

Параллельно с модератором собрали отдельный инструмент — ban-by-phone бот. Работает просто: пишешь в личный канал телефонный номер — бот за секунду вытаскивает участника из всех 4 чатов и заносит в глобальный чёрный список. Статус операции возвращается обратно: из каких чатов удалён, добавлен ли в blacklist, были ли ошибки.

Раньше бан делался через UI мессенджера — вручную, последовательно, в каждом чате отдельно: найти участника в списке, открыть профиль, выбрать действие, подтвердить. Умножить на 4 чата. Это 4-5 минут при хорошем знании интерфейса, и «непонятно как» для тех, кто с WhatsApp-группами работает редко. Теперь весь процесс — 5 секунд и одна команда.

К каждому подозрительному посту в мой личный канал прилетает карточка с двумя кнопками: «удалить пост» и «забанить везде». В сами чаты руками не лезу — всё через интерфейс бота. Это важно: когда в операционке несколько чатов с разными людьми, прямое вмешательство в UI мессенджера — это источник ошибок.

Отдельный результат того же дня — инвентаризация inactive-участников. Экспортнул историю 3 чатов в текстовый файл, прогнал парсером статистики сообщений: суммарно 1770 участников в трёх чатах написали за всю историю ровно одно сообщение или не написали ни одного. В самом большом чате до лимита WhatsApp в 1024 участника оставалось совсем немного свободных мест. Вычистка 1770 кандидатов — не наказание, а освобождение мест под тех, кто реально пользуется чатом.

Community-страница: публичная оферта как SEO и страховка

Описание группы в WhatsApp лимитировано по символам. У нас 4 публичных WhatsApp-чата плюс Telegram-зеркала — хотелось одно место со всеми правилами, ссылками и юридическим позиционированием. Решение: отдельная страница на сайте компании.

Сделали solarpropertybali.com/community/ — на русском и английском. На странице: список всех чатов с описанием бюджетных диапазонов и прямыми ссылками, блок «как мы модерируем» с конкретными примерами действий бота, и публичная оферта на 7 пунктов. Ключевой пункт: Solar Property является хостом площадки, а не стороной сделки между арендодателем и арендатором.

Это решает три задачи одновременно. SEO-точка для тех, кто ищет чаты аренды вилл на Бали — по ключевым словам «WhatsApp чат аренда вилл Бали», «Bali Villas chat», «чат аренды недвижимости Бали». Единое место для актуализации правил: вместо правки 4 закрепов в 4 чатах одновременно — одна страница, одно обновление. Юридическая страховка на случай «вы нам обещали» или «вы несёте ответственность» — страница явно описывает роль платформы и границы ответственности.

B2B-инсайт: milestone-pricing вместо скидки

Отдельная история того же дня — разговор с клиентом по медицинскому проекту. Исходное предложение: 180 тысяч рублей за внедрение системы автоматизации. Клиент заинтересован, компетентен, понимает что задача нужна — но cashflow зажат до середины лета. Такая сумма авансом в текущей ситуации не лезет.

Стандартный ответ в такой ситуации — скидка или ожидание. Оба варианта теряют либо выручку, либо время. Я предложил третий вариант: milestone-pricing.

Вместо полного внедрения за 180 тысяч — один модуль за 90 тысяч, разбитый на 3 равных платежа по 30 тысяч. Привязка не к календарю, а к проверяемым событиям: первый платёж при старте работ, второй при запуске пилота в production через 3 недели, третий после 30 дней работы на реальных цифрах. На любой стадии клиент может остановиться, потеряв максимум 30 тысяч — стоимость одного завершённого этапа.

Это принципиально другая структура риска для обеих сторон. Клиент не берёт обязательство на 90 тысяч заранее — он берёт обязательство на 30 тысяч и проверяет реальный результат перед следующим шагом. Я получаю старт проекта вместо отказа, и при этом не теряю в итоговой выручке — три платежа по 30 это те же 90 тысяч.

Большой оффер пугает не ценой, а неопределённостью результата. Разбивка на этапы с конкретным проверяемым результатом за каждый снимает страх — и при этом не снижает итоговую ценность для вас как исполнителя. Именно эту модель я стал применять в последние несколько месяцев в ситуациях, где клиент технически готов, но психологически не готов к большому авансу.

Критерии: когда строить самому, а когда платить внешнему сервису

По итогу этого дня могу сформулировать критерии, по которым я принимаю решение о замене внешнего сервиса. Это не теория — это конкретная рабочая матрица, выведенная из реального опыта.

Платишь внешнему сервису, пока выполняется хотя бы одно из:

  • Интеграция заняла часы, а не недели — сервис снизил порог входа, как и должен был.
  • Возможности сервиса покрывают ваши потребности с запасом, без обходных решений.
  • Баг у них — это их проблема, а не ваша операционная авария.
  • Стоимость сервиса меньше зарплаты разработчика на поддержку аналогичного стека.
  • У вас нет смежной инфраструктуры, которую можно расширить до нужных функций.

Строишь сам, когда:

  • Сервис ограничивает то, что критически нужно — кнопки, форматы, скорость — и обходного пути нет.
  • Вы уже держите аналогичный стек для другой задачи. В нашем случае: Baileys уже работал для модерации чатов. Расширить его до функции шлюза — это не «построить с нуля», это «добавить роль существующему компоненту».
  • Потеря контроля стоит дороже, чем инженерный день. Авария у внешнего сервиса в 23:00 с живыми гостями — стоимость зависимости измеряется не деньгами, а репутацией.
  • Миграция может быть сделана через адаптерный слой без переписывания бизнес-логики. Это самое важное условие: если для перехода нужно трогать 1500 строк — это месяц работы. Если нужно написать 80-строчный адаптер — это один день.

Последний критерий — ключевой. Вопрос не «стоит ли переезжать», а «насколько сложно переезжать именно сейчас при текущем состоянии архитектуры». Хорошо написанные системы позволяют замену транспортного слоя без касания бизнес-логики. Плохо написанные — требуют переписывать всё при каждом изменении инфраструктуры. Инвестиция в адаптерные слои и единые точки входа окупается не при первой миграции, а при каждой последующей.

В нашем кейсе с wa-sms ответ оказался «один рабочий день». Именно поэтому мы переехали в конкретный день, а не откладывали на «когда-нибудь потом». Три месяца назад ответ был бы «три недели» — потому что не было Baileys-инфраструктуры рядом. Сейчас была — и это изменило всё.

Если вы строите операционную автоматизацию и хотите не попасть в ту же ловушку зависимостей — смотрите не только на то, что сервис умеет сейчас, но и на то, что вы не сможете сделать, когда он перестанет справляться. Архитектурный долг от неправильно выбранных внешних зависимостей накапливается медленно, но платить по нему приходится в самый неудобный момент.

Следующий вопрос — логичный: сколько у вас в операционке сервисов, без которых всё встанет завтра? И сколько из них вы могли бы заменить собственным стеком за один рабочий день, если бы уже держали нужную инфраструктуру рядом? Это не призыв строить всё самому — это призыв знать ответ на этот вопрос. Сервис, без которого всё встанет и который вы не можете заменить — это риск, который живёт в вашем бизнесе каждый день, независимо от того, думаете вы о нём или нет.

О том, как мы выстраиваем автоматизацию операционки через WhatsApp-вебхуки и уведомления, я писал подробно в статье про WhatsApp-вебхуки и автоматизацию уведомлений. А про принятие решений об автоматизации без лишних инвестиций — в реальном кейсе автоматизации операционки на Бали.

Частые вопросы

Когда стоит заменять внешний сервис собственной инфраструктурой?
Переход оправдан, если выполняются три условия одновременно: сервис ограничивает что-то критически нужное (форматы, скорость, кнопки), вы уже держите аналогичный стек для другой задачи, и миграцию можно сделать через адаптерный слой без касания бизнес-логики. В кейсе Solar Property все три условия выполнялись: Baileys уже работал для модерации чатов, wa-sms не поддерживал интерактивные сообщения, и адаптер для входящих занял 80 строк против 1500 строк потенциальных правок.
Сколько времени занимает миграция с внешнего WhatsApp-шлюза?
При грамотной архитектуре — один рабочий день. Три фазы: расширение схемы базы данных под новый формат идентификаторов (40 минут, 312 контактов), написание универсального handler для исходящих (4 скрипта в одну точку входа), прослойка-адаптер для входящих вебхуков (80 строк). Финальный переключатель — одна строка в конфиге. Ждать приходится только технически: WhatsApp размораживает номер после отвязки от старого сервиса за 6-8 часов.
Что такое Baileys и безопасно ли использовать его в бизнесе?
Baileys — open-source библиотека на TypeScript для работы с WhatsApp Web API. Используется как альтернатива официальному WhatsApp Business API для случаев, когда нужны функции недоступные в официальном API: нестандартные сообщения, интеграция с произвольным стеком, полный контроль над форматом данных. В Solar Property Baileys работает в production для управления 4 чатами аренды с суммарной аудиторией более 1770 участников и обрабатывает входящие сообщения в реальном времени уже несколько месяцев.
Как переупаковать дорогой оффер если у клиента зажат бюджет?
Milestone-pricing: вместо одного большого платежа — серия малых, привязанных к проверяемым результатам, а не к календарю. В реальном кейсе: оффер 180 тысяч рублей переупакован в один модуль за 90 тысяч, разбитый на 3 платежа по 30 тысяч. Старт, запуск пилота в проде через 3 недели, 30 дней работы на цифрах. На любом этапе клиент может выйти, потеряв максимум 30 тысяч. Это снимает страх перед большим размером без снижения итоговой выручки.

Читайте также

Подписаться на блог в Telegram

Читайте свежие кейсы об AI-автоматизации, системной архитектуре и масштабировании бизнеса.

Подписаться