Email-рассылки

Email-рассылка отправляет одну опубликованную версию контента аудитории, давшей согласие, с вашего подтверждённого домена. Откройте раздел Email и перейдите по ссылке Email-рассылки.

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

Редактор

Редактор рассылки состоит из четырёх шагов.

Письмо и отправитель. Название рассылки (до 160 символов), домен отправки, имя отправителя, адрес для ответа, тема (до 255 символов) и прехедер (до 500 символов). Адрес для ответа указывается «голым», например hello@example.com, без отображаемого имени. Переводы строки в имени отправителя, адресе для ответа, теме и прехедере запрещены: через них возможна подмена заголовков письма.

Контент и предпросмотр. Выберите опубликованную версию email-контента или создайте её из шаблона. Рассылка сохраняет идентификатор документа вместе с точным номером версии: последующая публикация документа не изменит уже запланированную рассылку.

Аудитория и эксперимент. Либо все email-подписчики бота, либо один готовый сегмент. При желании подключите A/B-эксперимент: при планировании рассылка зафиксирует версию эксперимента и будет отчитываться по этому снимку.

Расписание и приватность. Время отправки, режим часового пояса, писем в минуту (от 1 до 10 000) и переключатель сбора открытий. Время отправки указывать необязательно: рассылка без него стартует сразу при запуске.

Есть два режима часового пояса:

  • По времени рассылки – один абсолютный момент для всех, посчитанный в часовом поясе рассылки.
  • По местному времени получателя – одно и то же время на часах в поясе каждого получателя.

Список поясов фиксированный: Москва, Белград, Лондон, Берлин, Дубай, Алматы, Тбилиси, Ереван, Ташкент, Бангкок, Токио, Нью-Йорк, Лос-Анджелес, UTC.

Перевод часов обрабатывается явно, а не молча:

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

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

Проверка получателей до отправки

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

В ответе используются те же коды, что видно в интерфейсе.

Адрес попадает в исключённые, если:

  • его подписка не в состоянии «подписан» – причиной тогда указано само состояние: unsubscribed, bounced или complained;
  • адрес пуст или непригоден (invalid_email);
  • такой же нормализованный адрес уже есть в этой рассылке (duplicate);
  • включён режим местного времени, а у человека нет часового пояса (missing_timezone), он не распознан (invalid_timezone) либо такого местного времени в этот день не существует (nonexistent_local_time);
  • согласие было отозвано между подготовкой списка и попыткой отправки (no_consent).

Запуск блокируют именно ошибки:

  • domain_not_verified – домен отправки не подтверждён;
  • content_not_published – выбранной версии контента нет или она не опубликована;
  • audience_not_ready – выбранного сегмента нет или он ещё не пересчитан;
  • experiment_not_ready – связанный эксперимент не запущен либо его текущая версия распределяет не по подписчикам;
  • scheduled_in_past – время отправки уже прошло;
  • nonexistent_local_time – см. выше;
  • empty_audience – рассылку никто не получит;
  • все ошибки контента из Студии контента, привязанные к полю content_document_id и к проблемному блоку.

Предупреждения контента приходят там же, плюс одно собственное:

  • missing_plain_text – в контенте нет читаемого текста, только медиа и кнопки, поэтому текстовая часть письма окажется пустой.

Рассылку можно запустить только при нуле ошибок. Предупреждения запуск не блокируют.

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

Запуск и управление

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

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

Статусы: Черновик, Запланирована, Отправляется, На паузе, Завершена, Отменена, Не удалась, В архиве. Из каких статусов доступны действия:

  • Поставить на паузу – из «Запланирована» или «Отправляется».
  • Продолжить – из «На паузе», обратно в «Запланирована».
  • Отменить – из «Черновик», «Запланирована», «Отправляется» или «На паузе». Тем, кому ещё не отправили, письмо не уйдёт.
  • В архив – из «Черновик», «Завершена», «Отменена» или «Не удалась».

Каждое действие управления тоже несёт ожидаемую ревизию, поэтому два оператора не могут незаметно перебить работу друг друга.

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

Трекинг, приватность и отписка

