Перейти к содержимому
VC
Кейс 4 из 33 · Интернет-торговля · поддержка

Пропущенные звонки: авто-SMS с реальным сроком отгрузки вместо шаблона

Телефония сообщает о пропущенном звонке → система находит клиента и его заказы в двух магазинах Shopify → уходит SMS с настоящим статусом отгрузки. Плюс страница «Пропущенные» в CRM: кто звонил, что у него в заказах и готовый текст ответа.

Отрасль
Интернет-торговля, покупатели в США
Стек
Python · вебхуки телефонии · Shopify API
Сроки
≈ 2 недели
Итог
≈200 звонков/мес без ответа человека → авто-ответ
01 · Боль

Покупатель звонит в свой рабочий день — а это ночь у поддержки

Бренд товаров для активного отдыха из США, два магазина на Shopify, команда поддержки в другом часовом поясе. Покупатель набирает номер в середине своего рабочего дня — в этот момент у команды вечер или ночь. За 90 дней журнал звонков держит 739 входящих: 496 пришли в объявленное окно приёма, и 310 из них остались без ответа.

Пропущенные нигде не собирались. Утро начиналось с «простыни» уведомлений в приложении телефонии: медиана 5 за ночь, в пиковые дни доходило до 25. Разобрать эту ленту руками — отдельная работа, которую никто не делал системно.

Самая частая тема звонка — «где мой заказ». То есть люди звонили именно потому, что не видели статуса: дорогой товар, длинный срок производства, а в письмах и в личном кабинете — тишина.

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

02 · Решение

Вебхук → заказ в Shopify → SMS с настоящим сроком

Приёмник вебхуков call.completed на стороне CRM: проверка подписи, отсечение лишних типов событий, защита от повторной обработки одного и того же звонка (иначе клиент получает два перезвона), запись в журнал. Дальше по номеру звонящего поднимается клиент и его заказы в обоих магазинах — и собирается ответ.

01
Сигнал о звонке

Событие call.completed от облачной телефонии: проверка подписи, фильтр типов события, защита от повторной обработки по номеру звонка

02
Поиск клиента

Номер звонящего → покупатель и его заказы в двух магазинах Shopify

03
Лестница ответа

От «реальный срок отгрузки» вниз до нейтрального запасного варианта

04
Отправка SMS

Тихие часы, пауза между сообщениями на один номер, ограничение частоты, режим черновика

05
Очередь в CRM

Страница «Пропущенные»: кто звонил, его заказы, готовый текст, отметка «разобрала»

Главная находка: даты отгрузки в заказе нет

Ключевой вопрос всего контура — откуда взять срок, который не стыдно отправить покупателю. Оказалось, что календарной даты отгрузки в заказе Shopify нет нигде. Проверены и отброшены четыре других источника — ни один не даёт обещанный клиенту срок.

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

Лестница ответа: от полезного к запасному

Ответ формируется ступенями — от максимально конкретного к нейтральному, и ни одна ступень не имеет права соврать:

  • клиент и заказ найдены, обещанная дата в будущем → называем дату
  • обещанная дата уже прошла → дату НЕ повторяем, честно пишем, что уточняем со складом
  • заказ есть, срока определить нельзя → пишем про заказ без выдуманных обещаний
  • клиент по номеру не найден → нейтральный ответ, звонок уходит в очередь на человека

Правило «просроченную дату не повторяем» проверено на исторической выгрузке (более 11 тыс. заказов): ни один старый застрявший заказ не получает будущей даты.

Предохранители на отправке

Авто-SMS — это то, что уходит от имени бизнеса без человека в контуре. Значит, ограничители важнее самой генерации:

  • тихие часы: отправка только в окне 11:00–21:00 по Нью-Йорку — безопасно для всех континентальных поясов США; Аляска и Гавайи считаются отдельно
  • кулдаун по номеру — повторный звонок не превращается в поток сообщений
  • ограничение частоты на контур в целом
  • режим черновика: всё считается и логируется, но наружу не уходит — и отдельный явный флаг боевой отправки

Страница «Пропущенные» в CRM

Автоответ снимает первое касание, но разбирать звонки всё равно человеку. Вместо ленты уведомлений — одна страница: кто звонил и когда, номер ссылкой tel:, найденный клиент, его заказы с состоянием («не отгружен · 82 дня»), готовый текст ответа и отметка «разобрала».

