Все статьи
Интеграции // Geron Labs

Как отправлять инструкции клиентам по SMS, если email не дошёл

Как связать рабочую систему, email и SMS: отделить общую инструкцию от данных доступа, обработать bounce и не устроить цикл повторных отправок.

Автор: Сергей Сидоров, основатель Geron Labs 29.09.2026 Код, API и границы решения

Чтобы автоматически доставлять инструкцию, когда email не дошёл, система должна знать результат отправки, а не просто вызвать почтовую функцию. При ошибке SMTP она собирает короткое SMS из данных той же заявки и отправляет его через SMS API. Поздний bounce обрабатывается отдельно: автоматизация находит завершённую заявку, заново получает данные доступа из рабочей системы и пробует резервный канал. Общую инструкцию лучше вынести на отдельную страницу без персональных данных, а логин, пароль или серийный номер передавать отдельным блоком. Если не сработали оба канала, задача остаётся менеджеру.

Заявка была одобрена, но клиент остался без инструкции

В проекте для сервисной компании мы автоматизировали выдачу доступов к домофону и видеонаблюдению. Заявки приходят по email, из Telegram и MAX. Система проверяет документ, находит объект в M4, получает данные для нужного приложения и ждёт решения менеджера.

Сначала финал выглядел просто: сгенерировать PDF, отправить письмо и закрыть задачу. На живой почте этого оказалось мало. SMTP может сразу вернуть ошибку. Бывает и другая ситуация: сервер принимает письмо, а через несколько минут или часов приходит bounce. Для программы отправка уже завершилась, но клиент ничего не получил.

Нам пришлось разделить два события:

  • сообщение принято каналом;
  • инструкция действительно доставлена или передана человеку для ручной отправки.

Пока система не умеет различать эти состояния, красивый статус completed ничего не доказывает.

Как устроен рабочий поток

В текущем контуре участвуют M4, почта и TextBee. Android-телефон с SIM работает как SMS-шлюз. Почему не SMS-агрегатор: для отправки от буквенного имени его нужно регистрировать у каждого оператора, это около 7 000 ₽, а наш поток — несколько служебных сообщений в день. Обычный Android-телефон с TextBee отправляет настоящие SMS со своего номера по обычному тарифу. Для массовых рассылок так делать нельзя: номер упрётся в антиспам-лимиты оператора.

Последовательность такая:

text
заявка клиента
  -> проверка адреса и услуги
  -> задача и решение в M4
  -> поиск оборудования и данных доступа
  -> общая инструкция
  -> персональные данные
  -> email
       -> отправлен: фиксируем результат
       -> временная ошибка: ограниченный повтор
       -> не отправлен: короткая SMS
       -> поздний bounce: короткая SMS или задача менеджеру

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

Почему инструкция и пароль не должны жить на одной странице

Для четырёх типов оборудования мы сделали общие HTML-инструкции: Dahua, Dvor24, Ufanet и Provizor. На этих страницах есть шаги настройки и ссылки на приложения. Там нет адреса клиента, логина, пароля или серийного номера.

Персональный блок строится во время обработки заявки. Например:

text
Приложение: Мой Умный дом
Login: user_example
Pass: ********
Instr: короткая ссылка

Так одну общую инструкцию можно обновлять без пересборки клиентских сообщений. Если меняется экран приложения, мы правим одну страницу. Данные доступа при этом остаются внутри конкретного процесса доставки.

Рабочие страницы закрыты от поисковой индексации через X-Robots-Tag: noindex, nofollow. Это не заменяет авторизацию и не делает ссылку секретным хранилищем. Поэтому персональные значения туда всё равно не попадают.

Как вызвать SMS API

Для нового подключения TextBee рекомендует account-level endpoint. Ключ хранится в переменной окружения и передаётся в заголовке x-api-key. Телефон нормализуется в E.164.

Упрощённый пример на TypeScript:

ts
type SmsResult = {
  accepted: boolean
  batchId?: string
  status: number
}

async function sendSms(phone: string, message: string): Promise<SmsResult> {
  const response = await fetch(
    'https://api.textbee.dev/api/v1/gateway/send-sms',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': process.env.TEXTBEE_API_KEY!,
      },
      body: JSON.stringify({
        recipients: [normalizeE164(phone)],
        message,
      }),
    },
  )

  if (!response.ok) {
    return { accepted: false, status: response.status }
  }

  const payload = await response.json()
  return {
    accepted: true,
    batchId: payload.data?.smsBatchId,
    status: response.status,
  }
}

В нашем действующем коде используется старый device-scoped маршрут TextBee. Он продолжает работать, но официальная документация уже помечает его устаревшим. Копировать его в новую интеграцию не стоит.

Ответ 200 означает, что API принял запрос. Это ещё не равнозначно статусу delivered. Если результат влияет на доступ, деньги или выполнение заявки, smsBatchId нужно сохранить и проверить позднее.

Что делать при ошибке email

Почтовая функция должна возвращать структурированный результат:

ts
type DeliveryResult = {
  ok: boolean
  channel: 'email' | 'telegram' | 'max'
  code?: string
  responseCode?: number
  error?: string
}

Раньше SMTP-ошибка могла попасть только в лог, а вызывающий код продолжал работу. Мы изменили контракт: ошибка стала частью результата. После этого основной процесс может решить, что делать дальше.

