Кейс

Один процесс вместо трёх интеграций

Сервис уведомлений на сервере студии сводит почту, звонки и заявки в один Telegram-канал и держит кнопку обратного звонка. Делает это один процесс, а не три отдельные интеграции. Ограничение среды жёсткое: на сервере Python 3.9 и нельзя ставить пакеты, поэтому сервис написан на одной стандартной библиотеке.

Python 3.9IMAPTelegram Bot APIТелефонияИнтеграции

Среда, в которой пришлось поместиться

На сервере доступен Python 3.9, и ставить пакеты нельзя. Это условие задачи, а не временное неудобство, поэтому сервис обходится стандартной библиотекой без единой внешней зависимости: чтение почты по IMAP, запросы к API телефонии и Telegram, работа с файлом состояния — всё это есть в стандартной поставке.

Форма тоже простая: один процесс делает три вещи — опрашивает почтовый ящик по IMAP, забирает статистику телефонии и держит long polling Telegram, чтобы обрабатывать нажатия на кнопки. Одна единица запуска, которую ставит и перезапускает скрипт деплоя.

Почему звонки опрашиваются, а не приходят вебхуком

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

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

Письма без дублей

Раз в 60 секунд сервис читает непрочитанные письма, отправляет их в группу и помечает прочитанными: состояние ящика само становится отметкой о том, что уже разобрано.

Отдельно обрабатывается один случай — письма от собственного адреса сайта пропускаются. Это дубль заявки, которую обработчик формы уже отправил в канал напрямую; без фильтра одна заявка приходила бы дважды, от формы и из почты.

Кнопка перезвона и способ её отладить

Нажатие на кнопку в канале инициирует обратный звонок через API телефонии. Разрешены только заранее заданные цели: произвольные номера из payload отбрасываются, потому что payload приходит извне и доверять ему как источнику номера нельзя.

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

Тем же способом выяснилось ещё одно ограничение: первым плечом callback можно ставить только свой номер, SIP или внутренний номер АТС, а чужой мобильный площадка отвергает. Текст ошибки при этом говорит про SIP и уводит в сторону.

Деплой и состояние

Выкладка сделана одним скриптом: прогон самопроверки, выкладка кода, установка и перезапуск сервиса. Есть режим --selftest, чтобы прогнать самопроверку отдельно, и --once — один проход без цикла, когда нужно посмотреть на поведение сервиса, а не оставлять его работать.

Состояние лежит в файле: offset Telegram и идентификаторы последних 500 звонков. Этого хватает, чтобы после перезапуска сервис не переспрашивал уже обработанные обновления и не присылал звонки повторно, и не нужна ни база, ни внешнее хранилище.

01Почтовый ящикIMAP, непрочитанное раз в 60 секунд
02Статистика телефонииОпрос за последние 3 часа, без вебхука
03Один процессПочта, звонки и long polling Telegram
04Фильтр дублейПисьма с адреса сайта пропускаются
05Сообщение в группуЗаявки, письма и звонки в одном канале
06Кнопка перезвонаCallback только на заранее заданные цели
07Файл состоянияOffset и id последних 500 звонков

Поток данных сервиса уведомлений

Направления по теме
Другие кейсы
Контакты

Нужно похожее? Опишите задачу — разберём и предложим решение.

Нажимая кнопку, вы соглашаетесь на обработку данных.
Позвонить и написать+7 967 555-81-17hello@fodastudio.ruTelegram
ОфисыМосква, Большая Пионерская, 24Москва, 5-й Донской проезд, 19Санкт-Петербург, Кузнечный пер., 13