Валидация переменных SMS-шаблона до отправки

Шаблон Заказ {{order_number}} готов выглядит безопасно, пока в данных не окажется пустой номер, слишком длинное имя или строка с переносом. Тогда клиент получает SMS с фигурными скобками, обрезанным смыслом или неожиданным количеством сегментов. Такие ошибки возникают до SMS API, поэтому провайдер не может исправить их за приложение.
Валидация переменных — отдельный этап между бизнес-событием и постановкой готового сообщения в очередь. Он проверяет схему данных, формирует текст и только затем разрешает отправку.
Контракт шаблона
У каждого шаблона должен быть машиночитаемый контракт:
- список переменных;
- обязательность;
- тип и допустимый формат;
- минимальная и максимальная длина;
- поведение при пустом значении;
- допустимый набор символов;
- источник данных;
- пример корректного значения;
- версия шаблона.
Хранить этот контракт только в комментарии недостаточно. Валидатор должен использовать ту же схему, по которой команда согласовала шаблон.
Обязательные и необязательные значения
Если обязательного поля нет, сообщение не отправляют. Подстановка пустой строки может изменить смысл: Встреча назначена на выглядит как ошибка и вызывает обращение в поддержку.
Для необязательного блока лучше условная конструкция на уровне шаблонизатора, а не пробел вместо значения. Например, фраза про зал или подъезд должна исчезать целиком вместе с пунктуацией. Иначе остаются двойные пробелы, запятые и незавершённые предложения.
Типы и формат
Дата форматируется в понятном часовом поясе, сумма — с валютой, номер заказа — без научной нотации, ссылка — только из разрешённого домена. Значение 2026-09-21T07:00:00Z технически корректно, но не готово для клиента.
Не полагайтесь на неявное преобразование типов. null, false, ноль и пустая строка означают разные состояния. Особенно важен ноль: скидка 0% может быть корректным значением, а не отсутствием данных.
Ограничение длины
Длину проверяют после всех подстановок. Короткий шаблон может стать длинным из-за имени организации или ссылки. Изменение алфавита одной переменной способно переключить кодировку и увеличить число сегментов.
Расчёт должен использовать ту же логику, что и отправка. Основы GSM-7, Unicode и сегментации разобраны в статье про длину SMS. Если итог превышает лимит сценария или бюджета, задача блокируется либо использует заранее согласованную короткую версию.
Автоматически обрезать имя или адрес опасно: можно удалить важную часть. Правило сокращения проектируют для конкретного поля и проверяют на примерах.
Небезопасные символы и контент
SMS не исполняет HTML, но данные всё равно могут ломать структуру: переносы строк, управляющие символы, невидимые пробелы, фигурные скобки шаблонизатора. Нормализуйте Unicode и разрешайте только ожидаемые символы для каждого поля.
Пользовательский ввод нельзя вставлять в рекламный текст без модерации. Он может содержать чужую ссылку, нежелательную лексику или персональные данные. Даже сервисное сообщение должно следовать принципу минимизации: передавайте клиенту только необходимое.
Ссылки
Ссылку собирают отдельно и проверяют до шаблона. Разрешите HTTPS и известные домены, запретите управляющие символы и неподтверждённые редиректы. Если используется короткая ссылка в SMS, её жизненный цикл должен покрывать время чтения сообщения.
Не позволяйте переменной заменять весь домен по произвольному значению из CRM. Лучше хранить базовый адрес в конфигурации, а из события принимать безопасный идентификатор.
Неизменяемые версии
После согласования шаблон получает версию. Изменение текста, набора переменных или ограничений создаёт новую версию, как описано в материале про версионирование SMS-шаблонов.
Задача в очереди должна ссылаться на конкретную версию. Иначе повтор после релиза сформирует другой текст из тех же данных, что усложнит аудит и идемпотентность.
Тестовые наборы
Для каждого шаблона подготовьте fixtures:
- обычный пример;
- минимальные и максимальные значения;
- пустое необязательное поле;
- отсутствие обязательного поля;
- кириллица, латиница, цифры и разрешённая пунктуация;
- длинная ссылка;
- значение с переносом и невидимым символом;
- дата на границе суток и часового пояса;
- текст, который меняет число SMS-сегментов.
Snapshot-тест полезен для итоговой строки, но не заменяет отдельные проверки схемы. Ошибка должна говорить, какая переменная нарушила правило, а не просто «рендер не удался».
Поведение при ошибке
Некорректная задача не должна бесконечно повторяться. Её переводят в карантин или DLQ с кодом шаблона, версией, причиной и безопасными ID. Ответственный получает агрегированный сигнал, если ошибок много.
Нельзя подставлять заглушку вроде не указано без согласования: для даты визита или суммы это может создать вредное сообщение. Fallback допустим только там, где он является частью контракта шаблона.
Чек-лист
- все переменные перечислены в схеме;
- обязательные поля не заменяются пустой строкой;
- типы и часовые пояса преобразуются явно;
- итоговая длина и сегменты считаются после рендера;
- ссылки ограничены доверенными доменами;
- управляющие и невидимые символы обрабатываются;
- версия шаблона неизменяема;
- есть тесты граничных значений;
- ошибка блокирует отправку и попадает в наблюдаемый карантин.
Итог
Валидация переменных превращает SMS-шаблон из строки с маркерами в проверяемый контракт. Она останавливает пустые подстановки, неожиданные сегменты и небезопасные ссылки до внешнего API. Чем раньше система обнаруживает ошибку данных, тем меньше риск отправить её клиенту и тем проще найти источник.