Повторять любой сбой подряд нельзя. SMTP 451 Try again later подходит для короткого ограниченного retry. Неверный адрес, отказ авторизации или ошибка конфигурации требуют другой реакции. После исчерпания безопасных повторов включается SMS.

В M4 остаётся приватный комментарий: основной канал не сработал, SMS принята шлюзом либо тоже завершилась ошибкой. Если резерв не помог, задача возвращается менеджеру. Автоматизация не закрывает её только потому, что успела сгенерировать текст.

Как обрабатывать поздний bounce

Поздний bounce приходит отдельным входящим письмом. Здесь нельзя использовать тот же catch, что и при SMTP-ошибке: первая отправка давно закончилась.

Обработчик делает следующее:

  1. распознаёт уведомление о недоставке;
  2. подавляет дубли одного и того же bounce в течение суток;
  3. ищет последнюю завершённую заявку по email;
  4. берёт телефон только из этой заявки;
  5. заново запрашивает оборудование и данные доступа в M4;
  6. собирает короткое SMS без PDF;
  7. отправляет его и записывает результат в исходную задачу.

Мы намеренно не создали отдельную базу контактов ради этого сценария. Пароли тоже не хранятся «на всякий случай». Если bounce пришёл позже, система снова читает первичный источник.

Когда телефон или данные доступа получить нельзя, создаётся отдельная задача для сотрудника. У автоматической ветки есть понятный конец.

Самая неприятная ошибка была в повторах

В этом же проекте есть второй SMS-контур: служебные команды на GSM-реле ворот. Он помог увидеть, насколько опасно путать accepted, sent и delivered.

В первой версии очередь ждала подтверждение несколько минут, считала отправку проваленной, переоткрывала задачу, а поллер снова отправлял ту же команду. Одна заявка создала 240 записей в очереди и исчерпала общий дневной лимит. Остальные заявки в тот день уже не могли воспользоваться шлюзом.

Позже мы сверили 105 записей, которые система считала исчерпанными. У TextBee 44 из них уже имели фактическую доставку. Реальных провалов было два. Телефон-шлюз иногда спал и отправлял накопившееся через несколько часов, а наша очередь принимала задержку за отказ.

После этого правило изменилось:

  • sent и pending означают «ждём», а не «отправляем ещё раз»;
  • повтор разрешён после явного failed или если провайдер не знает о сообщении;
  • недоступность Android-шлюза останавливает расход попыток;
  • через сутки заявка всё равно уходит менеджеру;
  • на одну заявку действует предел отправок;
  • поздняя доставка сверяется по всей истории, чтобы не повторять уже выполненную операцию.

В отдельном инциденте телефон оставался online и продолжал посылать heartbeat, но модем отклонял SMS. Причиной оказался баланс SIM. Мы добавили watchdog, который проверяет присутствие телефона и серию modem errors. Алерт сразу предлагает проверить баланс.

Что должен видеть менеджер

Менеджеру не нужен сырой ответ API. Ему нужен следующий шаг.

В рабочей задаче достаточно четырёх состояний:

  • email отправлен;
  • email не дошёл, SMS принята шлюзом;
  • оба автоматических канала не сработали, требуется ручная отправка;
  • SMS для критичной операции подтверждена как delivered.

Телефон в комментарии маскируется. Ключ API, пароль клиента и полный текст ответа провайдера туда не попадают. Для расследования остаются тип события, время, request ID и безопасный код ошибки.

Где заканчивается текущая автоматизация

У клиентского SMS fallback в этой версии нет отдельной постоянной очереди. HTTP 2xx и batch ID считаются успешной передачей TextBee. Для регистрации телефона в GSM-реле проверка строже: есть outbox и ожидание terminal-статуса delivered.

Такое различие нельзя прятать. Если SMS сообщает справочные данные, принятия шлюзом может хватить для первой версии при наличии ручной эскалации. Если сообщение подтверждает оплату, доступ или юридически значимое действие, нужно отслеживать доставку и сохранять переходы состояния.

Ещё два ограничения:

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

Чек-лист перед запуском

  1. Основной канал возвращает результат, который видит бизнес-процесс.
  2. Общая инструкция не содержит персональных данных.
  3. Телефон берётся из той же заявки, для которой отправлялась инструкция.
  4. 400 и 401 не повторяются автоматически.
  5. Для 429 и временных сетевых ошибок есть ограниченная политика retry.
  6. Batch ID сохраняется там, где нужно доказать доставку.
  7. Повтор одного события не создаёт второе бизнес-действие.
  8. После отказа обоих каналов задача остаётся человеку.
  9. В журнале нет токенов, паролей и открытых телефонов.
  10. Отдельно проверены мгновенная ошибка SMTP, поздний bounce, спящий Android и исчерпание лимита.

Если у вас уже есть заявки в CRM или service desk, Geron Labs может сначала разобрать один путь доставки: от решения менеджера до инструкции у клиента. По этому пути быстро видно, где нужен API, где достаточно короткой ссылки и в каком месте нельзя закрывать задачу без подтверждения.

Источники

Разбор процесса

Нужна такая же схема у вас?

Пришлите один реальный сценарий: заявку, переписку или файл. Покажем, где нужен ИИ, какие API понадобятся и во что обойдётся пилот. Простой агент с несколькими функциями — в среднем от $500.

SYS: COOKIE

Мы используем технические cookies для корректной работы сайта. Политика конфиденциальности