Перейти к основному контенту

SMS-шлюз: beeline-service

Для разработчиков. Состояние на 28.09.2026. Переменные, все ручки и выкатка по шагам — README репозитория Buharestskie/beeline-service. Как лендинги работают целиком — статья о квиз-лендингах в этой же книге.

Коротко

beeline-service — общий шлюз исходящих SMS. Сервис присылает номер и готовый текст, шлюз проверяет, что номер российский мобильный, применяет антиспам-лимиты и отдаёт сообщение Билайну (A2P) или хабу МегаФона. Шаблонов у шлюза нет: текст целиком приходит от вызывающего. Снаружи шлюз доступен по адресу https://donula26.ru/service/beeline, пускает только адреса из своего списка доступа.

Кто вызывает

Сейчас — три квиз-лендинга, которые подтверждают телефон посетителя кодом из SMS («Ваш код подтверждения: 1234»):

ЛендингЧто передаёт
do-nula-refinancing-2phone, message, ip посетителя. lead_id не передаёт никто
bfl_fspp
mfcspisan (на стороннем сервере)

mfctest шлюз не вызывает: SMS-подтверждения у него нет, после телефона посетитель уходит в мессенджер. Все вызывающие ходят через nginx по публичному адресу, поэтому адрес каждого нового вызывающего нужно заранее внести в ALLOWED_CALLER_CIDRS, иначе он получит 403 Caller IP is not permitted.

Как устроен

  • Один процесс FastAPI (Python 3.12, httpx, APScheduler) и свой Redis в том же compose. Redis нужен только для лимитов. Базы нет, тестов нет.
  • Номер приводится к +7XXXXXXXXXX. Принимаются +7…, 8… и десять цифр с пробелами, скобками и дефисами. Всё, что не складывается в десять цифр с первой девяткой, получает 400 «Введите правильный российский номер» — до лимитов и до провайдера.
  • Четыре лимита проверяются по порядку, каждый отвечает 429 с заголовком Retry-After:
    ЛимитПорогКогда действует
    Повтор на тот же номер1 SMS в 60 секундтолько если передан ip
    Разных номеров с одного ip3 за 24 часатолько если передан ip
    SMS на один номер3 за 24 часавсегда
    Всего SMS1000 за сутки по UTCвсегда
    ip — адрес конечного посетителя, который видел бэкенд лендинга, а не адрес самого лендинга. Пороги — константы в app/constants.py, меняются только вместе с кодом.
  • Отправитель в обоих каналах — donulya.ru. Вызывающий получает 202 и сырой ответ провайдера строкой в provider_response. Ошибка провайдера — 502.
  • Примечание в amoCRM. Если в запросе есть lead_id, после ответа фоновой задачей в сделку добавляется примечание «исходящая SMS» с номером и текстом. Шлюз пишет его своим токеном напрямую, не через amocrm-service и не через amo_queue, со своим ограничением 6 запросов в секунду. Ошибка примечания только пишется в лог.

Билайн или МегаФон

SMS уходит в хаб МегаФона, только если выполнены оба условия:

  1. на странице настроек шлюза включён переключатель «Использовать сервис Мегафон»;
  2. номер по плану нумерации выделен МегаФону: таблица диапазонов лежит в app/services/megafon_ranges.json (25 DEF-кодов) и читается один раз при старте.

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

  • Страница настроек — https://donula26.ru/service/beeline/megafon-sms-settings: переключатель и кнопка «Сохранить». Нужны одновременно адрес из списка доступа и отдельный логин и пароль страницы (MEGAFON_SETTINGS_PAGE_LOGIN / MEGAFON_SETTINGS_PAGE_PASSWORD, к учётке МегаФона отношения не имеют).
  • Первого числа каждого месяца в 00:00 по Москве переключатель выключается сам. Если в этот момент сервис лежал, выключение сработает при старте в тот же день.
  • Оператор определяется без учёта переноса номера. Номер, ушедший от МегаФона к другому оператору, всё равно пойдёт через МегаФон, и наоборот. Скрипта для обновления диапазонов в репозитории нет.

Как вызвать

curl -X POST https://donula26.ru/service/beeline/sms/send \
  -H 'Content-Type: application/json' \
  -d '{"phone": "+7 000 000-00-01", "message": "Ваш код подтверждения: 4821", "ip": "<ip посетителя>"}'
ПолеОбязательноЧто это
phoneдаРоссийский мобильный в любом написании, 5–32 символа
messageдаТекст, 1–1000 символов, уходит провайдеру как есть
ipнетАдрес посетителя. Без него не действуют лимит повтора и лимит номеров на IP
lead_idнетСделка amoCRM для примечания об SMS

Проверка живости — GET /sms/health, открыта без списка доступа, отвечает {"status":"ok"}.

Где код и как выкатывается

  • Репозиторий Buharestskie/beeline-service, ветка main.
  • На сервере — git-клон main: git pull --ff-only и docker compose up -d --build. Контейнер опубликован только на локальном интерфейсе сервера, снаружи — через nginx с префиксом /service/beeline/.
  • Внешней пробы в мониторинге у шлюза нет, поэтому окно обслуживания на выкатку объявить не на что.
  • Локально: cp .env.example .env, заполнить, поднять Redis и uvicorn app.main:app --port 8001. С боевыми ключами SMS уйдут на настоящие номера.

На что наступают

  • После выкатки переключатель МегаФона снова выключен. Его состояние хранится в файле внутри контейнера, тома под него нет. Если маршрут был включён — перед выкаткой проверьте его (GET /megafon-sms-settings/api) и после неё включите заново на странице настроек.
  • 202 от шлюза не значит, что МегаФон принял SMS. Хаб отвечает HTTP 200 и при отказе, вердикт лежит в теле. Шлюз такой отказ только пишет в лог. Смотрите в provider_response: у МегаФона там error.code, 0 — принято.
  • Передавайте номер всегда в одном формате. Лимиты считаются по строке phone как её прислали: +79000000001 и 8 900 000-00-01 — это разные счётчики.
  • Счётчики растут и на отказах. Если сработал другой лимит или провайдер вернул 502, повторить отправку на тот же номер можно только через 60 секунд, а суточный счётчик номера уже увеличен.
  • Суточный лимит в 1000 SMS обнуляется в 03:00 по Москве (сутки считаются по UTC), а ежемесячный сброс МегаФона срабатывает в 00:00 по Москве.
  • Адрес шлюза в .env.example лендингов неправильный. У bfl_fspp и mfcspisan там /services/beeline с «s», у do-nula-refinancing-2 — адрес внутри чужой docker-сети. Рабочий адрес один: https://donula26.ru/service/beeline.
  • Страница настроек локально не загрузит состояние: её скрипт ходит на боевой путь /service/beeline/megafon-sms-settings/api. Локально переключайте маршрут через curl на /megafon-sms-settings/api.
  • Примечания с lead_id нагружают общий лимит amoCRM мимо общего шлюза. Если начнёте передавать lead_id массово, учитывайте это вместе с остальными сервисами (см. «Как ходить в amoCRM из своего сервиса»).