Структура SMPP-квитанции о доставке
Если вы отправляете SMS с registered_delivery, равным 1, SMSC позже присылает PDU deliver_sm по соединению receiver или transceiver. Бит 0x04 в esm_class отмечает его как квитанцию о доставке, а не входящее сообщение, а в short_message передаётся текстовая квитанция. Приложение B спецификации SMPP v3.4 приводит такой формат как пример, и ему следуют почти все SMSC: id:IIIIIIIIII sub:SSS dlvrd:DDD submit date:YYMMDDhhmm done date:YYMMDDhhmm stat:DDDDDDD err:E text:….
Приложение B — это пример, а не строгое правило, поэтому реальные квитанции отличаются: ключи в нижнем регистре, лишние пробелы, submit_date с подчёркиванием, 12-значные даты с секундами или отсутствующий text. Декодер принимает все эти варианты и сообщает, каких полей не хватает.
- id: идентификатор сообщения, который SMSC вернул в submit_sm_resp. По нему квитанция сопоставляется с вашим сообщением. В примере из спецификации он состоит из 10 символов, но многие SMSC используют более длинные идентификаторы, иногда в hex.
- sub и dlvrd: количество изначально отправленных и доставленных сообщений, 3 цифры. Они важны в основном для списков рассылки; для одного SMS обычно это 001 и 001 (или 000).
- submit date и done date: когда сообщение было отправлено и когда оно достигло финального состояния, по часам SMSC.
- stat: финальное или промежуточное состояние в виде 7-буквенного кода, например DELIVRD.
- err: 3-символьный код ошибки, зависящий от сети или SMSC; 000 означает отсутствие ошибки.
- text: первые 20 символов исходного сообщения, часто обрезанные или пустые.
- Необязательные TLV могут дублировать эти данные в более чистом виде: receipted_message_id (0x001E), message_state (0x0427) и network_error_code (0x0423).
Коды stat в DLR и их значение
SMPP v3.4 определяет восемь состояний сообщения. ENROUTE и ACCEPTD — промежуточные, остальные — финальные. Наш API приводит все каналы к шести статусам вебхука (SUBMITTED, SENT, DELIVERED, READ, FAILED, REJECTED), а декодер показывает ближайшее соответствие для каждого значения stat. Считайте это соответствие ориентировочным: точное правило для вашего аккаунта подтверждается при подключении.
- DELIVRD (message_state 2): доставлено на телефон. Ближайший статус вебхука: DELIVERED.
- ACCEPTD (6): формально «прочитано вручную от имени абонента службой поддержки», но большинство SMSC отправляют его как промежуточную квитанцию «принято сетью». Ближайший: SENT.
- ENROUTE (1): всё ещё в пути, например в очереди на повторную попытку. Ближайший: SENT.
- UNDELIV (5): недоставляемо, например из-за неверного или неактивного номера. Ближайший: FAILED.
- EXPIRED (3): срок действия истёк раньше, чем удалось связаться с телефоном, часто потому что он был выключен или вне зоны покрытия. Ближайший: FAILED.
- DELETED (4): удалено до доставки, например через cancel_sm. Ближайший: FAILED.
- REJECTD (8): отклонено сетью или файрволом, например из-за незарегистрированного имени отправителя или заблокированного содержимого. Ближайший: REJECTED.
- UNKNOWN (7): у SMSC нет финального состояния. Считайте его неподтверждённым, а не доказанной неудачей.
Коды err: почему нет единого списка
Поле err состоит из трёх символов, а спецификация SMPP не определяет его значения. 000 всегда означает отсутствие ошибки, но ненулевые значения задаёт вендор SMSC, оператор или промежуточный агрегатор. Одни SMSC передают причины ошибок GSM MAP (например, MAP error 1 — неизвестный абонент, 27 — абонент недоступен), другие сопоставляют всё со своей таблицей.
Если квитанция содержит TLV network_error_code (0x0423), ему можно доверять больше: первый октет задаёт тип сети (3 означает GSM), следующие два — код ошибки. Квитанция DELIVRD с ненулевым err противоречива, и об этом стоит сообщить провайдеру; декодер помечает такие случаи.
Ошибки command_status и квитанции о доставке
SMPP сообщает о проблемах в два момента. command_status в submit_sm_resp приходит через миллисекунды и показывает, принял ли SMSC ваш запрос; 0x00000000 (ESME_ROK) означает «принято», а не «доставлено». Квитанция о доставке приходит через секунды или дни и содержит финальный результат.
Значения command_status определены в разделе 5.1.3 SMPP v3.4. Чаще всего в продакшене встречаются 0x00000058 ESME_RTHROTTLED (вы превысили согласованное число сообщений в секунду: снизьте скорость и повторяйте с экспоненциальной задержкой), 0x00000014 ESME_RMSGQFUL (очередь заполнена: сделайте паузу), 0x0000000A и 0x0000000B (неверный адрес отправителя или получателя) и 0x0000000D или 0x0000000E (ошибка bind или неверный пароль). Значения от 0x00000400 до 0x000004FF зарезервированы под ошибки конкретных вендоров SMSC, поэтому уточняйте их у провайдера.
Даты, часовые пояса и задержка доставки
Даты в квитанции имеют формат YYMMDDhhmm, а многие SMSC добавляют секунды (YYMMDDhhmmss). Часовой пояс в квитанции не указан: время берётся по часам SMSC, обычно в UTC. Декодер показывает даты в том виде, как они записаны, и считает задержку как done date минус submit date. Часовой пояс на неё не влияет, если обе даты взяты с одних часов.
Задержка — полезный показатель качества. Здоровый OTP-трафик на доступный телефон обычно доставляется за секунды. Большая задержка с последующим EXPIRED указывает на выключенные телефоны или роуминг, а done date раньше submit date означает, что часы расходятся. По датам с точностью до минуты нельзя измерить задержку короче минуты, поэтому лучше выбирать SMSC, которые передают секунды.
Надёжная обработка квитанций в продакшене
Отвечайте на каждый deliver_sm сообщением deliver_sm_resp, даже если обработка завершилась ошибкой. Иначе SMSC повторит отправку и может ограничить скорость соединения. Сохраняйте исходную квитанцию, а затем обрабатывайте её асинхронно и идемпотентно по ID сообщения: квитанции могут приходить дважды или не по порядку, и запоздавший ENROUTE не должен перезаписывать DELIVRD.
Если вы не хотите разбирать квитанции самостоятельно, наш REST SMS API передаёт ту же информацию в виде нормализованных статусов вебхука, а пропущенные квитанции можно отправить повторно через POST /sms/resendDlr. SMPP-соединения по TLS доступны для отправителей с большими объёмами и реселлеров.