Отдельно заложено поведение при частичном сбое: битая строка очереди не роняет страницу, а сбой чтения пишет «очередь не прочиталась». Стояло бы там «звонков нет» — операционист спокойно закрыл бы вкладку и ушёл.

03 · Стек

Ничего лишнего — контур должен пережить ночь без дежурного

Python stdlib

Приёмник вебхуков и HMAC-проверка подписи без веб-фреймворка

Облачная телефония

Сигналы о звонках, отправка SMS, расшифровки голосовой почты

Shopify Admin API

Поиск покупателя по номеру и его заказов в двух магазинах

Языковая модель

Пишет текст ответа под конкретный заказ

systemd

Сервис приёмника, автоперезапуск, журнал

pytest

Зелёных тестов по контуру звонков и аналитики — 2437–2471: ступени ответа, тихие часы, защита от повторов, разбор события

PythonWebhooksHMACSMS APIShopify Admin APILLMsystemdpytest
04 · Результат

Сначала разбор данных, потом код

Реальный масштаб
108 ~200

пропущенных в месяц — задача оказалась в 1,6 раза больше исходной оценки

Канал в работе
29%

пропущенных уже имеют текстовую расшифровку голосовой почты — по ней видно, о чём был звонок

Стоимость отправки
62%

сообщений стали односегментными после правки на один символ

Работа началась с выгрузки телефонии. Разбор показал, что задача в 1,6 раза больше исходной оценки, что 29% пропущенных уже имеют расшифровку голосовой почты — сегодня по этим расшифровкам видно, о чём был звонок, — и что 36% клиентских SMS оставались без человеческого ответа. Три факта, которых не было ни в одном отчёте.

Попутно починены пять поломок, три из них держали контур мёртвым

  • поиск клиента падал на несовпадении сигнатур — каждый звонок считался «клиент не найден»
  • разбор события брал не тот уровень вложенности и молча выбрасывал любой пропущенный
  • номер звонящего искался в поле, которого в вебхуке нет
  • карточка автоответов уронила всю страницу звонков — ту, с которой два оператора начинают утро: код был написан по угаданным именам полей, а заглушка в тесте сочинена такой же выдуманной и потому подтверждала догадку автора
  • агрегаты недозвонов были написаны и покрыты тестами, но их никто не вызывал: вопрос руководителя «сколько теряем на недозвонах» оставался без ответа, хотя ответ уже лежал в коде

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

Как мы убеждаемся, что дверь вебхуков открыта

Тишина в контуре читается двояко: звонков действительно нет либо события перестали доходить. Пока эти два состояния выглядят одинаково, любой отчёт о спокойной ночи ничего не стоит. Поэтому у контура есть отдельные проверки самой двери:

  • Приёмник 18 суток исполнял код почти трёхнедельной давности при счётчике перезапусков ноль, и совпадение хеша файла на диске с репозиторием это никак не опровергало: сверять надо время старта процесса со временем изменения файлов. Следствие было ровно то, о чём кейс, — поиск клиента по номеру уходил удалённым запросом на другой сервер, при пустом ответе функция молча отдавала пустой результат, и звонок становился «клиент не найден». Перед перезапуском код сверен с репозиторием побайтно и прогнаны 732 зелёных теста по контуру
  • Датчик «а были ли звонки» опирался на проверочный метод API поставщика телефонии, который отдаёт HTTP 400 без массива данных: наивный разбор печатает «0 звонков», и ошибка визуально неотличима от доказательства, что звонков не было. Вскрылось контрольным окном, где звонки заведомо были: тот же метод дал «0». Рабочим датчиком назначен другой метод, а дверь вебхуков проверяется четырьмя независимыми способами, ни один из которых не опирается на очередь
  • Растяжка записана заранее: «нет ни одного события к 18:00 понедельника — идём в настройки вебхуков». Идти не пришлось: календарная гипотеза подтвердилась в точности — пятница 4 события, суббота 0, воскресенье 3, понедельник 6, и 41 час тишины объяснился выходными
  • Сторож почтового ответа поставщика прочитал нужное письмо, записал его в своё состояние и никого не разбудил: фильтр отписок искал фразу по всему телу, а система тикетов подставляет всю переписку ниже. Починено разбором только новой части письма. Там же вскрылось второе: в логе сторожа было 50 строк и все до одной ошибки, потому что успешный прогон не печатал ничего — «сработал и тихо» и «не работает вовсе» выглядели одинаково