Каждая отслеживаемая ссылка – пиксель открытия, переход и отписка – это подписанный токен на вашей же установке вида https://<ваш-хост>/e/t/<токен>. Токены подписаны HMAC, ограничены одним ботом, рассылкой и получателем и истекают через 90 дней после отправки. Ключ подписи выводится из секрета почтовых вебхуков установки: без него рассылка не отправится вообще, а не уйдёт с неотслеживаемыми ссылками.

  • Отслеживание открытий – это GIF 1×1. Оно необязательно: если выключить «Собирать статистику открытий», пиксель просто не добавляется, а доставка, переходы, жалобы и отписки продолжают считаться. Открытия всегда занижены, объяснение есть в статье Email с вашего домена.
  • Отслеживание переходов переписывает только кнопки-ссылки на https://. Токен перехода несёт адрес назначения, и этот адрес обязан быть https://-ссылкой с хостом и без встроенных учётных данных, поэтому перенаправить его в другое место нельзя.
  • Токены отписки намеренно не несут ни идентификатора ссылки, ни адреса перехода: отписка – это действие, а действие не должно уметь никуда перенаправлять.

Недействительный, истёкший, чужой или неизвестный токен получает 404 без подробностей, поэтому по токену нельзя разведать существующие рассылки и получателей.

Отписка работает по RFC 8058 в один клик. GET показывает простую HTML-страницу подтверждения без JavaScript, а POST с единственным полем формы List-Unsubscribe=One-Click выполняет отписку. Повторная отписка тем же токеном считается успехом, а не ошибкой: почтовые клиенты предзагружают ссылки, а люди кликают дважды. Отказ записывается и в строку получателя, и в канонический учёт согласий одной транзакцией, поэтому ссылка не может отчитаться об успехе, оставив согласие активным.

Отчёт

Отчёт по рассылке разделяет: Получатели, Исключены, Ожидают, Отправляются, Принято провайдером, Статус неизвестен, Доставлено, Открыто, Переходы, Возвраты, Жалобы, Отписки, Ошибки и Отменены. Переходы показываются по каждой ссылке, а получателей можно листать постранично со статусом и причиной исключения.

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

Ответы получателей

Входящий маршрут превращает адрес на подтверждённом домене в обращение в поддержку. Локальная часть адреса хранится только в виде необратимого хеша, поэтому сам адрес рядом с маршрутом не сохраняется.

Экрана для маршрутов в панели пока нет. Управляйте ими через API с токеном, у которого есть settings:read / settings:write:

curl -X POST "$BASE_URL/api/bots/$BOT_ID/email/inbound-routes" \
  -H "Authorization: Bearer $PAT" -H "Content-Type: application/json" \
  -d '{"domain":"mail.example.com","local_part":"support"}'

Локальная часть приводится к нижнему регистру и должна подходить под [a-z0-9][a-z0-9._+-]{0,62}. Домен уже должен быть подтверждён для этого бота, иначе ответ будет 422; повторный тот же адрес – 409. Полный адрес возвращается один раз, при создании: в списке потом виден только домен, потому что локальная часть не хранится. Если в установке не настроен секрет почтовых вебхуков, ответ будет 503 – входящая почта выключена целиком.

Входящее письмо нормализуется до того, как попадёт в диалог:

  • отправитель сопоставляется с подписчиком по нормализованному адресу;
  • In-Reply-To и References продолжают существующую переписку, но только если она принадлежит тому же человеку;
  • цитируемая история вырезается из текста;
  • исходное письмо ограничено 8 МиБ, извлечённый текст 256 КиБ, тема 255 символами, а References 32 записями;
  • сохраняется не больше 5 вложений размером не больше 1 МиБ каждое, по контентному ключу; исполняемые вложения (.bat, .cmd, .com, .dll, .exe, .msi, .scr и MIME-типы исполняемых файлов) отклоняются;
  • письмо только в HTML сводится к тексту.

Письмо на неизвестный маршрут принимается и отбрасывается, поэтому опечатка в адресе не превращается в шторм повторов.

Контракт провайдера

