SMS-шлюз: beeline-service
Для разработчиков. Состояние на 28.09.2026. Переменные, все ручки и выкатка по шагам — README репозитория Buharestskie/beeline-service. Как лендинги работают целиком — статья о квиз-лендингах в этой же книге.
Коротко
beeline-service — общий шлюз исходящих SMS. Сервис присылает номер и готовый текст, шлюз проверяет, что номер российский мобильный, применяет антиспам-лимиты и отдаёт сообщение Билайну (A2P) или хабу МегаФона. Шаблонов у шлюза нет: текст целиком приходит от вызывающего. Снаружи шлюз доступен по адресу https://donula26.ru/service/beeline, пускает только адреса из своего списка доступа.
Кто вызывает
Сейчас — три квиз-лендинга, которые подтверждают телефон посетителя кодом из SMS («Ваш код подтверждения: 1234»):
| Лендинг | Что передаёт |
|---|---|
do-nula-refinancing-2 | phone, 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 часа только если передан ipSMS на один номер 3 за 24 часа всегда Всего SMS 1000 за сутки по UTC всегда ip— адрес конечного посетителя, который видел бэкенд лендинга, а не адрес самого лендинга. Пороги — константы вapp/constants.py, меняются только вместе с кодом. - Отправитель в обоих каналах —
donulya.ru. Вызывающий получает202и сырой ответ провайдера строкой вprovider_response. Ошибка провайдера —502. - Примечание в amoCRM. Если в запросе есть
lead_id, после ответа фоновой задачей в сделку добавляется примечание «исходящая SMS» с номером и текстом. Шлюз пишет его своим токеном напрямую, не черезamocrm-serviceи не черезamo_queue, со своим ограничением 6 запросов в секунду. Ошибка примечания только пишется в лог.
Билайн или МегаФон
SMS уходит в хаб МегаФона, только если выполнены оба условия:
- на странице настроек шлюза включён переключатель «Использовать сервис Мегафон»;
- номер по плану нумерации выделен МегаФону: таблица диапазонов лежит в
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 из своего сервиса»).
Нет комментариев для отображения
Нет комментариев для отображения