Кампании

Кампания – это многошаговый автоматический путь подписчика: вход по триггеру, затем последовательность узлов (сообщения, задержки, условия, эксперименты), которая может растягиваться на дни и недели. Это не то же самое, что рассылка или email-рассылка – у тех один документ контента и одна волна отправки; кампания же – это граф с ветвлениями, который читает и меняет данные подписчика на каждом шаге.

Версии и черновик

У кампании в любой момент ровно один редактируемый черновик (draft) и, если она хоть раз публиковалась, история immutable-версий: draftpublishedretired. Черновик содержит триггер, аудиторию, настройки и граф узлов. Правка черновика идёт через PUT /draft с expected_revision: если кто-то другой уже сохранил черновик, запрос отклоняется как конфликт, а не тихо перезатирает чужую правку.

Публикация (POST /publish) замораживает текущий черновик как новую опубликованную версию и сразу открывает следующий черновик – его копию. Подписчик, вошедший в кампанию во время действия версии N, доходит по её пути до конца на версии N, даже если позже опубликуют версию N+1: триггер, аудиторию, настройки и граф уже вошедшего путешествия версия N+1 не меняет.

Изменить граф узлов можно только когда кампания остановлена – при статусе «Выполняется» или «На паузе» сохранение графа отклоняется с ошибкой: кто-то может стоять именно на этом узле по его идентификатору, и менять граф под живым подписчиком нельзя.

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

Триггеры входа

  • Вручную – без настроек; кнопка «Отправить тест себе» в редакторе вызывает тот же путь и сразу выполняет входной узел, не дожидаясь фонового обхода.
  • Событие сайта – то же событие клиентского SDK/виджета, что и в реакциях, с тем же редактором условий.
  • По расписанию – один раз в момент at либо повторяясь. Повторяющееся расписание задаёт локальное время суток (ЧЧ:MM) в явном IANA-часовом поясе – значение «Local» не принимается – опционально фильтр по дням недели и обязательные start_at/end_at не длиннее двух лет: у повторяющегося расписания обязательно есть конец, иначе забытая кампания сканировала бы аудиторию вечно. Момента, которого не существует из-за перевода часов весной, по умолчанию просто нет в расписании; политика «следующий существующий момент» переносит его на конец провала вместо пропуска.
  • Изменение метки – добавление или снятие одной конкретной метки, не оба сразу: это разные путешествия с разным содержанием.
  • Изменение свойства – переход одобренного свойства профиля из одного состояния в другое. Условия «было» и «стало» независимы и оба необязательны; повторное сохранение того же значения переходом не считается и вход не вызывает.
  • Вход в сегмент – переход участия в готовом сегменте из «не входит» в «входит». Обычный пересчёт, который лишь подтверждает уже имеющееся участие, входом не считается.

Узлы графа

Граф – это узлы и рёбра между ними; у рёбра есть метка ветки (например, yes/ no). Ровно один узел без входящих рёбер – точка входа; терминальные узлы («Цель», «Выход») не имеют исходящих рёбер, и от каждого узла должен быть путь хотя бы до одного терминального узла – граф с недостижимым узлом или узлом без выхода к финалу не сохранится. У узла может быть максимум 32 КиБ конфигурации; у кампании в целом – не больше 200 узлов и 400 рёбер.

  • Сообщение – блоки текста/фото/файла/задержки, выполняются как обычная реакция тому же подписчику. Блоки ИИ, вложенная реакция, «ожидаемый ответ» и сохранение параметров с побочным эффектом здесь не поддерживаются – повторный запуск узла после сбоя должен быть безопасным.
  • Контент – в отличие от «Сообщения», отправляет конкретную опубликованную и зафиксированную по версии документ на явно названный канал (Telegram, VK, WhatsApp или веб-чат) и никогда не переключается на другой канал сам: недоступность адресата либо уходит по отдельной ветке «когда канал недоступен», если она проведена, либо узел явно проваливается.
  • Задержка – ждёт заданное число секунд (не больше 180 дней).
  • Ожидание события – ждёт клиентское событие по имени (до 64 символов, без зарезервированного префикса $) с таймаутом до 180 дней; у узла ровно две ветки – основная (событие пришло) и timeout.
  • Условие – ветвится yes/no по параметрам подписчика, периодам времени, давности последнего визита или числу сообщений – тот же словарь условий, что у реакций.
  • Разделение – весовое A/B-ветвление: подписчик детерминированно попадает в одну и ту же ветку по хэшу узла и своего идентификатора, и это не меняется при повторной проверке или перезапуске.
  • Эксперимент – ветвится по варианту общего эксперимента с закреплёнными идентификатором и версией; от двух до шестнадцати вариантов, и каждый объявленный вариант – обязательная ветка: невыведенный вариант молча обрывал бы путешествие тем, кому эксперимент его назначил.
  • Доступность канала – ветвится yes/no по тому, можно ли именно сейчас достучаться до подписчика на явно названном канале (email и телефон не подходят – это не канал доставки). Существует, чтобы автор явно решал вопрос резервного канала, а не полагался на то, что отправка сама переключится.
  • Действие – смена меток; снятие параметров (установка, добавление и инкремент здесь не поддерживаются – тот же принцип безопасного повтора, что у «Сообщения»); синхронный веб-запрос (без фонового режима, без вложенных реакций на успех/ошибку, заголовок Idempotency-Key зарезервирован); синхронизация с подключённой CRM; назначение диалога поддержки агенту или группе; отправка клиентского события (как вызов track() из виджета – увидят сегменты, воронки и подходящий триггер); запуск lead-бота.
  • Цель и Выход – терминальные узлы без настроек: первый – успешное завершение, второй – уход без успеха.