Если вы держите собственную установку или пишете адаптер к почтовому провайдеру, вот два публичных эндпоинта, с которыми он общается. Оба проверяют подпись HMAC-SHA256 по сырому телу запроса, в hex, в заголовке X-Signature, на настроенном секрете вебхуков почты. Запрос без подписи, а также любой запрос, если секрет не настроен, отклоняется с 401: эндпоинт закрывается по умолчанию.

События доставки и вовлечения

POST /esp/{provider}/webhook, тело до 1 МиБ:

{
  "provider_id": "prv_00000000-0000-0000-0000-000000000000",
  "event": "delivered",
  "bounce_class": "hard",
  "url": "https://example.com/offer",
  "event_id": "evt_0000000001",
  "tag": "email-campaign:11111111-1111-1111-1111-111111111111:22222222-2222-2222-2222-222222222222"
}

Это собственный обобщённый контракт платформы, а не формат какого-то одного провайдера. Ваш адаптер переводит callback провайдера в него.

  • event принимает значения delivered, opened, clicked, bounced, complained, unsubscribed. Незнакомое значение сохраняется для разбора и ничего не меняет.
  • bounce_class равен hard или soft и имеет смысл только для bounced.
  • tag – это стабильные метаданные, переданные при отправке, в виде email-campaign:<id рассылки>:<id подписчика>. Он позволяет найти получателя, если callback пришёл раньше, чем сохранился идентификатор провайдера.

Как события отображаются в состояния:

  • delivered – письмо и получатель рассылки переходят в «Доставлено»;
  • bounced с bounce_class: hard – письмо «Возврат», адрес исключается, получатель «Возврат»;
  • bounced с bounce_class: soft – только запись события. Мягкий возврат говорит о конкретной попытке, а не об адресе, поэтому он никого не исключает и не меняет состояние получателя;
  • complained – записывается жалоба, адрес закрывается навсегда;
  • unsubscribed – согласие отзывается, получатель «Отписан»;
  • opened, clicked – записываются как события, состояние не меняется.

Callback, пришедший не по порядку, не может откатить конечное состояние назад.

Идемпотентность строится на event_id, если провайдер его присылает, иначе на SHA-256 сырого тела. Проверка, эффекты и отметка о приёме пишутся одной транзакцией, сериализованной по паре «бот и ключ», поэтому два одновременных дубля одного события не применятся дважды.

Коды ответа подсказывают адаптеру, что делать:

  • 200 – событие понято. Сюда же попадают намеренно проигнорированные события: дубль или неизвестный идентификатор письма, которое эта установка не отправляла. Повтор ничего не изменит.
  • 400 – некорректное тело или отсутствует provider_id либо event.
  • 401 – подпись неверна или отсутствует.
  • 413 – тело больше лимита.
  • 500 – временный сбой на нашей стороне. Повторять стоит только этот класс.

Входящая почта

POST /esp/{provider}/inbound, тело до 12 МиБ:

{
  "event_id": "in_0000000001",
  "raw_mime": "<base64 исходного MIME-письма>"
}

Раскодированный MIME не должен превышать 8 МиБ. 200 с {"created": true|false} означает, что письмо принято (false – это дубль уже записанного event_id), 204 – адрес получателя не совпал ни с одним маршрутом, 400 – конверт или MIME не разобрались, 401 – подпись не сошлась, 413 – тело запроса больше 12 МиБ.

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

Что делать, если

  1. Ничего не отправляется, а на карточке домена предупреждение – домен отправки ещё не подтверждён. См. Email с вашего домена.
  2. «Рассылку пока нельзя запустить» – откройте проверку получателей и устраните все ошибки; одни предупреждения запуск не блокируют.
  3. Много получателей исключено по причине missing_timezone – рассылка идёт по местному времени, а у людей нет часового пояса. Переключитесь на время рассылки или заполните поле профиля.
  4. Время отправки не принимается или проверка отдаёт nonexistent_local_time – такого времени на часах в этот день не существует из-за перевода часов. Выберите другое время.
  5. Открытий подозрительно мало – это ожидаемо и не чинится. Сравнивайте рассылки по переходам.
  6. События провайдера не приходят – проверьте, что адаптер подписывает ровно сырое тело и что секрет вебхука совпадает; неподписанный callback получает 401.

Что дальше