Это то же правило, что на странице очереди: «не прочиталось» никогда не показывается как «пусто».

Отдельная находка — деталь на один символ с прямым влиянием на счёт за SMS: длинное тире в шаблонах выводило сообщение за пределы базового алфавита и переводило его в двухбайтную кодировку, из-за чего 100% сообщений уходили минимум в два сегмента. Замена длинного тире на дефис делает односегментными 62%.

Владелец бизнеса включил боевую отправку, первые SMS доставлены. Каждый пропущенный звонок теперь попадает в очередь в CRM и получает ответ с настоящим статусом отгрузки.

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

Контур проработал достаточно, чтобы журнал вебхуков ответил на вопрос «в какие часы держать линию». Замер сделан по журналу, лежащему на диске, без единого обращения к чужому API: дубли убираются по идентификатору звонка, побеждает последнее событие с финальным статусом, часовой пояс клиента определяется библиотекой разбора номеров. Всего 739 входящих за 90 дней, пояс определён у всех. Считать по своему времени было нельзя: восемь утра на восточном побережье — это пять утра на западном, и вывод получился бы обратным.

  • пик обращений — 11:00 по местному времени клиента; окно 09:00–12:00 даёт 38% звонков
  • 82% звонков приходится на рабочее время; вечер даёт 5%, ночь — 1%: ходовая версия «звонят вечером» не подтвердилась
  • понедельник почти вдвое тяжелее пятницы
  • 496 звонков из 739 пришли в объявленное окно приёма, и 310 из них пропущены
  • в дневном отчёте 7 недозвонов из 9 пришлись на заявленные часы приёма

Отсюда управленческая цифра, ради которой всё считалось: сдвиг закрытия на два часа, до 17:00, добрал бы 133 звонка за 90 дней — около 44 в месяц. Праздники в расчёте берутся формулой, потому что таблицу с датами забудут продлить.

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

Карточка «Вчера» на странице звонков

Рядом с очередью встала сводка за прошедшие сутки: сколько автоответов ушло, сколько не ушло и по каким причинам. Карточка собирается из полного набора источников, и на этом держится проверка: полный вызов даёт «не ушло 2» с двумя причинами, урезанный — «не ушло 0». Расхождение сразу показывает подмену источников заглушками.

05 · Применимость

Где ещё ложится та же методология

За кейсом стоит типовая задача «событие в канале связи → подтягиваем контекст клиента из учётной системы → отвечаем фактом». Та же архитектура применима везде, где обращения приходят вне рабочих часов:

  • Интернет-магазин с длинным сроком доставки — «где мой заказ» закрывается статусом из учётки
  • Сервис и ремонт — пропущенный звонок → SMS со стадией заказ-наряда и реальной готовностью
  • Клиники и салоны — звонок вне часов приёма → подтверждение записи или ближайшее окно из расписания
  • Бизнес с покупателями в другом часовом поясе — ночная смена заменяется контуром, который знает факты о заказе
  • Любой входящий канал — почта, мессенджер, форма: меняется только источник события, лестница ответа и предохранители остаются
Что переиспользуется на следующих проектах
  • Приёмник вебхуков с проверкой подписи, фильтром типов событий и защитой от повторной обработки
  • Лестница ответа с явным запретом называть просроченную дату — обкатывается на исторической выгрузке до запуска
  • Набор предохранителей отправки: тихие часы по часовому поясу клиента, пауза между сообщениями на один номер, режим черновика и отдельный переключатель боевого режима
  • Страница-очередь с готовым текстом ответа и деградацией вместо падения: «не прочиталось» никогда не показывается как «пусто»
Похожая задача?

Если у вас копятся пропущенные — сначала замер, потом автоматизация

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

Готовы начать?

Аудит за 9 900 ₽ — с конкретным отчётом и сметой

Расскажу что внедрить в вашем бизнесе в первую очередь, какая будет окупаемость, и нужен ли вообще AI для вашей задачи (иногда — нет).

Или просто напишите свой вопрос — отвечу в течение 2 часов