Предпроверка и публикация

POST /preflight проверяет форму текущего черновика (тот же граф-валидатор, что и при каждом сохранении) и связи с другими сущностями – существуют ли названные сегменты, документы контента, эксперименты – и ничего не отправляет. Ответ – ready, номер версии, её контрольная сумма и список ошибок с кодом (invalid_graph, invalid_reference) и текстом. Предпроверка не считает размер аудитории, доступность каналов или согласия – это не входит в её задачу.

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

Отчёт по версии и трейс путешествия

Отчёт (GET /report) считается по одной версии, а не по кампании в целом: у версии N и версии N+1 разные счётчики, публикация новой версии не сдвигает цифры старой. В отчёте – воронка путешествий (вошли, активны, ждут, на повторной попытке, дошли, вышли, провалились) и по каждому узлу – вошли, завершили, провалились. Отчёт зафиксирован (sealed), когда версия уже не принимает новых вошедших и ни одно путешествие по ней не может сдвинуться – такие цифры сохранены как есть и больше не пересчитываются; отчёт живой опубликованной версии всегда пересчитывается заново.

Трейс одного путешествия (GET /journeys/{subscriberID}/{runNo}) – это ограниченный по числу событий журнал: вход в узел, сработавший эффект, уход в ожидание, выход из ожидания, назначенная повторная попытка, отказ после исчерпания попыток, завершение. В трейсе – идентификаторы, ветки, номера попыток и коды ошибок, но никогда текст сообщения или ответ провайдера: это аудит пройденного пути, а не копия того, что было отправлено. Флаг truncated показывает, что путешествие превысило лимит событий и часть записей не сохранилась. Отдельный список (GET /failures) показывает, кто застрял и на каком узле, после того как раннер исчерпал попытки; застрявшее путешествие можно перезапустить (POST /journeys/{subscriberID}/{runNo}/retry).

Тихие часы

У версии есть свободные настройки (settings), которые по умолчанию создаются как {"allow_reentry": false, "timezone_mode": "bot", "quiet_hours": {"enabled": false}}. Повторный вход (allow_reentry) в кампании – это отдельное, реально работающее поле самой кампании (не настроек версии): оно решает, может ли один подписчик войти в кампанию повторно. А вот timezone_mode и quiet_hours внутри settings на сегодня – это только зарезервированная форма конфигурации: раннер кампаний их не читает и не применяет. Это отличает их от одноимённой функции Web Push, где тихие часы действительно откладывают доставку. Не закладывайтесь на то, что кампания сама подождёт «удобное» время – паузу нужно моделировать узлом «Задержка» или условием по периоду времени.

Права

Личные токены и роли видят кампании через три отдельных scope: campaigns:read, campaigns:write и campaigns:publish – публикация версии и перезапуск застрявшего путешествия отдельно от обычного редактирования черновика. Все три требуют право бота «Реакции»: без него раздел кампаний не виден и не доступен ни на чтение, ни на запись.

Что дальше