Как отправлять уведомления из CRM через очередь без дублей?

Как отправлять уведомления из CRM через очередь без дублей?

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

Почему CRM отправляет одно уведомление несколько раз?

Представим интернет-магазин: менеджер переводит заказ в статус «Готов к выдаче», и CRM вызывает сервис отправки SMS. Сервис принимает запрос, но соединение обрывается до ответа. CRM считает попытку неудачной и повторяет её. Если получатель запроса не распознаёт повтор, клиенту уходят одинаковые сообщения.

Другой сценарий возникает без сетевого сбоя. Менеджер повторно нажимает кнопку, CRM заново передаёт вебхук, а после перезапуска обработчик возвращается к прежней задаче. Для программы это разные запросы, хотя для бизнеса событие одно: конкретный заказ готов к выдаче.

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

Начните с описания события: какой переход статуса запускает уведомление и в каких состояниях заказа отправка допустима. Например, повторное сохранение карточки без смены статуса не должно создавать новое сообщение. Такой порядок настройки рекомендует материал «Почему SMS отправляется дважды: защита API и SMPP от дублей» (smpp.by).

Как связать событие, журнал и очередь?

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

  1. Определите ключ уведомления. Свяжите идентификатор события с каналом и назначением сообщения. Если один заказ проходит один и тот же статус несколько раз и каждый переход требует SMS, включите идентификатор перехода.
  2. Создайте запись в журнале до обращения к SMS-шлюзу. На поле ключа задайте уникальное ограничение базы данных: оно не позволит двум обработчикам одновременно создать одинаковые записи.
  3. Сохраните событие и задание на передачу в очередь в одной транзакции, если они находятся в общей базе. Отдельный процесс затем перенесёт задание в очередь. Так сбой между сохранением и публикацией не оставит событие без задачи.
  4. Поручите обработчику очереди получать право на работу с записью атомарно, то есть одним неделимым действием. Другой обработчик не должен одновременно отправлять то же уведомление.
  5. После обращения к шлюзу сохраните результат и идентификатор сообщения, если шлюз его вернул. Подтверждайте выполнение задачи в очереди после сохранения результата.

Простая последовательность «проверить наличие ключа, затем добавить запись» оставляет окно для дубля: параллельные процессы могут одновременно увидеть, что ключа ещё нет. Защиту обеспечивает именно ограничение в базе, а обработчик должен корректно разбирать конфликт записи.

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

Когда повторять отправку, а когда проверять статус?

Разделите результаты на понятные состояния: «ожидает отправки», «в обработке», «принято шлюзом», «ошибка» и «результат неизвестен». Это пример внутренних обозначений, а не универсальные статусы SMS-сервисов. Доставку фиксируйте отдельно: приём запроса шлюзом ещё не подтверждает получение сообщения абонентом.

При временном отказе, который однозначно означает, что сервис не принял сообщение, задачу можно вернуть в очередь. Увеличивайте паузу между попытками и ограничьте их количество. После исчерпания лимита переводите запись в отдельный список для разбора, чтобы очередь не повторяла её бесконечно. Возможность повторной отправки после временной ошибки описана в материале об SMS для ремонта техники (smsinfo.by).

Таймаут требует другого решения. Отсутствие ответа не показывает, успел ли шлюз принять сообщение. Переведите запись в состояние «результат неизвестен» и запросите сведения о предыдущей попытке, если интерфейс сервиса позволяет её найти. Для проверки используйте идентификатор сообщения или клиентский идентификатор, который поддерживает сервис.

Если шлюз поддерживает идемпотентные запросы, повтор передавайте с прежним ключом по правилам его интерфейса. Локальный ключ в вашей базе сам по себе не обязывает внешний сервис подавлять дубли. При отсутствии такой поддержки и возможности проверить результат потребуется разбор неопределённой попытки: гарантировать единственную отправку через произвольный шлюз нельзя.

Практические ситуации с повторными событиями разобраны в статье на smsinfo.by. При настройке своей интеграции отдельно зафиксируйте, какие ответы разрешают автоматический повтор, а какие требуют проверки.

Что записывать в журнал и контролировать в инфраструктуре?

Журнал должен объяснять путь конкретного уведомления. Храните ключ события, текущее состояние и время следующей попытки. Для каждой попытки отдельно фиксируйте время, результат обращения и код ошибки; идентификатор сообщения у шлюза связывайте с той же записью. Это позволит отличить повторный вебхук от повторного сетевого запроса.

Настройте сигнал ответственному, когда очередь перестала разбираться, появились задачи старше допустимого срока или начали накапливаться неизвестные результаты. Сам срок задайте по смыслу уведомления: сообщение о готовности заказа и срочное подтверждение требуют разных правил. В архитектуре уведомлений очередь также помогает учитывать лимиты провайдера и приоритеты событий (GSE).

Если CRM отвечает медленно, сначала отделите задержку её интерфейса от задержки SMS-шлюза. Для этого полезен разбор проверки задержек API между сайтом и CRM. Сопоставляйте время появления события, начала обработки и ответа шлюза: так будет видно, на каком участке задача ждёт.

Для компании в Беларуси с офисной CRM отдельно проверьте поведение при потере интернет-соединения. Задачи должны оставаться в журнале, а после восстановления связи обработчик должен продолжать работу с прежними ключами. Скорость подключения сама по себе не устраняет повторные запросы.

Если обработчик работает в облаке, включите в техническое задание автоматический перезапуск, резервное копирование журнала и контроль очереди. При подборе облачной инфраструктуры эти требования помогают определить состав услуги и границы сопровождения. В интеграциях телефонии с CRM полезно проверить тот же принцип работы с событиями; смежный пример — настройка облачной АТС и CRM через вебхуки без потери лидов.

Какие ошибки проверить перед запуском?

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

  • Новый ключ при каждом повторе. Система перестаёт узнавать прежнее событие. Сохраняйте ключ при повторной передаче задачи и создавайте новый только для нового уведомления.
  • Ключ только из номера заказа. Он блокирует другие нужные сообщения по этому заказу. Учитывайте тип события, а при необходимости — конкретный переход статуса.
  • Запись в журнале после отправки. При сбое между отправкой и сохранением следующий обработчик не увидит предыдущую попытку. Создавайте запись заранее и меняйте её состояние по результату.
  • Автоматический повтор после любого таймаута. Предыдущая отправка уже могла состояться. Сначала проверяйте результат или используйте идемпотентность шлюза, если она предусмотрена.
  • Удаление ключей раньше завершения возможных повторов. Старое событие снова станет новым для системы. Согласуйте срок хранения ключей с периодом повторов CRM и очереди.

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

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

Сейчас потратьте две минуты: выберите одно уведомление в CRM и запишите правило его ключа, например «готовность заказа + идентификатор перехода статуса». Это станет первым проверяемым требованием к настройке очереди.