API, Betta 0.232 2024-02-17

Формат протокола

Вызов

Адрес для вызова

https://api.sendsay.ru/general/api/v100/json/ACCOUNT

или

https://api.sendsay.ru/general/api/v100/json/ACCOUNT/

Где ACCOUNT - код вашего аккаунта (совпадает с основным логином).

Для вызовов, не требующих аккаунта (например, ping), используйте знак минус '-'.

Транспорт - https - HTTP 1.0/1.1 с TLSv1.2 или TLSv1.3 - RFC 2616 http://tools.ietf.org/html/rfc2616, RFC 5246 http://tools.ietf.org/html/rfc5246, RFC 8446 https://datatracker.ietf.org/doc/html/rfc8446

или

Транспорт - https - HTTP 1.0/1.1 с GOST2012/GOST8912 - используйте хост apigost.sendsay.ru

Возможно ГОСТ-шифрование всего канала cвязи - свяжитесь со Службой поддержки.

Рекомендуемые расширения - SNI

Имя api.sendsay.ru имеет несколько ip-адресов. При невозможности соединения с каким-то из этих адресов необходимо повторять запрос с использованием других.

Предыдущая версия продолжает поддерживаться (https://sendsay.ru/api/doc/API-0.174-20190417.html)

Метод вызова

Метод вызова - POST - RFC 2616 section 5 https://tools.ietf.org/html/rfc2616#section-9.5

Максимальный размер запроса - в настоящий момент 400 Мбайт. Если вам этого недостаточно, свяжитесь со Службой поддержки.

Content-Type: запроса - application/json

Содержимое запроса - JSON-строка в кодировке UTF-8.

{
 "action" : "код вызываемого действия" -- обязательно

 прочие параметры, если нужны
}

Описание url-кодирования, применяемого в некоторых случаях, доступно в RFC 3986 https://www.rfc-editor.org/rfc/rfc3986#section-2.1

Описание формата JSON доступно в RFC 7159 http://tools.ietf.org/html/rfc7159

Не забывайте кодировать символы, которые не могут быть непосредственно записаны в JSON.

"Красивое" форматирование пробелами совершенно не обязательно и его отсутствие может заметно уменьшить размер вашего запроса, что увеличит скорость его обработки.

Настоятельно рекомендуется пользоваться готовыми функциями вашего языка программирования для перевода структуры данных в json-строку.

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

Дополнительные параметры запроса

Он никак не анализируется (за исключением описанного ниже) и просто возвращается обратно в параметре ответа request.id.

Параметр request-id можно передать одним из трёх способов:

- в url запроса

https://api.sendsay.ru/general/api/v100/json/ACCOUNT?request.id=идентификатор-запроса-url-кодированный

https://api.sendsay.ru/general/api/v100/json/ACCOUNT/?request.id=идентификатор-запроса-url-кодированный

- в содержимом запроса

{
 "action" : "идентификатор api-вызова" 

,"request.id" : "идентификатор запроса" 

 прочие параметры, если нужны
}

- в HTTP заголовке X-Request-ID

X-Request-ID: url-кодированный-идентификатор-запроса

Базовые лимиты_вызовов

5 одновременно длящихся запросов на изменение данных или расчёт статистики.

10 одновременно длящихся запросов всего.

80 запросов в секунду.

Вы можете получить api-ошибку rate_limit, HTTP код ответа 503. Тайм-аут запроса - в зависимости от отношения системы к текущему превышению лимита.

Ошибки оформления вызова

Готовые библиотеки для работы

В данный момент доступны готовые библиотеки для языков:

JavaScript - https://github.com/sendsay-ru/sendsay-api-js

Проверка request.id

Несмотря на то, что параметр request.id не обязателен и служит, в целом, только для облегчения отладки, есть одно его применение, которое позволяет избежать нежелательных последствий.

Если request.id не пустой и action входит в число контролируемых, то повторный вызов с таким же request.id в пределах 15 минут заканчивается сразу ошибкой repeated_request.id

Контролируемые вызовы:

issue.send
issue.send.multi
member.import
member.import.probe
member.sendconfirm

Для не транзакционного issue.send и для member.import дополнительно в obj будет повторён ответ на первоначальный запрос (например, там будет полезный параметр track.id).

Можно управлять этим временем в сторону увеличения через параметр запроса

{
 "repeated_request_ttl" : время в секундах, время менее стандартного игнорируется, время более 2 часов - считается как 2 часа
}

вверх

Последовательность обработки

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

Если вам требуется твёрдая уверенность в том, что вызов Б нормально воспользуется результатами более раннего вызова А, то вы должны дождаться явного окончания вызова А и только потом посылать вызов Б.

Конкурентные вызовы изменяющие один и тот же объект настоятельно рекомендуется выполнять последовательно по одному.

Например, два вызова anketa.quest.add в случае, когда они сделаны для одной и той же анкеты и второй вызов послан до окончания первого, могут дать три разных результата:

- добавятся оба вопроса
- только первый
- только второй

Также следует помнить о порядке обработки при удалении сложных объектов. Например, если удаляемый объект ссылается на другие необходимые ему объекты и они тоже при этом должны быть удалены, то сначала следует удалить основной объект и явно дождаться результата и только после этого удалять объекты, на которые он ссылался. Иначе, при параллельной обработке может сложиться ситуация, когда вспомогательный объект не будет удалён, так как ещё будет существовать основной (вызов удаления вспомогательного объекта дошёл до обработки ранее завершения удаления основного).

Не заданные параметры api-вызова

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

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

Представление чисел

Как при вызове, так и при ответе, параметр, имеющий численное значение, может быть представлен в любом из двух видов:

- число как json-число - 123

- число как json-cтрока - "123"

Представление дат

Временная зона всех дат - MSK (MSD, если летнее время опять вернут).

Даты принимаются и возвращаются в виде строки в формате с точностью до секунд.

YYYYY-MM-DD hh:mm:ss

где

YYYY - год
MM - месяц
DD - день
hh - час
mm - минута
ss - секунда

Лидирующие нули не обязательны ("2019-5-31 17:7:4")

Параметры с типом "дата" имеют пометку их точности, составленную из первых букв старшего и младшего полей, например:

Ys - от года до секунды, формат "YYYYY-MM-DD hh:mm:ss"

Yh - от года до часа, формат "YYYYY-MM-DD hh"

YD - от года до месяца, формат "YYYYY-MM-DD"

Ответ

Ответ выдаётся в формате JSON и кодировке UTF-8

Content-Type: application/json

http-статусы

При нормальном развитии событий ответ 200.

Необходимо уметь обрабатывать ответы 3xx, которые возможны при работе по HTTP.

Все прочие статусы должны трактоваться как неизвестная ошибка исполнения запроса.

Синхронность

Если иного не описано, то вызов является синхронным - запрошенное действие будет выполнено (или не выполнено при ошибке) к моменту ответа на вызов.

Если вызов помечен как асинхронный, то успешный ответ обозначает, что запрошенное действие принято и будет выполнено позже в порядке обработки очереди запросов на такое же действие.

Асинхронный вызов возвращает track.id - номер трекера для отслеживания с помощью track.get

Общие поля ответа


{

 "request.id" : "то, что было в запросе в параметре request.id" 

,"duration" : null или "время обработки запроса в секундах" 

}

Нормальный ответ


{

 <общие поля>

,<поля специфические для конкретного запроса>

}

Формат ответа при ошибке

При наличии ошибки (ошибок), препятствующих полному или частичному выполнению запроса в ответе, появится массив errors со списком описаний каждой ошибки.

Обратите внимание, что в зависимости от вызова и самой ошибки, поле explain может быть не только строкой, но и массивом, и объектом.

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


{

 <общие поля>

."errors" :  [
              {
               "id" : "код ошибки-1" 

              ,"explain" : "возможное более развёрнутое описание-1" 

              ,<возможно поля специфические для конкретного запроса-1>
              }
             ,{
               "id" : "код ошибки-2" 

              ,"explain" : "возможное более развёрнутое описание-2" 

              ,<возможно поля специфические для конкретного запроса-2>
              }

              ......

             ]
}

вверх

Специальный ответ "Смена пароля"

Получение ответа с ошибкой "error/auth/failed" и уточнением "force_change_password" означает, что для продолжения работы требуется сменить пароль с помощью данной комбинации login/sublogin.

Это может быть вызвано как автоматическими событиями (например истёк срок действия пароля), так и прямой установкой такого требования через вызов user.set или sys.password.set

Для продолжения работы необходимо:


{
 <общие поля>

,"errors"   : [
               {
                "id: : "error/auth/failed" 

               ,"explain" : "force_change_password" 
               }
              ]
}

вверх

Кэширование

Часть вызовов поддерживает кэширование ответов, что позволяет быстрее получать ответ, если вы уверены, что данные не изменились, и не придумывать свою собственную систему кэширования. В данный момент это вызовы stat.uni и member.list.count.

Кэширование применяется только для вызовов, которые завершились без ошибок.

Для использования кэширования в вызове передаётся параметр cache с как минимум значениями mode и key.

В ответ вызова, использующего кэширование с режимами "use", "fetch" и "refresh", будет добавляться параметр cache с информацией о результате использования кэша.

Если вызов асинхронный, то параметр cache будет отсутствовать в ответе на основной вызов:

Если вызов асинхронный, то при режиме "fetch" и при отсутствии данных, основной вызов сразу завершится с hit == 0, а асинхронная часть даже не начнётся и её действия не будут выполнены. Проверяйте на наличие hit и его равенство 0 для установления факта не вызова асинхронной части.

Вызов:

{
 "action: "xxx" 

,"cache" : {
            "mode" : режим -- обязательно
                           --
                           -- ignore  - не использовать
                           --
                           -- use     - при наличии кэша - ответ из него
                           --         - при отсутствии - получить новый результат и закэшировать его
                           --
                           -- refresh - получить новый результат и закэшировать его
                           --
                           -- fetch   - при наличии кэша - ответ из него
                           --         - при отсутствии - зависит от вызова, но в общем случае нет даже самих ключей ответа
                           --                            проверяйте сначала на hit == 0
                           --         - в зависимости от вызова, не требуется передача всех или почти всех параметров необходимых
                           --           для работы вызова так как, при отсутствии данных в кэше, они не вычисляются заново.

            ,"key" : ключ кэша -- обязательно
                               --
                               -- до 64 печатных символов ASCII кроме пробела
                               -- для каждого вызова набор уникальных ключей свой
                               -- можно использовать один и тот же ключ с разными вызовами, они не будут смешаны
                               --
                               -- каждый ключ виден всем суб-логинам одного общего логина - один может что-то посчитать
                               -- и закэшировать, а остальные воспользоваться результатом

            ,"ttl" : желаемое максимальное время жизни кэша в секундах
                       -- не обязательно, применимо для use и refresh при записи к кэш
                       --
                       -- при отсутствии - теоретически время не ограничено
                       --
                       -- практически - любая запись в кэше может перестать существовать в любой момент
                       -- и данные будут получены путём обычного выполнения запроса
           }

 <прочие параметры вызова>
}

В ответ на "refresh":

{
 ....

 "cache" : {
            "hit" : 0

           ,"created" : "YYYY-MM-DD hh:mm:ss" -- дата и время занесения записи в кэш

           ,"expired"  : null или "YYYY-MM-DD hh:mm:ss" -- дата и время окончания времени жизни записи.
                                                        --  null - бесконечно, но прочитайте выше примечания к параметру ttl
           }

 ....
}

При отсутствии данных в кэше в ответе на "use" и "fetch" будет:

{
 ....

 "cache" : {
            "hit" : 0
           }

 ....
}

При использовании данных из кэша в ответе на "use" и "fetch" будет:

{
 ....

 "cache" : {
            "hit" : 1

           ,"created" : "YYYY-MM-DD hh:mm:ss" -- дата и время занесения записи в кэш

           ,"expired"  : null или "YYYY-MM-DD hh:mm:ss" -- дата и время окончания времени жизни записи.
                                                        --  null - бесконечно, но прочитайте выше примечания к параметру ttl
           }

 ....
}

вверх

Несколько запросов за один вызов

Параметры аутентификации session и one_time_auth работают только в основном запросе и игнорируются во вложенных.

Вызовы login и logout во вложенных запросах приводят в ошибке.

Запросы выполняются последовательно.

Запросы изолированы друг от друга и ничего не знают о результатах работы своих соседей. Т.е. схема "в одном запросе создать объект, а в следующем его изменить" в общем случае работать не будет.

Вы можете получить ошибку о слишком большом расходе памяти, если рассчитываете несколько статистических запросов в одном batch - выделяемая память переиспользуется между запросами, но не освобождается - суммарный её расход в какой момент может превысить предел.

{
 "action" : "batch" 

,"stop_on_error" : 0|1 -- прекращать выполнение после первого же запроса закончившегося с ошибками
                       -- не обязательно. по умолчанию 0
,"do" : [

               { один запрос }

              ,{ другой запрос }

              ....
             ]
}

ответ

{
 <общие поля>

,"result" : [

               { один ответ }

              ,{ другой ответ }

              ....
             ]
}

вверх

Проверка доступности и авторизации

Эти вызовы позволяют проверить, правильно ли вы работаете с API и авторизацией.

Пинг без авторизации


{

 "action" : "ping" 

}

ответ


{

 <общие поля>

,"pong" : "что-то, более-менее каждый раз разное" 

}

вверх

Пинг с авторизацией


{

 "action" : "pong" 

}

ответ


{

 <общие поля>

,"ping" : "что-то, более-менее каждый раз разное" 

}

вверх

Возвращаемое значение

Часть вызовов имеет возможность указать, что результат их работы можно сохранить как отчёт или загрузить на клиентский сервер.

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


 <другие параметры вызова>

,"result" : [
             { "type" : .... способ-сохранения-результата-1 }
            ,{ "type" : .... способ-сохранения-результата-2 }
            ............
            ]

Если параметр отсутствует, задан как пустой скаляр или задан как пустой массив, то по умолчанию выбирается значение "response".

Если запрошенное действие "response" или "none", то работа вызова происходит синхронно, и его результат возвращается сразу в ответе.

Иначе вызов возвращает track.id - номер запроса для отслеживания с помощью track.get, а само действие производится асинхронно.

Возможные действия с результатом приведены ниже.

Во всех случаях использования http(s)/ftp(s) ссылок в них работает кастомизация текущим временем {DT...} или случайным числом {RND} как описано в разделе "Кастомизированные ссылки". Такая же кастомизация работает для параметра filename. Таким образом, при использовании вызовов в "Действиях по расписанию" имеется возможность получать каждый раз разные ссылки и имена файлов.

Везде, где можно указать format, можно указать для него необязательные параметры:

- compress - zip, bzip2, gzip или пусто (не сжимать). По умолчанию не сжимать для xlsx и сжимать zip для csv, html и json.

- separator - разделитель полей для формата csv. По умолчанию - запятая, игнорируется для других форматов.

- always_quote - 0 или 1 - всегда обрамлять значение каждой ячейки для формата csv. По умолчанию - 0 - нет, игнорируется для других форматов.

- utf8 - 0 или 1 - использовать ли utf-8 как кодировку данных для формата csv. Игнорируется для других форматов.

Форматы csv и xlsx исторически созданы с расчётом на одно значение (число, строка, пусто) в ячейке. Если же значением ячейки является массив или хэш, то они будут закодированы в json-строку.

Для csv по умолчанию, для аккаунтов открытых до 28 февраля 2018 года используется windows-1251 (и включить utf-8 можно указав "utf8":1). Для аккаунтов, открытых с 28 февраля 2018 года по умолчанию используется кодировка utf-8 (вернуть windows-1251 можно, указав "utf8":0). Рекомендуется всегда использовать utf-8, так как, как минимум, в интернациональных email могут быть символы, не представленные в windows-1251, не говоря уже о данных подписчика.

Кодировка всех остальных форматов отчётов - utf-8.

Отправить результат на электронную почту

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

Название файла задаётся в необязательном параметре filename, а при его отсутствии генерируется автоматически.

Формат файла задаётся в необязательном параметре format, а при его отсутствии используется csv.

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

    { "type": "email_file", "to" : [ <email1>, .... ], "draft": <draft_id>, "filename": <filename>, "format": <xlsx|csv|html|json> },

Сохранить в Отчёты

После вычисления результата он сохраняется в хранилище отчётов.

Срок хранения файла - 90 дней, после чего он автоматически удаляется.

Название файла задаётся в необязательном параметре filename, а при его отсутствии генерируется автоматически. Название файла в дальнейшем доступно в результатах отслеживания запроса через track.get

Формат файла задаётся в необязательном параметре format, а при его отсутствии используется csv.

    { "type": "save", "to": <filename>, "format": <xlsx|csv|html|json> },

Загрузить на внешний ресурс пo http(s)

После вычисления результата он передаётся по ссылке указанной в to.

Если to не указан, то используется адрес владельца аккаунта.

Для загрузки по http(s) используется метод POST с типом multipart/form c именем переменной, указанным в name (необязательно, по умолчанию "file") и именем файла указанным в filename (необязательно, по умолчанию генерируется автоматически).

Формат файла задаётся в необязательном параметре format, а при его отсутствии используется csv.

    { "type": "url_file", "to" : <http(s)-url>, "name" : "<post-arg-name>", "filename": <post-filename>, "format": <xlsx|csv|html|json> },

Загрузить на внешний ресурс пo ftp(s)

После вычисление результата он загружается по ссылке, указанной в to.

Для загрузки по ftp(s) указывается сервер и каталог на сервере (параметр to), в который загружается файл с именем, указанным в filename (не обязательно, по умолчанию генерируется автоматически).

Формат файла задаётся в необязательном параметре format, а при его отсутствии используется csv

    { "type": "url_file", "to" : <ftp(s)-url>, "filename": <filename>, "format": <xlsx|csv|html|json> }

Отправить уведомление на электронную почту

После вычисления результата на адреса, указанные в массиве to высылается письмо с уведомлением об окончании расчёта.

Если to не указан, то используется адрес владельца аккаунта.

Обратите внимание, что сам результат в письме не содержится и его получение должно быть предусмотрено отдельно (email_file,save,url_file).

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

    { "type": "email_notify", "to" : [ <email1>, .... ], "draft": <draft_id> },

Отправить уведомление по sms

После вычисления результата на номера телефонов, указанные в массиве to, высылается сообщение с уведомлением об окончании расчёта.

Обратите внимание, что сам результат в письме не содержится и его получение должно быть предусмотрено отдельно (email_file,save,url_file).

    { "type": "sms", "to" : [ <cellphone1>, <cellphone2> ] },

Уведомить по внешней ссылке

После вычисления результата вызывается ссылка, указанная в to (методом GET).

Обратите внимание, что сам результат в вызове не содержится и его получение должно быть предусмотрено отдельно (email_file,save,url_file).

    { "type": "url_notify", "to" : <url> },

Вернуть в ответе

Результат вычисляется синхронно и возвращается в ответе.

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

Это действие предназначено для работы интерфейсов взаимодействия с человеком.

Хотя его и получится использовать при интеграции работы с API автоматических систем, но запросы могут быть автоматически прекращены системой по разным критериям (например, длительность выполнения).

Рекомендуется в случаях автоматизированного взаимодействия использовать схему работы с асинхронным получением ответа через запрос с сохранением результата в отчёты и последующим забором отчёта по готовности (можно определить по трекеру или получив callback) или с отправкой результата по готовности на ваш url.

    { "type": "response" },

Не возвращать

Это действие имеет смысл только для вызова Универсальной статистики при задании пересчёта кэша и позволяет сократить размер ответа.

    { "type": "none" },

вверх

Защищённые экземпляры сущностей

Большинство сущностей поддерживают защиту от изменения и удаления.

Такие сущности имеют свойство protected.

Оно принимает значения: 0 - не защищено, по умолчанию, 1 - защищено пользователем, -1 - защищено системой

При protected = 1 для изменения или удаления экземпляра необходимо в вызове передать дополнительный необязательный параметр "ignore_protection".

Значением ignore_protection должно быть значение id объекта.

Список сущностей которые поддерживают защиту:

Группы group.(
Черновики issue.draft.*
Форматы format.*
Анкета anketa.*
Формы form.*
Логины sys.user.*
Роли sys.role.*
Действия по расписанию cron.*
Триггеры sequence.*
DKIM-ключи issue.dkim.*
Адреса еmail-отправителей issue.emailsender.*
Названия sms-отправителей issue.emailsender.*

вверх

Авторизация

Настоятельно рекомендуется для вашей же безопасности при реализации автоматической работы по API создавать для этих целей отдельный саблогин с максимально ограниченными правами и использовать для его работы ключ API или JWT-токены, а саблогину отключить возможность работы с интерфейсом, исключив право на вызов login !

Авторизация по логину и паролю

Полученный в ответ идентификатор сессии должен передаваться во всех последующих запросах (кроме запросов "ping" и "login", в которых он игнорируется) как описано ниже.

Время жизни сессии несколько часов. Рекомендуется завершать явным вызовом "logout".

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

Для продолжения работы будет необходимо:

{

 "action" : "login" 

,"login"  : "общий логин" 

,"sublogin" : "личный логин" 

,"passwd" : "пароль" 

}

ответ

{

 <общие поля>

,"session" : "номер сессии" 

,"login" : "общий логин для которого выдана авторизация" 

,"sublogin" : "личный логин для которого выдана авторизация" 

}

Полученный в ответ идентификатор сессии должен передаваться во всех последующих запросах (кроме запросов "ping" и "login", в которых он игнорируется) или в самом запросе:


{
 "session" : "идентификатор сессии" 

 данные для вызова
}

или в HTTP заголовке Authorization:

Authorization: sendsay session=идентификатор-сессии-url-кодированный

вверх

Авторизация по ключу api

Создайте заранее ключ api для саблогина вызовом sys.user.apikey.create.

Авторизация заканчивается c окончанием вызова и завершения вызовом "logout" не требует.

Процессы, порождённые асинхронными вызовами, продолжат работать до их естественного завершения.

Для использования ключа api укажите его:

- или в самом запросе:

{
 "apikey" : "ключ api" 

 данные для вызова
}

- или в HTTP заголовке Authorization:

Authorization: sendsay apikey=ключ-api-url-кодированный

вверх

Авторизация c JWT

Требования к токену:

- соответствие RFC 7519 https://tools.ietf.org/html/rfc7519

- поддерживаются алгоритмы RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512

- содержимое полезной нагрузки учитываемое при аутентификации

{
 "account"  : "код аккаунта" 

,"sublogin" : "саблогин" -- не обязательно

,"exp" : целое число -- обязателен. timestamp момента окончания действия. использование интервалов менее 5 минут снизит эффективность работы

,"nbf" : целое число -- не обязательно, проверяется при наличии
}

- после генерации пары приватный-публичный ключ сообщите Службе поддержки публичный в формате PEM для внесения в настройки

- пример генерации пары RSA-ключей:

> openssl genrsa -out key.pem 1024

> openssl rsa -in key.pem -outform PEM -pubout -out public.pem

- для работы с api генерируйте jwt с использованием своего приватного ключа и предпочитаемого exp

Для использования jwt-токены укажите его:

- или в самом запросе:

Передаётся в параметре apikey с приставкой "jwt:'

{
 "apikey" : "jwt:токен" 

 данные для вызова
}

- или в HTTP заголовке Authorization:

Authorization: sendsay apikey=jwt:токен-url-кодированный

Авторизация AGSES-карт. Запрос

Авторизация биометрических карт AGSES происходит в два этапа:

1. На этапе запроса вы получаете flicker-код.
2. На этапе ответа сообщаете код, полученный клиентом от своей карты и, в случае успеха, в ответ получаете номер сессии.

{

 "action" : "login.agses.challenge" 

,"card" : номер карты, наличие лидирующих 0 не обязательно

}

ответ:

{

 <общие поля>

,"flicker" : код фликера

,"hedgeid" : номер запроса

}

вверх

Авторизация AGSES-карт. Ответ

Сессия, полученная в этом вызове точно такая же по своим свойствам, как и сессия от обычного вызова login. Храните её в надёжном, cухом, светлом месте вдали от детей.


{

 "action" : "login.agses.response" 

,"card" : номер карты, наличие лидирующих 0 не обязательно

,"hedgeid" : номер запроса

,"response" : ответ клиента

}

ответ:


{

 <общие поля>

,"session" : "номер сессии" 

,"login" : "общий логин для которого выдана авторизация" 

,"sublogin" : "личный логин для которого выдана авторизация" 

}

вверх

Окончание работы

Текущая сессия становится не действительной:


{

 "action" : "logout" 

}

ответ:


{

 <общие поля>

}

вверх

Логины и пароли в ссылках

Многие вызовы api могут получать данные, осуществляя доступ по http(s)/ftp(s) к сторонним серверам.

Хорошей практикой будет требовать логин-пароль при доступе к таким данным.

Однако использование стандартной для такого случая схемы записи ссылки с указанием логина-пароля прямо в ней

http://login:password@host.tld/bla/bla

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

Создайте один раз объект "Внешняя авторизация" (вызов authext.create, type = 0, login = "метка для памяти", "token" = "логин:пароль") и используйте его номер в ссылках в виде:

http://authext:777@host.tld/bla/bla

вверх

Отслеживание асинхронных запросов

Список асинхронных запросов

Список всех или отобранных по фильтру запросов.

Информация о запросах старше двух месяцев регулярно автоматически удаляется из системы.

Информация о запросах issue.send/personal хранится ещё меньше - два дня.

По умолчанию, не выдаются запросы issue.send/personal. Для их получения явно указывайте action = "issue.send/personal".

{

 "action" : "track.list" 

-- параметры фильтрации, должен быть хотя бы один параметр
--
-- если выбрана последняя порция списка, то ответ содержит "last_page" : 1
--
-- доступны поля
--
-- track.id         -- "идентификатор трека" 
-- track.host       -- "хост последнего изменения" 
-- track.pid        -- "процесс последнего изменения" 
-- track.login      -- "автор запроса, строковое значение" 
-- track.dt         -- "дата создания (Ys)" 
-- track.cron.id    -- "номер действия по расписанию, при выполнении действия которого появился этот трекер"  (null)
-- track.request    -- "код запроса" 
-- track.action     -- "название запроса, соответствующее коду request" 
-- track.dt_status  -- "дата установки статуса (Ys)" 
-- track.status     -- "код статуса: -2 - закончился с ошибкой; -1 - закончился без ошибок;  0 - принят; -- остальное зависит от вызова" 
-- track.error      -- "код ошибки завершения (строковый)" 
-- track.letter     -- "номер письма" 
-- track.email      -- "адреc эл. почты" 
-- track.objid      -- "некий id объекта которые может обрабатыватьcя/генерироваться запросом" 
-- track.info       -- "дополнительная информация, переданная в вызове, создавшем трекер через параметр track.info" 
-- track.reltype    -- число
-- track.relref     -- число

 ,"filter" : [ фильтр в синтаксисе stat.uni ]

 ,"order" : [ сортировка ответа в синтаксисе stat.uni ]

 ,"skip" : количество пропускаемых записей от начала списка -- по умолчанию 0

 ,"first" : количество выбираемых записей после skip -- по умолчанию 50, не более 50
}

ответ:

{
 <общие поля>

 "list" : [
            {
             объект как track.get

             для issue.send отсутствует letter->message по сравнению с track.get
            }

           .........
          ]
}

Состояние асинхронного запроса

{

 "action" : "track.get" 

,"id"     : номер трекера

}

ответ

{
 obj : {
        "id" : номер трекера

       ,"dt" : дата-время создания (YYYY-MM-DD hh:mm:ss)

       ,"action" : -- тип запроса
                    "issue.send" 
                    "issue.send/personal" 
                    "stat.uni" 
                    "member.list" 
                    "member.list.count" 
                    "member.import" 
                    "member.sendconfirm" 
                    "member.update" 
                    "member.delete" 
                    "member/activate" 
                    "issue.split.create" 
                    "stoplist.add" 
                    "stoplist.delete" 
                    "stoplist.erase" 
                    "sequence.member.start" 
                    "sequence.member.pause" 
                    "sequence.member.resume" 
                    "sequence.member.stop" 
                    "email.test" 
                    "email.cleanerror" 
                    "cron/run" 
                    "sys.message" 

       ,"status" : состояние запроса
                    -- -7 - действие приостановлено
                    -- -6 - действие не прошло модерацию
                    -- -5 - действие на премодерации
                    -- -4 - отложенное действие (например, отложенный выпуск рассылки)
                    -- -3 - отменён
                    -- -2 - закончился ошибкой
                    -- -1 - закончился успешно
                    --  0 - принят
                    --  1 - запущен
                    --  2 - начата обработка
                    --  3 - сортировка
                    --  4 - форматирование
                    --  5 - генерация отчёта
                    --  6 - антиспам проверка
                    -- остальное - зависит от типа запроса

       ,"status.dt" : дата-время установки текущего состояния (YYYY-MM-DD hh:mm:ss)

       ,"cron.id" : номер действия по расписанию, при выполнении действия которого появился этот трекер  (null)

       ,"error"  : код ошибки    -- не обязательно
                                 -- смысл зависит от типа запроса

       ,"track.info" : "строка" -- дополнительная информация, переданная в вызове, создавшем трекер через параметр track.info. 1024 байта (null)

       ,reltype" : ...
       ,relref" : ...

   -- дополнительные параметры в зависимости от запроса и его состояния

      ,"param" : {

   -- === issue.send ===
   -- === issue.send/personal ===

                   ,"group.id" : "код группы" 

                   ,"group.name" : "название группы" 

                   ,"api.request" : { ... } -- оригинальный запрос на рассылку. для personal - не запоминается. для всех - без attaches. для masssending и без users.list.

                   ,"api.request.id" : "........" -- оригинальный номер запроса на рассылку. для personal - не запоминается.

                     -- характеристики выпуска, если они уже известны

                   ,"issue.id" : номер выпуска

                   ,"issue.dt" : время начала выпуска

                   ,"issue.size" : количество sms для sms  или примерный размер сообщения в байтах для других

                   ,"issue.members" : количество получателей, для которых уже сформированы письма

                   ,"issue.format" : "email" , "sms" , "viber", "push", "vk", "tg" ,"vknotify" 

                   ,"issue.name" : "название выпуска" 

                   ,"issue.split.id" : "номер сплит-тестирования, если это его выпуск" 

                   ,"issue.split.variant" : "номер варианта сплит тестирования" 

                   ,"issue.split.winner" : "выпуск - победитель в сплит тестировании" 

                   -- в состоянии "начата обработка" примерная информация о достигнутом прогрессе

                   ,"eta" : {

                             "rest"        : примерное количество секунд до окончания

                            ,"perc"        : примерный процент обработки

                            ,"dt.finished" : примерное дата-время окончания

                            ,"dt.updated   : дата-время последнего обновления информации eta
                            }

                   -- информация о номере письма для Транзакционных писем после завершения работы

                   ,"letter" : номер письма -- пусто, если адресу не было отправлено письмо (например, подписчик отписался или какие-то ошибки выпуска)

                   ,"email" : адрес получателя

                   ,"error" : ошибка обработки если была

                   ,"error.info" : дополнительная информация произвольной структуры об ошибке обработки

                   -- отчёт и статистика после завершения работы для Экспресс-выпуска

                   ,"report_file" : "название файла" -- название файла с отчётом для использования в вызове rfs.file.get
                                                     -- отчёт формируется, если в выпуске были адреса с ошибкой обработки
                                                     -- название предсказуемо masssending/<НОМЕРВЫПУСКА>.zip, в котором содержится файл НОМЕРВЫПУСКА.csv

                   ,"statistic" : {
                                   "total"       : всего строк
                                  ,"taked"       : верных строк
                                  ,"erroneous"   : строк с ошибками
                                  ,"repeated"    : повторов адресов
                                  ,"only_unique" : 0|1 -- был ли выпуск запущен с запретом повторной
                                                       -- рассылки по дублирующимся адресам
                                  }

   -- === stat.uni ===

                    "report_file" : "название файла" -- пусто до окончания запроса
                                                     -- после окончания - название файла с отчётом для вызова rfs.file.get
                                                     -- или пусто, если заказывалась высылка на почту

                   ,"api.request" : { ... } -- запрос, для которого появился трекер

                   ,"api.request.id" : "........" -- номер запроса, для которого появился трекер

   -- === member.list ===

                    "group.id" : "код группы" -- пусто, если считалось без указания группы,
                                              -- иначе код группы, указанной для расчёта

                   ,"group.name" : "название группы" 

                   ,"filter" : [ фильтр использованной группы или параметр group.filter ] -- если считалось по группе или по явно указанному фильтру

                   ,"report_file" : "название файла" 

                   ,"eta" : { в состоянии "начата обработка" примерная информация о достигнутом прогрессе }

   -- === member.list.count ===

                    "group.id" : "код группы" -- пусто, если считалось без указания группы,
                                              -- иначе код группы, указанной для расчёта

                   ,"group.name" : "название группы" 

                   ,"filter" : [ фильтр использованной группы ] -- если считалось по группе

                   ,"statistic" : { копия результата расчёта как в ответе вызова }

                   ,"eta" : { в состоянии "начата обработка" примерная информация о достигнутом прогрессе }

   -- === member.import ===

                    "group.id" : "код группы" -- пусто, если при импорте не указывалось автосоздание новой
                                              -- или заполнение существующей группы, иначе код автосозданной
                                              -- существующей группы

                   ,"group.name" : "название группы" 

                   ,"eta" : { в состоянии "начата обработка" примерная информация о достигнутом прогрессе }

                   -- отчёты и статистика после завершения работы

                   ,"report_file" : "название файла" -- файл в формате xlsx с отчётом в хранилище отчётов, если были ошибки
                                                     -- первые 1000 ошибок для просмотра человеком

                   ,"report_file.json" : "название файла" -- файл в формате json запакованный zip с отчётом в хранилище отчётов если были ошибки
                                                          -- все ошибки в машиноразбираемом виде

                   ,"report_file.csv" : "название файла" -- файл в формате csv запакованный zip с отчётом в хранилище отчётов если были ошибки
                                                         -- все ошибки в машиноразбираемом виде

                   ,"statistic" : {
                                   "inserted"    : добавлено новых адресов
                                  ,"updated"     : обновлено адресов
                                  ,"economed"    : без изменений (вносимые данные совпали с имеющимися)
                                  ,"erroneous"   : строк с ошибками
                                  ,"unconfirmed" : новых адресов внесённых без необходимости подтверждения
                                  ,"needconfirm" : новых адресов внесённых с необходимостью подтверждения
                                  ,"repeated"    : повторов адресов
                                  ,"ignored" -   : игнорировано из-за if_exists : ignore
                                  ,"listadd"     : адреса, добавленные в группу импорта
                                  ,"datarows"    : строк с данными (т.е. строк которые было было попробовано импортировать)
                                  ,"upserted"    : inserted + updated
                                  ,"uniqs"       : количество уникальных идентификаторов в импорте

                                  }

                   ,"api.request" : { ... } -- запрос, для которого появился трекер

                   ,"api.request.id" : "........" -- номер запроса, для которого появился трекер

   -- === member/activate ===

                    "group.id" : "код группы" -- код активируемой группы

                   ,"group.name" : "название группы" 

                   ,"eta" : { в состоянии "начата обработка" примерная информация о достигнутом прогрессе }

                   -- статистика после завершения работы

                   ,"statistic" : {
                                   "done       : активировано адресов
                                  ,"notneed"   : для какого количества адресов не требовалась активация
                                  ,"overlimit" : не активировано адресов из-за исчерпания лимита
                                  }

   -- === email.test ===

                    "group.id" : "код группы" -- пусто, если считалось без указания группы,
                                              -- иначе код группы, указанной для расчёта

                   ,"group.name" : "название группы" 

                   ,"report_file" : "название файла" 

                   ,"eta" : { в состоянии "начата обработка" примерная информация о достигнутом прогрессе }

                   ,"records" : число -- количество обработанных записей

                   ,"method": "group | list | filter | url |stat.uni" 

                   -- для filter

                  ,"filter" : [ filter из запроса ]

                  -- для url

                  ,"url" : "url из запроса" 

                  -- для stat.uni

                  ,"query" : [ query из запроса ]

   -- === group.snapshot ===

                    "group.id" : "код исходной группы" -- пусто, если считается без указания группы,
                                                      -- иначе код группы, указанной для расчёта

                   ,"group.name" : "название исходной группы" 

                    "togroup.id" : "код получающей группы" 

                   ,"togroup.name" : "название получающей группы" 

                   ,"eta" : { в состоянии "начата обработка" примерная информация о достигнутом прогрессе }

                   ,"records" : число -- количество обработанных записей

                   ,"method": "group | list | filter | url |stat.uni" 

                   -- для filter

                  ,"filter" : [ filter из запроса ]

                  -- для url

                  ,"url" : "url из запроса" 

                  -- для stat.uni

                  ,"query" : [ query из запроса ]

   -- === member.sendconfirm ===
   -- === member.update ===
   -- === member.delete ===
   -- === stoplist.add ===
   -- === stoplist.delete ===
   -- === stoplist.erase ===
   -- === group.clean ===
   -- === sequence.member.start ===
   -- === sequence.member.pause ===
   -- === sequence.member.resume ===
   -- === sequence.member.stop ===
   -- === email.cleanerror ===

                    "group.id" : "код группы" -- пусто, если считается без указания группы,
                                              -- иначе код группы, указанной для расчёта

                   ,"group.name" : "название группы" 

                   ,"eta" : { в состоянии "начата обработка" примерная информация о достигнутом прогрессе }

                   ,"records" : число -- количество обработанных записей

                   ,"method": "group | list | filter | url |stat.uni" 

                   -- для filter

                  ,"filter" : [ filter из запроса ]

                  -- для url

                  ,"url" : "url из запроса" 

                  -- для stat.uni

                  ,"query" : [ query из запроса ]

   -- === issue.split.create  ===

                    -- отслеживается не "создание" (как формально следует из названия),
                    -- а процесс формирования списка получателей и назначения им того или иного варианта,
                    -- соответственно, трекер отслеживает тестирование в состояние "подготавливается к запуску" 

                    "split.id" : "номер сплит-тестирования" 

                   ,"eta" : { в состоянии "начата обработка" примерная информация о достигнутом прогрессе }

   -- === cron/run ===

           "cron.id" : "номер крона" 

           "cron.name" : "название крона" 

           "errors" : "количество ошибочных запусков подряд" 

           "result" : { -- последний ответ api c ошибкой, полученной при исполнении крона, в нём интересен errors
                      .......
                      }

   -- === sys.message ===

           "text" : "сообщение от системы или Службы поддержки" 

       }
}

Изменить состояние асинхронного запроса.

Выпуск рассылки управляется отдельно через issue.running.* !

Если трекер в состоянии 0 (принят), 1 (запущен) или 2 (начата обработка), то для поддерживаемых вызовов его состояние можно установить в -3 (отменено) или -7 (пауза).

Это приведёт через некоторое время к прекращению работы асинхронного запроса состояние которого отражает трекер.

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

Если запрос по завершении работы должен сохранить или вывести отчёт, то такой отчёт может быть создан в не полном объёме, а только на основании обработанной части.

Например, отмена member.list вернёт список только тех, кого успел перечислить.

{
 "action" : "track.set" 

 ,"id" : номер трекера

 ,"status" : -3 -- отмена. для всех поддерживаемых вызовов
             -7 -- пауза. для member.import
}

Поддерживаемые вызовы:

member.import

member.list member.list.count

stat.uni

group.snapshot

stoplist.add stoplist.delete

email.test email.cleanerror

member.delete member.update member.sendconfirm member.where

вверх

Интеграция

Для получения данных снаружи и внесения их в систему Sendsay можно использовать импорт данных (вызов member.import) или callback (настройка через sys.settings.get)

Для выгрузки данных из системы Sendsay можно использовать выгрузку данных подписчиков (вызов member.list), массовую выгрузку статистических данных (вызов stat.uni) и информирование внешней системы о событиях в реальном времени через Callback/Webhook (смотрите соответствующий раздел)

Традиционные источники

Используйте обычные ссылки (http, https, ftp, ftps, soap, soaps) на внешние данные для вызова member.import для импорта данных и при выпуске рассылки для формирования списка получателей.

Siebel

Используйте вызов member.import со схемой soap:// для импорта данных.

Google Big Query

Используйте вызов member.import со схемой gbd:// для импорта данных и при выпуске рассылки для формирования списка получателей.

Используйте Callback/Webhook со схемой gbd:// для передачи данных о событиях в Google Big Query.

Flocktory

Получите ссылку через параметр integration.flocktory вызова sys.settings.get и указывайте её в настройках Flocktory, чтобы данные передавались в Sendsay в реальном времени.

Tilda

Получите ссылку ссылку через параметр integration.tilda вызова sys.settings.get и указывайте её в настройках форм Tilda, чтобы данные передавались в Sendsay в реальном времени.

Можно настроить как все формы Tilda на одну и ту же ссылку, так и индивидуально каждую форму на свою ссылку.

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

Дополнительные данные, получаемые от формы, запоминаются в подписчике в ключе данных tilda.названиеформы, где "название формы" это её название в Tilda.

Настоятельно рекомендуется, что бы название состояло только из символов a-zA-Z0-9, "-" и "_" - это позволит без дополнительных действий напрямую использовать такие ключи в персонализации писем.

Названия содержащие прочие символы будут нормализованы - все не буквы (любого языка) и не символы 0-9, "-" и "_" будут заменены на символ "_".

Например, данные формы с названием "Данные от клиенты" будут сохранены в ключе данных "tilda.Данные_от_клиента". Их без проблем можно будет проматривать и использовать в фильтрах, но вместо просто подстановки персонализации [% tilda.Данные_от_клиента %] придётся конструировать имя ключа данных в промежуточной переменной языка персонализации ProScipt и работать далее с ним.

Формы без названия записываются в tilda.default.

Новое заполнение формы полностью заменяет в данных подписчика старое.

amoCRM

Используйте вызов member.import со схемой amocrom:// для импорта данных.

Или получите ссылку ссылку через параметр integration.amocrm вызова sys.settings.get и указывайте её в настройках amoCRM, чтобы данные передавались в Sendsay в реальном времени.

Bitrix24

Используйте вызов member.import со схемой bitrix24:// для импорта данных.

Или получите ссылку ссылку через параметр integration.bitrix24 вызова sys.settings.get и указывайте её в настройках Bitrix24, чтобы данные передавались в Sendsay в реальном времени.

Яндекс.Маркет

Для использования данных о товарах из Яндекс.Маркет (формат YML) для персонализации писем воспользуйтесь интерфейсом. Или обратитесь в Службу поддержки (ask@sendsay.ru) для разовой настройки регулярного автоматического получения данных от вас.

Для тестирования разбора вашего файла или его импорта вне расписания используйте вызов cron.runonce.

Примеры использования описаны ниже в разделе подключения внешних данных.

Mail.Ru Смарт-карточки

Для подстановки в письмо кода смарт-карточки Mail.Ru ( https://help.mail.ru/developers/jsonld/smartcards/ ) используйте команду шаблонизатора [% mailru_smartcard(...) %]

Обратите внимание, что в данный момент код валюты подставляется автоматически как RUB.

Значения параметров должны соответствовать требованиям Mail.Ru.

Возможны два способа передачи параметров:

- по значению:

[% mailru_smartcard("type", "Order", "name", "Симулякр", "offer_name", "Заказ", "order", "123-4567890-1234567", "price", "539.00") %]

- готовой структурой:


[% data = { "type" => "Order", "name" => "Симулякр", "offer_name" => "Заказ", "order" => "123-4567890-1234567", "price" => "539.00" } %]

[% mailru_smartcard(data) %]

Далее описаны возможные для генерации смарт-карточки в формате "Название параметра в шаблонизаторе" => Описание параметра [ Генерируемый путь в шаблоне mail.ru ]

Счёт (Invoice)

Параметр "type" должен иметь значение "Invoice".

Обязательные параметры:

"type" => "Invoice" -- обязательная константа
"name" => Ваше краткое название для карточек ['provider', 'name']
"id" => Идентификатор вашего клиента ['accountId']
"payment" => Сумма счёта ['totalPaymentDue', 'price']
"payment_due" => Крайний срок оплаты ["paymentDue"]

Дополнительные параметры:

"description" => Назначение платежа ["description"]
"legal_name" => Официально название компании ["provider", "legalName"]
"payment_min" => Минимальная сумма к оплате ["minimumPaymentDue", "price"]
"logo" => Ссылка на логотип компании  ["provider", "logo"]
"billing_period" => Период оплаты ["billingPeriod"]
"customer_name" => Имя заказчика ["customer", "name"]
"customer_address" => Адрес заказчика ["customer", "address"]
"customer_phone" => Телефон заказчика ["customer", "telephone"]
"url" => Ссылка на заказ, продукт, файл со счётом ["url"]
"image" => Ссылка на превью ["image"]

Заказ (Order)

Параметр "type" должен иметь значение "Order".

Обязательные параметры:

"type" => "Order" -- обязательная константа
"name" =>  Ваше краткое название для карточек ["merchant", "name"]
"order" => Номер заказа ["orderNumber"]
"price" => Общая сумма заказа ["price"]
"offer_name" => Наименование заказа ["acceptedOffer", "itemOffered", "name"]

Дополнительные параметры:

"legal_name" => Официальное название компании ["merchant", "legalName"]
"url" => Ссылка на корзину ["url"]
"offer_url" => Ссылка на корзину ["acceptedOffer", "itemOffered", "url"]
"offer_image" => Ссылка на превью-картинку заказа ["acceptedOffer", "itemOffered", "image"]

EmailOnAcid

Сервис EmailOnAcid https://www.emailonacid.com/ позволяет посмотреть, как будет выглядеть ваше письмо в разных почтовых клиентах и сервисах.

Вам необходимо создать свою учётную запись в этом сервисе.

Потом разово создать у нас внешнюю авторизацию с типом 15 - вызовы authext.*

После чего можно использовать вызов предпросмотра issue.draft.preview для получения результатов от EmailOnAcid.

Календарное событие

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

Для подстановки в письмо события календаря используйте команду шаблонизатора [% calendar_event(...) %]

Данная команда "визуально" ничего не меняет в письме, но добавляет в него необходимые элементы для того, чтобы совместимые почтовые системы распознали событие.

Возможна также подстановка ссылки на Google Calendar с помощью команды [% calendar_event_gc_url(...) %]

Есть два способа передачи параметров:

- по значению

[% calendar_event("name", "Важное событие", "start_date", "2020-01-01 00:00:00", ......) %]

- готовой структурой


[% ce = { "name" => "Важное событие", "start_date", "2020-01-01 00:00:00", ....... } %]

[% calendar_event(ce) %]

Параметры:

name - Название события
description - Описание события
start_date - Дата начала - YM или Ys
end_date - Дата окончания (если есть) - YM или Ys
location - Адрес места проведения события
organizer - Название организатора события
organizer_email - Email адрес организатора события
url - Ссылка на что-то связанное с событием (описание на сайте, индивидуальная страница настройки события)

Gmail Promotional Annotations

Для подстановки в письмо блока промо-акций Gmail используйте команду шаблонизатора [% gpa(...) %]

Как обычно, параметры можно передать как списком, так и готовой структурой.

Обязательные параметры:

name - Имя отправителя
logo - https-url логотипа отправителя, не анимационное изображение 48x48
subject - Тема

Дополнительные параметры:

discount_description - Описание предложения
discount_code - Код предложения
start_date - Дата начала действия кода YD или Ys
end_date - Дата окончания действия кода YD или Ys
promocard - промо-изображение, https-url, не анимационное изображения 538x138

Другое

Сообщите нам, если вам не хватает какого-либо источника. Мы изучим, как интегрировать это в систему.

Подписчики

Идентификация подписчика

Каждый подписчик имеет свой набор связанных с ним данных и идентифицируется в системе своими адресом электронной почты - email, и/или номером мобильного телефона - msisdn, и/или номером Viber - viber, и/или клиентским идентификатором - csid, и/или идентификатором регистрации - push, и/или номером регистрации ВКонтакте - vk, и/или номером регистрации в Телеграм - tg, и/или номером мобильного телефона для VK Notify - vknotify.

Именно их вы указываете в параметре, традиционно называемом "email", и в данных импортирования.

При сохранении, каждому идентификатору система назначает свой внутренний целочисленный номер. Он постоянен (даже если подписчика удалить и потом создать заново) и доступен вам как member.id.

Так как указание адреса или телефона в открытом виде не всегда допустимо, то система предоставляет клиенту две возможности по их сокрытию - "Клиентский идентификатор" и "Заменитель адреса", которые описаны ниже.

Большинство вызовов также понимают тип идентификатора "id" - системный номер уже существующего подписчика.

Набор данных, связанный с идентификатором, тоже имеет свой номер - member.dataset. Но этот номер не постоянен. Если вы создали, удалили и опять создали подписчика с одним и тем же идентификатором, то его системный номер member.id будет в обоих случаях один и тот же, а номер набора данных будет другой.

Номер набора данных напрямую не требуется для работы и доступен только для того, чтобы:

Использование адреса электронной почты

Система полностью поддерживает национальные доменные имена (IDN) и национальные имена получателей. Вы можете без проблем использовать адреса не только из домена .рф (например проверка@тест.рф), но и из любых других национальных доменов (например, 企业@企业.企业), и записывать из без всяких ухищрений (типа xn--кодирования) просто как есть.

Система нормализует email, приводя их к написанию маленькими буквами (да, формально это не по стандарту, но такова практика всех почтовых систем).

Также производится проверка email-адреса на соответствие правилам RFC и правильности указания его домена верхнего уровня.

Национальные почтовые адреса по умолчанию включены для всех аккаунтов, созданных с 2016-07-26. Для ранее созданных аккаунтов возможность включается через параметр email.utf8 вызова sys.settings.set. Если к моменту включения вы уже имели в базе адреса с xn--кодированием, то обратитесь в Службу поддержки для их преобразования.

Использование номера мобильного телефона

Система нормализует номера телефонов к виду +7XXXYYYYYYY.

Также производится проверка номера телефона на наличие в реестре "Российской системы и плана нумерации" Федерального агентства связи.

Использование клиентского идентификатора

Клиентский идентификатор регистрозависим и нормализуется только удалением начальных и конечных пробельных символов. Остальное в нём - ваше дело. Для системы это просто набор символов.

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

Но кроме такого простого использования вам доступен более продвинутый способ - система может следить за уникальностью вашего клиентского идентификатора при внесении и изменении данных, и вы можете указывать его в "Экспресс-выпуске" и "Транзакционных письмах" вместо реального адреса.

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

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

Если же вы подключите в Службе поддержки возможность делать выпуски и импорты по клиентскому идентификатору, то подрядчик сможет производить "Экспресс-Выпуск" и "Транзакционные Письма" с использованием известного ему идентификатора, который система сама превратит в адрес получателя.

Также это даёт возможность импортировать данные с указанием не настоящего адреса, а вашего клиентского идентификатора.

Хранение заменителя адреса

Вы можете хранить в системе не настоящие email-адреса, а их заменители. Главное, чтобы по заменителю адреса вы могли однозначно понять о каком вашем подписчике идёт речь.

Во всех отчётах и данных системы будет использоваться именно заменитель.

Реальный адрес необходимо будет указывать только при выпуске рассылки (к сожалению, при использовании заменителя адреса вам будет доступен только "Экспресс-Выпуск" и "Транзакционные Письма").

Данная возможность настраивается по обращению в Службу поддержки.

Мультиканальность

Одному подписчику могут соответствовать несколько идентификаторов (member.email), по которым вы получите доступ к одному и тому же набору данных (member.dataset).

Думайте об этом как о Змее-Горыныче: несколько голов (идентификаторов) с одним телом (набором данных).

В данный момент доступны идентификаторы видов: email-адрес, номер мобильного телефона, клиентский идентификатор, номер push-подписки, номер регистрации ВКонтакте, номер регистрации в Телеграм.

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

У набора данных должен быть хотя бы один идентификатор подписчика связанный с ним (тело без головы не живёт).

Участие подписчика в группах-списках учитывается по идентификаторам подписчика. Разные идентификаторы могут быть внесены в разные группы-списки (впрочем, в одну и туже - тоже).

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

Например, если у тела есть почтовая голова с фатальными ошибками доставки, то из email-рассылки такое тело будет исключаться, но на рассылку sms это не повлияет.

Благодаря поддержке нескольких идентификаторов у одного подписчика вам доступны разнообразные сценарии мультиканального взаимодействия с ним. Например, "Выслать письмо, а тем кто за 10 дней так и не прочёл и не сделал ни одного перехода выслать смс".

Форматы хранения

В данный момент имеется два формата хранения - текущий с моделью "Анкета-Вопрос-Ответ" (АВО) и новый с моделью хранения "Ключи Данных" (КД).

Формат АВО проверен временем, но имеет несколько недостатков, которые делают его непригодным для реализации сложных проектов.

Формат КД разработан как замена АВО и может быть использован в любых проектах.

Формат "Анкета-Вопрос-Ответ"

Формат АВО хранит данные в жестко структурированной форме "Анкет" которые состоят из "Вопросов" разных типов.

Данные о подписчике могут быть представлены только как "Ответы" на "Вопросы Анкет".

Формат АВО имеет только одно преимущество - при доступе к данным и изменении данных система следит за тем, что указаны существующие "Анкета" и "Вопрос", и проверяет соответствие данных типу "Вопроса".

Недостатки формата АВО более многочисленны и печальны:

- ограничен набор символов, которые можно хранить

- невозможно хранить несколько ответов на один "Вопрос" (массив)

- невозможно хранить структуры данных произвольной сложности - иерархия данных только двухуровневая "Анкета-Вопрос"

- в перспективе формат будет полностью заменён на КД и объявлен устаревшим

Формат "Ключи Данных"

(Изучите отдельную главу, описывающую ключи данных)

Новый формат КД разработан для замены форматы АВО и поддержки хранения сложных структур данных

- Данные формата АВО полностью доступны через КД

- Нет ограничения на набор хранимых данных

- Нет ограничений на сложность иерархии хранения данных и их сочетание (например, "адреса подписчика" могут представлять собой массив из объектов, хранящих структуру одного адреса по компонентам (страна, город, улица, дом, квартира, координаты), а компоненты адреса в свою очередь там же быть объектами - "координаты" это "широта" и "долгота").

- Нет ограничений на формат данных

- У разных подписчиков по одному и тому же ключу данных можно хранить разные структуры и типы данных

- Доступ к данным в выпуске рассылки осуществляется как и прежде путём прямой записи [% anketa.ключ.данных %] или через функцию datakey()

Cистемная анкета member

Через системную анкету member вы можете получить дополнительную служебную информацию о подписчике.

В формате "Ключи Данных" она имеет вид:

{
 "member" : {
             -- информация от наборе данных (теле) подписчика, общая для всех его идентификаторов (голов)

            "create" : {
                        "time" : "2015-09-12 18:53:39" -- дата создания (Ys) набора данных
                       ,"host" : "1.1.1.1"             -- источник создания набора данных
                       }

           ,"update" : {
                        "time" : "2015-09-12 18:53:39" -- дата последнего изменения (Ys) набора данных
                       ,"host" : "1.1.1.1"             -- источник последнего изменения набора данных
                       }

           ,"import" : {
                        "time" : "2015-09-12 18:53:39" -- дата последнего изменения (Ys) набора данных при импорте
                       }

            -- информация о том идентификаторе (голове), через который прочитаны данные, своя для каждого идентификатора

           ,"addr_type" : "email"    -- тип идентификатора (email, msisdn, viber, csid, push, vk, tg, vknotify)

           ,"email" : "test@test.ru" -- идентификатор

           ,"domain" : "test.ru"     -- домен почтового адреса, если идентификатор email

           ,"id" : 1380900         -- внутренний номер идентификатора в системе

           ,"dataset" : 1231273891 -- внутренний номер набора данных (тело), с которым связан данный идентификатор (голова)

           ,"last" : {
                      "tz" : число -- временная зона последнего клика или чтения. Если устройство подписчика определено как "Робот", то не учитывается
                                   -- хранится смещение от UTC в часах и минутах (HHMM или -HHMM), так как это число, но лидирующих нулей нет
                                   -- подходит автоматически ведомый системой источник данных для выпусков с учётом временной зоны получателя
                                   -- (вызов issue.send, параметр tz_observance)
                     }

           -- информация о блокировках идентификатора (головы), через который прочитаны данные, своя для каждого идентификатора

           ,"haslock" : 0,  -- все блокировки идентификатора в одном параметре. Может ли получать сообщения
                           -- 0 - нет            - может
                           -- 1 - отписался      - не может
                           -- 2 - не подтверждён - не может
                           -- 4 - имеет фатальные ошибки доставки - не может получать письма
                           -- прочие значения ( 3,5,6,7 ) - одновременное наличие нескольких указанных блокировок

           ,"confirm" : { -- состояние подтверждения внесения в базу
                        "lock" : 0,                    -- 0 - подтверждено, 1 - нет
                       ,"time" : "2015-09-12 18:53:39" -- дата подтверждения (Ys)
                       ,"host" : "1.1.1.1"             -- источник подтверждения
                       }

           ,"error" : { -- состояние ошибок доставки
                       "lock" : 1                       -- заблокирован из-за ошибок доставки (1) или нет (0)
                      ,"date" : "2015-09-12 18:53:39"   -- дата последней ошибки доставки (Ys) (удаляется при первой же успешной доставке)
                      ,"str"  : "name=test.ru type=A: Host not found" -- описание последней ошибки доставки (удаляется при первой же успешной доставке)
                      ,"error" : 1                      -- общее число ошибок доставки, произошедших подряд (устанавливается в 0 при первой же успешной доставке)

                      ,"issue"  : 123 -- выпуск последней ошибки доставки
                      ,"letter" : 123 -- письмо последней ошибки доставки

                      ,"lock_issue"  : 456 -- выпуск приведший к блокировке
                      ,"lock_letter" : 456 -- письмо приведшее к блокировке
                     }

           ,"lockremove" : 0 -- адрес отписан или в стоп-листе (1) или нет (0)

           -- информация об участии в сообществах ВКонктаке для подписчиков с типом vk

            "vk" : {
                    "номер сообщества" : {
                                          "sub" => "дата Ys подписки" -- подписан

                                         ,"member" => "дата Ys вступления" -- участник

                                         ,"unsub" => "дата Ys отписки" -- отписался

                                         ,"left' => "дата Ys покидания" -- покинул

                                         ,"err" => "дата Ys последней ошибки" -- ошибки доставки
                                         }
                   }

           -- информация о подписчиках Телеграм для подписчиков с типом tg

            "tg" : {
                    "номер бота" : {
                                          "sub" => "дата Ys подписки" -- подписан

                                         ,"unsub" => "дата Ys отписки" -- отписался

                                         ,"err" => "дата Ys последней ошибки" -- ошибки доставки
                                         }
                   }

           }
}

В формате АВО анкета member содержит те же поля, но с названиями адаптированными к формату "анкета.вопрос". Например member.confirm.time

Полей member.dataset, member.last.tz member.vk.*, member.tg.* в ABO нет.

Псевдо-ключ -group"

Псевдо-ключ "-group" описывает участие и дату внесения идентификатора в группах списка.

Перечисляются коды только тех групп-списков, в которых состоит именно этот идентификатор.

Дата внесения - дата и время с точностью Ys внесения в группу-список.

Фактическая точность - YD, но для совместимости с возможными будущими изменениями она увеличена до Ys добавкой "00:00:00".

{
 "-group" : {
              "gid1" : "дата-время внесения" 

             ,"gid2" : "дата-время внесения" 

                .............

             ,"gidN": "дата-время внесения" 

            }

В фильтрах сегментов для отбора "Участвует/Не участвует в группе-списке" используйте операции "in_group" и "!in_group".

Для фильтрации по дате внесения - обычные операции сравнение и ключ данных "-group.кодгруппы".

Проверить существование подписчика


{
  "action": "member.exists" 

 ,"email": "идентификатор подписчика" 

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификатора. Не обязательно, система сама распознает email или msisdn
}

ответ


{
  <общие поля>

 "list" : {
           "адрес электронной почты или номер телефона" : 0|1|null -- подписчик существует (1) или нет (0) в базе, null - ошибка в адресе
                                                                   -- в случае невалидного адреса запись в warnigns
          },

,"warnings" : [ cписок предупреждений про невалидные адреса ]

}

Все подписчики с данным идентификатором

Одна и та же строка символов может быть использована как идентификатор подписчика для разных типов идентификаторов. Также усложняет жизнь то, что идентификаторы типа email и msisdn хранятся в нормализованном виде.

Это означает, что по строке нельзя однозначно сказать какого типа идентификатор она представляет. Например, "test@test.RU" может быть и email-адресом "test@test.ru" и идентификатором типа csid "test@test.RU".

Данный вызов позволяет получить для заданной строки все типы идентификаторов, соответствующих ей (с учётом нормализации), для подписчиков в системе.

{
  "action": "member.find" 

 ,"email": "идентификатор подписчика" 

}

ответ

{
  <общие поля>

 "list" : [

           {
            "id" : номер подписчика-1

           ,"addr_type : "тип идентификатора-1" 

           ,"email" : "идентификатор-1" -- нормализованный
           },

           {
            "id" : номер подписчика-2

           ,"addr_type : "тип идентификатора-2" 

           ,"email" : "идентификатор-2" -- нормализованный
           }

           ......

          ]
}

Cоздать подписчика / Обновить данные подписчика (КД)

При отсутствии адреса в базе он создаётся автоматически.

Изменяются только указанные данные.


{
  "action" : "member.set" 

 ,"email": "идентификатор подписчика" 

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификатора. не обязательно, система сама распознает email или msisdn

 ,"source" : "ip-адрес оригинального запроса" 
              -- если оригинальный инициатор запроса вы сами - ваш ip, иначе ip-адрес откуда вам пришёл запрос

 ,"if_exists" : "error|must" -- не обязательно, по умолчанию существующий подписчик обновляется, а отсутствующий - создаётся
                 -- error     - ошибка при наличии подписчика в базе
                 -- must      - ошибка при отсутствии адреса в базе

 ,"newbie.confirm": "подписчик должен подтвердить внесение в базу (1|0)" 
                     -- используется только при внесении email-адресов
                     -- действует лимит внесения без подтверждения
                     -- подробнее описанный в вызове member.import

 ,"newbie.letter.confirm" : "номер шаблона информационного письма" 
                           -- высылается, если новый адрес до этого отсутствовал в базе, и внесён с необходимостью подтверждения
                           -- если параметр пуст или отсутствует, то ничего выслано не будет
                           -- но необходимость подтвердить регистрацию никуда не денется и выслать письма вы сможете позже через member.sendconfirm
                           -- этот параметр НЕ зависит от newbie.confirm - если у вас кончился лимит внесения без подтверждения, то импорт
                           -- продолжит вносить уже с подтверждением и этот параметр будет использоваться
                           -- при внесении номеров телефонов никакие уведомления не высылаются
                           -- информационное письмо должно иметь заполненный адрес отправителя и не находиться на модерации

 ,"newbie.letter.no-confirm" : "номер шаблона информационного письма" 
                           -- высылается? если новый адрес до этого отсутствовал в базе, и внесён с без необходимости подтверждения
                           -- если параметр пуст или отсутствует, то ничего выслано не будет
                           -- при внесении номеров телефонов никакие уведомления не высылаются
                           -- информационное письмо должно иметь заполненный адрес отправителя и не находиться на модерации

-- записываемые/изменяемые данные

,"datakey" : [

  [ "ключ-данных-1", "режим-1", "новое-значение-1", "тип-1" ]
 ,[ "ключ-данных-2", "режим-2", "новое-значение-2", "тип-2" ]
 ,[ "ключ-данных-3", "режим-3", "ключ-данных-из-3", "тип-3" ]

 ........
]

-- "режим" имеет следующие значения

-- режимы работы со значениями указанными непосредственно в вызове

--     "set"       - полностью и безусловно заменить имеющиеся данные новыми
--                   если значение присваивается элементу массива, и элемент ещё не существовал и не первый, то в массиве появится необходимое количество
--                   элементов (со значением null), предшествующих присваемому
--
--     "update"    - заменить имеющиеся данные новыми, только указанный ключ уже есть
--                   ("есть" это именно есть - null, пустая строка, пустой массив, пустой объект, находящиеся по указанному ключу - это "ключ есть"),
--                   иначе изменение игнорируется. Поведение при присвоении не первому элементу массива аналогично set (при условии, что замена реально была
                     произведена)
--
--     "insert"    - добавить данные, только если указанного ключа ещё нет
--                   ("нет" это именно нет - null, пустая строка, пустой массив, пустой объект, находящиеся по указанному ключу - это "ключ есть"),
--                   иначе изменение игнорируется. Поведение при присвоении не первому элементу массива аналогично set (при условии, что данные реально были
                     добавлены)
--
--     "merge"     - "set" по каждому ключу нового значения в существующее, а именно:
--                   * если имеющиеся данные - это объект и новое значение тоже объект, то каждый ключ и его величина из нового значения добавляются при
                     отсутствии
--                   или его величина заменяют собой величину существующего такого же ключа в имеющихся данных, если ключ в них уже есть
--                   * если данных по указанному ключу нет и новое значение - объект, то новое значение создаст объект по указанному ключу
--                   * в других случаях вызов завершится ошибкой
--
--     "merge_update" - "update" по каждому ключу нового значения в существующее, а именно:
--                   * если имеющиеся данные - это объект и новое значение тоже объект, то величина каждого ключа из нового значения заменяют собой величину
--                   существующего такого же ключа в имеющихся данных. Ключи нового значения, отсутствующие в имеющихся данных игнорируются.
--                   * если данных по указанному ключу нет и новое значение - объект, то новое значение будет проигнорировано
--                   в других случаях вызов завершится ошибкой
--
--     "merge_insert" - "insert" по каждому ключу нового значения в существующее, а именно:
--                   * если имеющиеся данные - это объект и новое значение тоже объект, то каждый ключ и его величина из нового значения добавляются при
                     отсутствии такого ключа в имеющихся данных. Ключи нового значения, уже существующие в имеющихся данных, игнорируются.
--                   * если данных по указанному ключу нет и новое значение - объект, то новое значение создаст объект по указанному ключу
--                   * в других случаях вызов завершится ошибкой
--
--     "push"      - если имеющиеся данные это массив и новое значение тоже массив, то новый данные добавляются в конец имеющегося массива
--                   если данных по указанному ключу нет и новое значение - массив, то новое значение создаст массив по указанному ключу
--                   * в других случаях вызов завершится ошибкой
--
--     "unshift"   - если имеющиеся данные это массив и новое значение тоже массив, то новый данные добавляются в начало имеющегося массива
--                   если данных по указанному ключу нет и новое значение массив, то новое значение создаст массив по указанному ключу
--                   * в других случаях вызов завершится ошибкой
--
--     "delete"    - данные будут удалены, "значение" и "тип" игнорируются и должны или отсутствовать или быть пустыми
--                 - если удалялся элемент массива и он был последним, то размер массива уменьшится на 1
--                 - если удалялся элемент массива и он не был последним, то размер массива не уменьшится, а удаляемый элемент получит значение null

-- режимы, копирующие значения из другого ключа данных

-- "set.copy", "update.copy", "insert.copy", "merger_copy", "merge_update.copy", "merger_insert.copy", "push.copy", "unshift.copy" - работают аналогично
-- режимам без .copy, но значением является то, что хранится в указанном в третьем элементе "ключе-данных-из".
--
-- если копируемое значение не скаляр, а структура данных, то создаётся независимая копия структуры данных

-- "тип" не обязателен и предназначен для контроля соответствия "нового-значения" формату данных.
--  В данный момент поддерживается только тип данных "дата-время", так как неверно заданные данные этого типа могут вызвать ложные срабатывания/не срабатывания
--  в фильтре групп операции сравнения дат.
--
--  возможные значения "тип":
--
--    отсутствует - проверки не производятся
--
--    ""          - проверки не производятся
--
--    null        - проверки не производятся
--
--    "dt"        - сокращение для dt:Ys
--
--    "dt:LR"     - производится проверка и нормализация "значения" как даты для указанного диапазона точности от L до R,
--                - где L и R - символы Y M D h m s, задающие диапазон компонентов даты
--                - например, "dt:Ys" - дата от года до секунды, "dt:Mh" - дата от месяца до часа
--                - год должен всегда задаваться четырьмя цифрами
--                - остальные компоненты - одной или двумя и они будут автоматически нормализованы до двух цифр
--                - например, дата "1971-5-4 3:2:1" при типе "dt:Ys" станет "1971-05-04 03:02:01" 
--
--    другие значения - вызов завершится с ошибкой
--
--  при указании типа данных "дата-время" кроме непосредственно даты, можно указывать время относительно текущего момента
--  используется тот же синтаксис, что и в универсально статистике stat.uni
--
--  current
--  current - 1 month
--  current:YD + 25 days
--
--  в ключ данных будет записан результат вычисления времени с указанной точностью
--
--  если точность указана разная одновременно и в типе (dt:YD), и в выражении (сurrent:Yh), то используется точность из выражения current
--
--  если несколько ключей изменяются с использованием выражения current, то они все вычисляются от какого-то одного и того же времени, запомненного в начале вызова
--
--  при импортировании все вычисления current для всех строк импорта также используют какое-то одно и то же время, запомненное в начале импорта
--

-- внимательно изучите примеры в разделе "Общие замечания" 

-- для управления участием указанного идентификатора в группах-списках с помощью псевдо-ключа данных "-group" используйте точное указание группы "-group.КОДГРУППЫ" и:
--   * режим "delete" для удаления из группы
--   * режим "set" и новое значение "1" для добавления в группу
--   * в других случаях вызов завершится ошибкой

-- для удаления ошибок доставки используйте ключ данных "member.error.error" и режим "delete", в других случаях вызов завершится ошибкой

-- для перевода подписчика в состояние "Требуется подтверждение внесения в базу" используйте ключ данных "member.lockconfirm", режим "set" и значение точно "1", в других случаях вызов завершится ошибкой

-- все прочие изменения по ключам данных "member.*" и "-group.*" в данный момент игнорируются, но могут стать ошибками в будущем, лучше не делайте их

-- если изменяемый ключ данных не существует, то он будет создан с учётом влияния "режима" 

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

-- в контексте создания не существующих частей пути, "не существует" - это действительно не существует, или существует, но равен null.

-- попытки использовать объект или скалярную величину как массив приведут к завершению вызова с ошибкой

-- попытки использовать массив или скалярную величину как объект приведут к завершению вызова с ошибкой

-- данные или изменяются/удаляются для всех указанных ключей (если не было ошибок) или не изменяются/удаляются вообще (при любой ошибке)

-- одновременное с созданием подписчика / внесением данных подписчика
-- добавление к нему дополнительных идентификаторов
-- аналогично последовательному вызову member.set и потом нужное число раз member.head.attach
-- но при данном использовании атомарно - или все действия по созданию/внесению и присоединению
-- завершаться успешно или же любая ошибка отменяет все изменения
--
-- режимы head_rule
--
--     error - отмена запроса с сообщением, что данный идентификатор непригоден
--     none - голова остается где была, запрос возвращает предупреждение
--     transplant - перенос головы от источника к приёмнику
--     decapitate - удалить голову head у источника
--     replace - перенести голову head от источника к приёмнику, а потом удалить голову email у приёмника
--     replace_same_type - перенести голову head от источника к приёмнику, заменив ею голову приёмника того же типа, что и head (если она есть и единственная по типу). Если у приёмника нет головы того же типа, что и head, выполняется режим transplant.
--
-- режим по умолчанию
--
--     для multi и single: error
--     для newbie: attach
--
-- обработка данных при слиянии срабатывает только для head_rule (multi или single в зависимости от присоединяемого идентификатора) = replace|replace_same_type|decapitate

 "head_attach" : [ -- не обязательно

                   { -- параметры аналогичны одноимённым из вызова member.head.attach
                    "head" : "дополнительный присоединяемый идентификатор" 
                   ,"head_addr_type" : "тип дополнительного присоединяемого идентификатора
                   ,"newbie.confirm" : "режим подтверждения" 

                   ,"head_rule": { -- параметры, определяющие обработку голов при слиянии
                       "multi": error|none|transplant|replace|replace_same_type|decapitate -- для многоголовых пользователей
                       "single": error|none|transplant|replace|replace_same_type|decapitate -- для одноголовых
                       "newbie": attach|none|replace|replace_same_type - для новых, несуществующих идентификаторов (без голов)
                     },
                   ,"data_rule": "обработка данных при слиянии" -- как в member.merge
                   }
                  .......
                 ]

-- необязательный

 ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

-- при "1" данные возвращаются как при запросе member.get c "datakey" : "*" 

}

ответ


{

 <общие поля>

,"newbie"  : 0|1 -- новый подписчик - 0 - уже был в базе, 1 - внесён в базу

,"member" : {
             "id"        : номер в базе,
             "email"     : "нормализованный идентификатор",
             "addr_type" : "тип идентификатора" 
            }

-- если "return_fresh_obj" : "1" 

-- данные от запроса "member.get" соответствующего адреса

}

вверх

Получить данные подписчика (ДК)

Обратите внимание, что в зависимости от того, что вы выбираете и имеются ли в ключе шаблоны, "данные для ключа" могут быть и скаляром, и массивом, и объектом, и null.

Обратите внимание, что значением для того, что было создано в модели АВО и называется там "вопрос с выбором" будет объект, а не массив. Ключами массива будут кода ответов, значениями - null.

Обратите внимание, что количество ошибок доставки имеет ключ member.error.error (совместимо со stat.uni), а не member.error как в АВО.

Системный ключ "member" и псевдо-ключ "-group", описывающий участие и дату внесения адреса в группах списка, описаны выше во вводной части раздела.


{

  "action" : "member.get" 

 ,"email": "идентификатор подписчика" 

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификатора. не обязательно, система сама распознает email или msisdn

 ,"with_stoplist" : 0|1|2 - В ключ member.stoplist добавляется информация о нахождении адреса в стоп-листах. Структура как у вызова email.get в member.stoplist

 ,"with_heads" : 0|1    - В ключ member.heads добавляется информация о всех головах адреса. Структура как у вызова member.head.list

 ,"missing_too" : 0|1   - Если 0, то ошибка при отсутствии такого подписчика. Если 1 и такой подписчик когда-то был, то возвращается оставшаяся информация (аналогично email.get) и, дополнительно, ключ "missing" : "1" для индикации, что такого подписчика всё же нет

-- один из способов указания интересующих ключей данных

-- или список ключей

 ,"datakey" : [
               "ключ данных-1" 
              ,"ключ данных-2" 
               .....
              ]

-- или один ключ

 ,"datakey" : "ключ данных" 

-- или специальный вариант одного ключа для получения всех данных

 ,"datakey" : "*" 

}

ответ

{

    <общие поля>

-- при запросе со списком ключей

 ,"datakey" : {
               "ключ данных-1" : данные для ключа-1
              ,"ключ данных-2" : данные для ключа-2
              ......
              }

-- при запросе с одним ключом или с "*" 

 ,"datakey" : данные для ключа

-- при запросе "*" 

 ,"datakey" : {
               данные для имеющихся ключей
              }

-- если missing_too = 1

 ,"missing" : 0|1 - нет или есть всё адрес как подписчик

}

Получить набор данных (ДК)

Это специализированный вариант вызова member.get для чтения всех данных подписчиков, связанных с конкретным набором данных


{

  "action" : "member.get" 

 ,"email": "номер набора данных" 

 ,"addr_type" : "dataset" 

-- один из способов указания интересующих ключей данных, по умолчанию - '*'

-- ключи member и -group недоступны, так как у каждой головы они свои и уже и так имеются в ответе в heads

-- или список ключей

 ,"datakey" : [
               "ключ данных-1" 
              ,"ключ данных-2" 
               .....
              ]

-- или один ключ

 ,"datakey" : "ключ данных" 

-- или специальный вариант одного ключа для получения всех данных

 ,"datakey" : "*" 

}

ответ

{
    <общие поля>

 "dataset" => "номер набора данных" -- member.dataset

,"data" => {
            -- общие данные. все или только указанные в datakey
           }

,"heads" => [ -- список номеров всех идентификаторов (member.id) связанных с этим набором данных
             123
            ,4567
            ....
           ],

,"head" => { -- информация о каждом идентификаторе
             -- данные аналогичны таким же полям системной анкеты member и описаны выше

            "номер идентификатора-1" 
                   => {
                       "id" => номер идентификатора

                      ,"dataset" => номер набора данных

                      ,"addr_type" => "тип идентификатора",

                      ,"email" => "идентификатор",

                      ,"domain" => "домен идентификатора для email" 

                      ,"haslock" => "все блокировки идентификатора" 

                      ,"lockremove" => "cостояние удаления" 

                      ,"create" => { -- информация о создании
                                     'time' => "дата (Ys)" 
                                     'host' => "источник" 
                                   }

                      ,"update" => { -- информация об изменении по АПИ
                                     'time' => "дата (Ys)" 
                                     'host' => "источник" 
                                   }

                      ,"import" => { -- информация об изменении при импорте
                                    'time' => "дата (Ys)" 
                                   }

                      ,"-group" => {
                                     -- участие в группах-списках
                                   }

                      ,"confirm' => {
                                      -- информация о подтверждении регистрации данного идентификатора
                                    },

                       ,"error" => {
                                    -- информация об ошибках доставки для данного идентификатора
                                  }

                       ,"stoplist" => [
                                        -- нахождение данного идентификатора в стоп-листах
                                        -- cтруктура как у вызова email.get
                                      ]
                      }

            ,"номер идентификатора-2' => {

                           ......................

                         }
          }

}

Cоздать подписчика / Установить ответы подписчика (АВО)

При отсутствии адреса в базе он создаётся автоматически.

Изменяются только указанные данные.

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

Установка member.error точно в "0" удаляет запись о проблемах доставки и снимает блокировку адреса из-за ошибок доставки, если таковая была.

Установка member.lockconfirm точно в "1" переводит подписчика в состояние "Требуется подтверждение внесения в базу".


{

  "action" : "member.set" 

 ,"email": "идентификатор подписчика" 

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификатора. не обязательно, система сама распознает email или msisdn

 ,"source" : "ip-адрес оригинального запроса" 
              -- если оригинальный инициатор запроса вы сами - ваш ip, иначе ip-адрес откуда вам пришёл запрос

 ,"if_exists" : "error|must|update|overwrite" -- не обязательно, по умолчанию существующий подписчик обновляется в режиме overwrite, а отсутствующий - создаётся.
                 -- правила учёта есть или нет подписчик
                 -- error     - ошибка при наличии подписчика в базе
                 -- must      - ошибка при отсутствии адреса в базе
                 --
                 -- правила изменения ответов анкетных данных. не действует на псевдоанкету "-group" 
                 -- update    - если ответ на изменяемый вопрос уже есть, то он остаётся неизменным
                 -- overwrite - ответ заменяется/создаётся в любом случае

  ,"newbie.confirm": "подписчик должен подтвердить внесение в базу (1|0)" 
                     -- используется только при внесении email-адресов
                     -- действует лимит внесения без подтверждения
                     -- подробнее описанный в вызове member.import

  ,"newbie.letter.confirm" : "номер шаблона информационного письма" 
                           -- высылается, если новый адрес до этого отсутствовал в базе и внесён с необходимостью подтверждения
                           -- если параметр пуст или отсутствует, то ничего выслано не будет,
                           -- но необходимость подтвердить регистрацию никуда не денется и выслать письма вы сможете позже через member.sendconfirm
                           -- этот параметр НЕ зависит от newbie.confirm - если у вас кончился лимит внесения без подтверждения, то импорт
                           -- продолжит вносить уже с подтверждением, и этот параметр будет использоваться
                           -- при внесении номеров телефонов никакие уведомления не высылаются
                           -- информационное письмо должно иметь заполненный адрес отправителя и не находиться на модерации

  ,"newbie.letter.no-confirm" : "номер шаблона информационного письма" 
                           -- высылается, если новый адрес до этого отсутствовал в базе и внесён с без необходимости подтверждения
                           -- если параметр пуст или отсутствует, то ничего выслано не будет
                           -- при внесении номеров телефонов никакие уведомления не высылаются
                           -- информационное письмо должно иметь заполненный адрес отправителя и не находиться на модерации

  ,"obj" : {

            -- управление анкетными данными подписчика

            -- ank   - код анкеты

            -- quest - код ответа

            -- val   - значение ответа или код ответа, если вопрос с выбором из списка

            -- значение не указанных вопросов не меняется

            "ank1" : {

                       -- установка значения ответа обычного вопроса

                       "quest" :"val" 

                       -- установка значения ответа для вопроса с типом "дата" в зависимости от заданной в анкете точности

                       "quest" : "YYYY-MM-DD" 
                       "quest" : "YYYY-MM-DD hh" 
                       "quest" : "YYYY-MM-DD hh:mm" 
                       "quest" : "YYYY-MM-DD hh:mm:ss" 

                       -- установка значения ответа у вопроса с множественным выбором

                      ,"quest" : [ "val", "val", "val"....]

                       -- установка значения ответа у вопроса с выбором одного из нескольких - всё равно массив

                      ,"quest" : [ "val" ]

                       -- удаление ответа

                      ,"quest" : null

                      }

          ,"ank2" : {

                      ..............

                     }

          -- управление участием указанного идентификатора в группах-списках с помощью псевдо-анкеты

          -- параметр "if_exists" не влияет на эту анкету

          ,"-group" : {

                  -- 0 - удалить из группы-списка

                  -- 1 - добавить в группу-список

                  -- участие в не перечисленных группах не меняется

                  "id1" : "(0|1)" 

                  ,"id2" : "(0|1)" 

                   .............

                  ,"idN" : "(0|1)" 

                }

          }

-- необязательный

 ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ


{

 <общие поля>

,"newbie"  : 0|1 -- новый подписчик - 0 - уже был в базе, 1 - внесён в базу

-- если "return_fresh_obj" : "1" 

 данные от запроса "member.get" соответствующего адреса

}

вверх

Получить ответы подписчика (АВО)


{

  "action" : "member.get" 

 ,"email": "идентификатор подписчика" 

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификатора. не обязательно, система сама распознает email или msisdn

 ,"with_stoplist" : 0|1 - В анкету member добавляется stoplist с информацией о нахождении адреса в стоп-листах. Структура как у вызова email.get в member.stoplist

 ,"with_heads" : 0|1    - В анкету member добавляется heads с информацией о всех головах адреса. Структура как в member.head.list

 ,"missing_too" : 0|1   - Если 0, то ошибка при отсутствии такого подписчика. Если 1 и такой подписчик когда-то был, то возвращается оставшаяся информация аналогично email.get  c учётом наличия datakey (есть error - структура, нет - набор полей) и, дополнительно, ключ "missing" : "1" для индикации, что такого подписчика всё же нет

}

ответ


{

    <общие поля>

-- ответы на вопросы анкет

  ,"obj" : {

            -- ank   - код анкеты

            -- quest - код ответа

            -- val   - значение ответа или код ответа, если вопрос с выбором из списка

            "ank1" : {

                       -- обычный вопрос

                       "quest" : "val" 

                       -- вопрос с множественным выбором

                      ,"quest" : [ "val", "val", "val"....]

                       -- вопрос с выбором одного из нескольких - всё равно массив

                      ,"quest" : [ "val" ]

                      }

          ,"ank2" : {

                      ..............

                     }

          -- псевдо-анкета, описывающая участие в группах-списках

          ,"-group": {
                      -- участие в группах-списках
                     }

          }

-- если missing_too = 1

 ,"missing" : 0|1 - нет или есть всё адрес как подписчик

}

Удалить подписчика

Адреса и данные реально удаляются из базы !

Запрос с указанием списка по умолчанию синхронный.

Запрос с указанием группы по умолчанию асинхронный. Используйте sync = 1, если вам реально нужен ответ с описанием того, как и какие адреса были обработаны.

Асинхронные запросы возвращают номер трекера для отслеживания.

В зависимости от параметра wipe вызов работает по-разному в случае наличия у подписчика нескольких идентификаторов.

При wipe = 0 (по умолчанию)

При wipe = 1

При любом wipe

В отличии от member.head.detach возможно удаление единственного (последнего) идентификатора.

В отличии от member.head.detach генерируется триггерное событие "Подписчик удалён".

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

Одновременно может выполняться только один такой запрос.

При асинхронном запуске обработку можно прекратить вызовом track.set


{

  "action" : "member.delete" 

 ,"wipe" : 0|1 -- способ удаления, по умолчанию 0

-- указание подписчиков одним из способов

 ,"email": "идентификатор подписчика" 

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификатора. не обязательно, система сама распознает email или msisdn

или

 ,"list" : [

            "идентификатор подписчика" 

           ,"идентификатор подписчика" 
            ........

           ]

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов в списке. не обязательно, система сама распознает email или msisdn

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 1 - синхронный

или

 ,"group" : код группы участники которой будут удалены

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

или

 ,"group.filter" : [
                    фильтр отбора как у group.filter.set
                   ]

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

или

 ,"url" : ссылка на файл со списком адресов, по одному на строке, возможно сжатие zip

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов в списке. не обязательно, система сама распознает email или msisdn

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

или

 ,"stat.uni" : { -- адреса для обработки получаются из запроса универсальной статистики

                 -- подразумевается unique = 1

               ,"filter" : [ условие выборки как у запроса в вызове stat.uni ]

               ,"have"   : [ не обязательно. условие отбора поле группировки в фильтре ]

               ,"cache"  : [ настройки кэширования как у запроса в вызове stat.uni ]

               ,"select" : [ ... ] -- используйте только если не подходит значение по умолчанию [ "member.email" ]

               }

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов. не обязательно, система сама распознает email или msisdn

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

 ,"track.info" : "дополнительная информация, которая будет потом доступна в track.list/get" -- строка 1024 байта. не обязательно
}

ответ


{

 <общие поля>

-- для асинхронного запроса

,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

-- для синхронного запроса

,"list" : {
           "адрес-1" : 0|1 -- результат удаления - 1 - удалён, 0 - нет (например, адрес ошибочен или отсутствует)

          ,"адрес-2" : 0|1

           .................
          }

}

вверх

Объединение данных двух подписчиков


{
  "action": "member.merge",

 ,"email": "идентификатор подписчика"  -- подписчик, получающий данные

 ,"addr_type": email|msisdn|viber|csid|push|vk|tg|vknotify|id      -- тип идентификатора. не обязательно

 ,"head": "идентификатор подписчика"   -- подписчик-источник данных

 ,"head_addr_type": email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификатора-источника. не обязательно

  -- параметры, определяющие обработку голов при слиянии данных

 ,"head_rule": none|transplant|decapitate -- действие с головами
                                          --
                                          -- Если у подписчика-источника единственная голова, то при выполнении transplant, decapitate или replace
                                          -- перестанет существовать подписчик - будет удалено его тело т.к. у тела должна быть хотя бы одна голова
                                          --
                                          -- none       - ничего не делать
                                          -- transplant - перенести голову head от источника к приёмнику
                                          -- decapitate - удалить   голову head у источника
                                          -- replace    - перенести голову head от источника к приёмнику, а потом удалить голову у приёмника, указанную в email

  -- параметры, определяющие обработку данных при слиянии. не обязательно.
  -- по умолчанию
  --               level   = 2
  --               hasmiss = set   - скопировать из источника ключ, отсутствующий в приёмнике
  --               hashas  = none  - не трогать ключ, имеющийся у обоих
  --               misshas = none  - не копировать ключ, имеющийся только у источника

 ,"data_rule": {

      "level" : 2 -- уровень иерархии (>=0), для которого выполняется слияние данных
                  -- по умолчанию - 2  (это коды вопроса в модели АВО или второй уровень элемента ключа данных)

      -- режимы для разных ситуаций слияния ключей данных, для которых не описаны индивидуальные режимы в dk_rule

      -- для случая, когда ключ есть в источнике и отсутствует в приёмнике

      ,"hasmiss": set|none|move -- по умолчанию set
                                -- set  -- скопировать данные из источника в приёмник
                                -- none -- ничего не делать
                                -- move -- скопировать данные из источника в приёмник и потом удалить из источника

      -- для случая когда ключ есть и в источнике и в приёмнике

      ,"hashas": none|delete|set|move|merge -- по умолчанию none
                                            -- none  -- ничего не делать
                                            -- delete-- удалить данные из приёмника
                                            -- set   -- скопировать данные из источника в приёмник
                                            -- move  -- скопировать данные из источника в приёмник и потом удалить из источника
                                            -- merge -- объединить данные источника с данными приёмника

      -- для случая, когда ключ отсутствует в источнике и есть в приёмнике

      ,"misshas" : none|delete -- по умолчанию none
                               -- none  -- ничего не делать
                               -- delete-- удалить данные из приёмника

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

      "dk_rule": {

        "ключ-данных-1" : "режим-1",
        ...
        "ключ-данных-N" : "режим-N" 
      }

    }

  ,"return_fresh_obj" : ....
}

вверх

Список идентификаторов

Выводится список всех идентификаторов и сопутствующая им информация для указанного пользователя


{

 "action" : "member.head.list" 

 ,"email" : "один из идентификаторов cуществующего пользователя" 

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификатора. не обязательно, система сама распознает email или msisdn

}

ответ


{

 <общие поля>

,"list" : [

            {

             "email" : "идентификатор пользователя" -- email, номер телефона,....

            ,"addr_type" : "email|msisdn|viber|csid|push|vk|tg|vknotify" -- тип идентификатора

            -- структуры состояния головы как описано в member.get

            ,"has_lock" : ...

            ,"lockerror" : ...

            ,"confirm" : { ... }

            ,"error" : { ... }

            }

            ...

           ]

}

вверх

Присоединение идентификатора

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

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

Возможно присоединение идентификаторов во время импорта пользователей.


{

 "action" : "member.head.attach" 

 ,"email": "идентификатор подписчика" 

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификатора. не обязательно, система сама распознает email или msisdn

 ,"head" : "присоединяемый идентификатор" 

 ,"head_addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify -- тип присоединяемого идентификатора. не обязательно, система сама распознает email или msisdn

 ,"newbie.confirm": "подписчик head должен подтвердить внесение в базу (1|0)" 
                     -- используется только при внесении email-адресов
                     -- действует лимит внесения без подтверждения
                     -- подробнее описанный в вызове member.import
}

ответ


{

 <общие поля>

}

вверх

Удаление идентификатора

При split= 0

*Идентификатор, указанный в email, удаляется от пользователя и становится более ни с кем не связан.

*Также прекращается участие указанного идентификатора в группах-списках, в которых он состоял.

При split = 1

*Идентификатор, указанный в email, удаляется от пользователя и становится cамостоятельным пользователем.

*За ним остаётся участие в группах-списках, в которых он состоял.

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

Статистика по действиям (участие в выпусках, доставки, переходы, чтения и их длительность и прочее) остаётся за удалённым идентификатором в части его действий
и за пользователем, от которого удаляется идентификатор в оставшейся части.

После удаления у пользователя, от которого удаляется идентификатор, должен остаться ещё хотя бы один другой идентификатор (для удаления всего пользователя есть member.delete).

В отличии от member.delete невозможно удаление единственного (последнего) идентификатора.

В отличии от member.delete не генерируется триггерное событие "Подписчик удалён".

Возможно удаление идентификаторов во время импорта пользователей.

{

 "action" : "member.head.detach" 

 ,"email"  : "удаляемый один из идентификаторов существующего пользователя" 

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификатора. не обязательно, система сама распознает email или msisdn

 ,"split" : 0|1 -- создать из удаляемого самостоятельного пользователя. не обязательно. по умолчанию 0.
}

ответ

{

 <общие поля>

}

вверх

Замена идентификатора

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

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

Этот вызов - аналог последовательного исполнения member.head.attach и member.head.detach, но является атомарным - любая ошибка полностью отменяет все изменения.


{

 "action" : "member.head.replace" 

 ,"email": "текущий идентификатор подписчика" 

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип текущего идентификатора. не обязательно, система сама распознает email или msisdn

 ,"head" : "новый идентификатор" 

 ,"head_addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify -- тип нового идентификатора. не обязательно, система сама распознает email или msisdn

 ,"newbie.confirm": "подписчик head должен подтвердить внесение в базу (1|0)" 
                     -- используется только при внесении email-адресов
                     -- действует лимит внесения без подтверждения
                     -- подробнее описанный в вызове member.import
}

ответ


{

 <общие поля>

}

вверх

Участие подписчика в группах-фильтрах

Списков групп-фильтров, в которых состоит подписчик. Участие в группах-списках и так известно через member.get.

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

При асинхронном запуске обработку можно прекратить вызовом track.set

{
 "action" : "member.where" 

 ,"email": "идентификатор подписчика" 

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификатора. не обязательно, система сама распознает email или msisdn

-- не обязательно, ограничение проверки

 ,"group" : "код группы" -- одной

-- или

 ,"group" : [ "код группы", ... ] -- несколькими группами

 ,"track.info" : "дополнительная информация, которая будет потом доступна в track.list/get" -- строка 1024 байта. не обязательно
}

ответ

{

 <общие поля>

,"list" : {
           "адрес" : [ список кодов групп-фильтров в которые адрес входит ]

          -- или

          ,"адрес" : null -- адрес не существует или синтаксически не верен

          }

}

Список подписчиков

НЕ СОВМЕСТИМОЕ ИЗМЕНЕНИЕ С 17 ИЮЛЯ 2017 ГОДА - при result=response размер выдачи не более 1000 строк.

Если формат фильтра у группы group или заданный непосредственно в group.filter использует формат ДК с оператором has c параметром save, то в результатах вызова будут также результаты всех срабатываний save.

При асинхронном запуске обработку можно прекратить вызовом track.set

{

  "action" : "member.list" 

-- источник адресов, одно из трёх или вообще ничего (тогда по всем адресам вообще)

 ,"group" : "идентификатор группы" 

-- или

 ,"group.filter" : [ фильтр отбора как у group.filter.set ]

-- для всех источников

 ,"addr_type": "тип выбираемых адресов подписчиков (email|msisdn|viber|csid|push|vk|tg|vknotify|vknotify)" 
                -- при отсутствии определяется по типу адресов в указанной группе
                -- если указан, то должен совпадать с типом адресов в группе, если и та указана
                -- если не указан и не указана группа, то выбираются адреса любого типа

 ,"member.haslock" : "код" -- учёт состояния блокировки подписчика
                   -- пусто или отсутствует - не учитывать состояние блокировки. может ли получать сообщения
                   -- -1 - есть хоть какая-то блокировка       - не может
                   --  0 - нет блокировки                      - может
                   --  1 - заблокирован так как отписался      - не может
                   --  2 - заблокирован так как не подтверждён - не может
                   --  4 - заблокирован так как имеет фатальные ошибки доставки - не может
                   --  прочие значения ( 3,5,6,7 ) - одновременное наличие нескольких указанных блокировок
                   --
                   -- формально этот параметр можно заменить дополнительными условиями в фильтре группы,
                   -- но его использование позволяет
                   --  * не меняя фильтры групп, узнавать сколько в ней всего участников и сколько из них могут получать письма
                   --  * не создавая группы, узнавать это же для всех свои адресов вообще
                   --  * выполнять запрос быстрее по сравнению с таким же условием в фильтре группы

-- формат вывода, одно из трёх или вообще ничего
-- если формат вывода не используется, то выводится только идентификатор подписчика

 ,"format" : "идентификатор формата вывода" -- использование существующего формата

-- или

 ,"format" : [ -- указание формата в формате Анкета-Вопрос-Ответ прямо в запросе
               -- специальная пара aid=member с qid=head.list позволяют получить в ответе список всех голов как в результате member.head.list

               { "anketa" : "код анкеты", "quest" : "код вопроса" }
              ........
             ]

-- или

 ,"format" : [ -- указание объекта Формат прямо в запросе

              -- запрос просто по ключу данных
              -- специальный ключ данных "member.head.list" позволяет получить в ответе список всех голов как в результате member.head.list

               { "datakey" : "ключ данных" }

              -- запрос по ключу из результатов работы save в условии has используемого в вызове фильтра
              -- результат null, если save не срабатывал или массив из результатов получения данных по
              -- ключу данных относительно каждого значения из результата save с указанным кодом

              ,{ "save" : "код сохранения", datakey" : "относительный ключ данных" }

              -- запрос значение меток из условий фильтрации

              ,{ "label" : "имя метки" }

              ........
             ]

 ,"sort" : "коданкеты.кодвопроса" 
             -- сортировка по указанному полю (не важно будет оно в итоговых данных или нет)
             -- если параметр отсутствует или пуст, то выдача идёт в неком внутреннем порядке
             -- сортировка по произвольной анкете/полю может резко увеличить время выполнения запроса,
             -- так как для его выполнения надо отсортировать всю выборку,
             -- но сортировка по ниже перечисленным полям практически не влияет на скорость выполнения
             --   member.id
             --   member.email       - при идентификатору подписчика
             --   member.domain      - сортировка по домену и внутри него по адресу
             --                      - при выборке телефонов эквивалентно просто member.email
             --   member.create.time - дата и время создания
             --   member.update.time - дата и время последнего изменения данных по API
             --   member.import.time - дата и время последнего изменения данных подписчика при импорте

  ,"sort.order" : "asc|desc" -- направление сортировки asc - по возрастанию (по умолчанию), desc - по убыванию

  ,"result" : [ способ возврата результата. Смотрите общее описание ]

  -- заголовок списка
  -- если параметр отсутствует или пуст, то такая строка не добавляется

  ,"caption" : "id" -- в первой строке выводить заголовок, содержащий для каждой колонки код анкеты и вопроса

  -- или

  ,"caption" : "name" -- в первой строке выводить заголовок, содержащий для каждой колонки названия анкеты и вопроса

  -- или

  ,"caption" : [ "имя 1", "имя 2" ... ] -- в первой строке выводить заголовок, содержащий собственные указанные названия

  ,"answers" : "decode" -- для АВО вместо кодов ответов на вопросы в выбором возвращать значения.
                        -- неизвестные кода возвращаются как есть

  ,"answers" : "unroll" -- для ДК для ключей совпадающих с вопросам анкет с типом 1m или nm вместо объекта соответствуюшего
                        -- структуре хранения таких значений возвращается скаляр с кодом ответа для 1m, и массив с кодами ответов для nm

  ,"answers" : "decode" -- для ДК подразумевает unroll и, далее, значениея ответов как для ABO

  ,"track.info" : "дополнительная информация, которая будет потом доступна в track.list/get" -- строка 1024 байта. не обязательно

---- дополнительные параметры запроса зависящие от result

-- *НЕ СОВМЕСТИМОЕ ИЗМЕНЕНИЕ С 17 ИЮЛЯ 2017 ГОДА*

-- ДО 17 ИЮЛЯ 2017 ГОДА

-- для response

  ,"page" : "номер страницы" -- при отсутствии выдаётся весь список. Должны быть указаны оба параметра или ни одного

  ,"pagesize": "размер страницы" 

-- ПОСЛЕ 17 ИЮЛЯ 2017 ГОДА

-- для response

  ,"page" : "номер страницы" -- при отсутствии выдаётся 1000 строк
                             -- должны быть указаны оба параметра или ни одного
                             -- если размер страницы больше 1000, то ошибка

  ,"pagesize": "размер страницы" 

}

ответ

{

    <общие поля>

-- для result != response

  ,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

-- для result = response

  ,"order" : [ -- описание порядка колонок выводимых результатов

              {

                anketa      : код анкеты

               ,anketa.name : название анкеты

               ,quest       : код вопроса

               ,quest.name  : название вопроса

              }

             ....

           ]

   ,"list" : [ -- по одной записи для каждого адреса

              [ значения запрошенных полей в порядке, указанном в параметре ответа order ]

             ,[ значения запрошенных полей в порядке, указанном в параметре ответа order ]

             ,[ значения запрошенных полей в порядке, указанном в параметре ответа order ]

             .....

           ]

  ,"save" : { -- только при result=response

              "aдрес" : {
                           результат (если таковой был) работы save в условии has фильтра
                           В описании фильтра формата КД описано когда и чем это полезно
                        }
            }

  ,"label" : { -- только при result=response

              "aдрес" : {
                           список сработавших меток и их значений
                           В описании фильтра формата КД описано когда и чем это полезно
                        }
            }

}

вверх

Количество подписчиков

Расчёт быстр, если ведётся по "всем", по группам-спискам, по группам-фильтрам, состоящим из групп-списков.

Расчёт по группе-фильтру может быть долог - в зависимости от сложности фильтра и общего числа подписчиков.

По умолчанию вызов синхронный. Используйте sync = 0, чтобы сделать его асинхронным.

Для групп-сообществ ВК ответ немного другой для получения дополнительной информации про сообщество.

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

При асинхронном запуске обработку можно прекратить вызовом track.set


{

  "action" : "member.list.count" 

-- источник адресов, одно из двух или вообще ничего (тогда по всем адресам вообще)

 ,"group" : "идентификатор группы" 

-- или

 ,"group.filter" : [ фильтр отбора как у group.filter.set ]

-- для всех источников

 ,"addr_type": "тип выбираемых адресов подписчиков (email|msisdn|viber|csid|push|vk|tg|vknotify|vknotify)" 
                -- при отсутствии определяется по типу адресов в указанной группе
                -- если указан, то должен совпадать с типом адресов в группе, если и та указана
                -- если не указан и не указана группа, то выбираются адреса любого типа

 ,"member.haslock" : "код" -- учёт состояния блокировки подписчика
                   -- пусто или отсутствует - не учитывать состояние блокировки. может ли получать сообщения
                   -- -1 - есть хоть какая-то блокировка       - не может
                   --  0 - нет блокировки                      - может
                   --  1 - заблокирован так как отписался      - не может
                   --  2 - заблокирован так как не подтверждён - не может
                   --  4 - заблокирован так как имеет фатальные ошибки доставки - не может
                   --  прочие значения ( 3,5,6,7 ) - одновременное наличие нескольких указанных блокировок
                   --
                   -- формально этот параметр можно заменить дополнительными условиями в фильтре группы,
                   -- но его использование позволяет
                   --  * не меняя фильтры групп, узнавать сколько в ней всего участников и сколько из них могут получать письма
                   --  * не создавая группы, узнавать это же для всех свои адресов вообще
                   --  * выполнять запрос быстрее по сравнению с таким же условием в фильтре группы

 ,"cache" : { параметры кэширования }

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 1 - синхронный

 ,"track.info" : "дополнительная информация, которая будет потом доступна в track.list/get" -- строка 1024 байта. не обязательно

 ,"with_minmax" : 0|1 - дополнительно вернуть информацию о минимальных и максимальных номерах подписчиков
}

ответ


{

  <общие поля>

-- для асинхронного запроса

,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*
                    -- также будет содержать копию ответа obj

-- для синхронного запроса не по сообществу ВК

 ,"obj" : {
           -- общая статитстика

            "total" : всего подписчиков

           ,"active" : количество подписчиков, которые могут участвовать в рассылках писем или смс

           ,"locked" : уникальное количество заблокированных - не могут быть в рассылке

           -- подробности по причинам блокировок (у одного подписчика может быть больше одной блокировки)

           ,"locked.unsubscribed" : в том числе заблокированные, так как отписались

           ,"locked.confirm" : в том числе заблокированные, так как не подтвердили внесение в базу

           ,"locked.stoplist" : в том числе заблокированные, так как находятся в стоп-листе

           ,"locked.hardbounced" : в том числе заблокированные, так как имеют фатальные ошибки доставки

           -- и разбивка по типам адресов

           ,"total.email" : всего именно email

           ,"total.msisdn" : всего именно телефонов

           ,"total.viber" : всего именно Viber-номеров

           ,"total.csid" : всего именно клиентских идентификаторов

           ,"total.push" : всего именно push-подписок

           ,"total.vk" : всего именно vk-подписок

           ,"total.tg" : всего именно tg-подписок

           ,"total.vknotify" : всего именно vknotify-подписок

           ,"active.email" : способных получать именно адреса

           ,"active.msisdn" : способных получать именно телефоны

           ,"active.viber" : способных получать именно viber-номера

           ,"active.push" : способных получать именно push-подписки

           ,"active.tg" : способных получать именно tg-подписки

           ,"active.vknotify" : способных получать именно vknotify-подписки

           -- минимальный и максимальный номер подписчика
           -- при with_minmax :1
           -- позволяет организовать перебор подписчиков не малоэффективным способом постраничным first/skip,
           -- а эффективным - по диапазонам member.id

           ,"id_min" : число | null

           ,"id_max" : число | null

           ,"id_min.email" : число | null
           ,"id_min.msisdn" : число | null
           ,"id_min.csid" : число | null
           ,"id_min.push" : число | null
           ,"id_min.umid" : число | null
           ,"id_min.vk" : число | null
           ,"id_min.viber" : число | null
           ,"id_min.pushapp" : число | null
           ,"id_min.tg" : число | null
           ,"id_min.vknotify" : число | null

           ,"id_max.email" : число | null
           ,"id_max.msisdn" : число | null
           ,"id_max.csid" : число | null
           ,"id_max.push" : число | null
           ,"id_max.umid" : число | null
           ,"id_max.vk" : число | null
           ,"id_max.viber" : число | null
           ,"id_max.pushapp" : число | null
           ,"id_max.tg" : число | null
           ,"id_max.vknotify" : число | null

           -- при подсчёте по всем

           ,"active.vk" : способных получать именно vk-подписки

           ,"confirmed.vk" : подтвердивших vk-подписки

           }

-- для синхронного запроса по сообществу ВК

 ,"obj" : {
           "member" : участников сообщества

          ,"total" : имеют в ВК разрешение на получение сообщений

          ,"active" : доступны для рассылки vk_DDDD

          ,"confirmed" : подтвердили желание получать сообщения

          ,"locked.hardbounced" : с ошибками доставки

          ,"locked.unsubscribed" : отписавшиеся от сообщений

          ,"left" : покинувшие сообщество
          }

}

вверх

Внесение списка подписчиков

Вызов предназначен для массового внесения/изменения данных подписчиков на основе данных, указанных в самом вызове или получаемых по указанной ссылке из внешних источников.

Внешние источники могут быть как традиционные (http,https,ftp,ftps,sftp), так и специальные Google Big Query, Siebel, amoCRM, Bitrix24.

Вызов member.import.probe предназначается для проверки того, что думает система импорта о ваших входных данных.

В ответ будет сообщено, как произошло авто-определение кодировки (сharset), разделителя столбцов, определилась ли первая строка данных как строка конфигурации (firstline), и каким ответом каких анкет приписаны столбцы данных.

Если вы знаете заранее ответы на эти вопросы, то можете сразу указать эти данные при вызове: тогда они будут иметь приоритет над авто-определением системы.

Вызов member.import реально импортирует данные. Описанные выше параметры будет определены автоматически, если их не указать сразу при вызове.

Вызов member.import асинхронный - получение положительного ответа означает, что задание на импорт поставлено в очередь. Это не означает успешность импорта - он может не состоятся из ошибок, возникших позже. Следить за его исходом можно по возвращаемому номеру асинхронного запроса.

Обратите внимание: Все возможные проблемы (фатальные или нет - смотрите по cannot_import), связанные именно с импортом (настройки, данные) при вызове member.import.probe сообщаются только через параметр warnings. А параметр errors служит для сообщения о проблемах вызова только с точки зрения API. При вызове же самого импорта, не фатальные ошибки остаются по-прежнему в warnings, а фатальные переносятся в errors. Это может выглядеть не логично, но это так из-за назначения вызова member.import.probe - предупредить вас о возможных ошибках, а не выполнять само импортирование.

Вызов позволяет импортировать данные модели КД при использовании источника данных в формате JSON-объект.

Вызов member.import сохраняет отчёты о строчках, которые он не смог обработать. Имена файлов с отчётами можно получить из трекера.

Имеется два вида импорта:

Обычный

Данные вносятся в базу, создаются отсутствующие подписчики, внесённые данные доступны до момента удаления подписчика и могут использовать в условиях группах-фильтрах отбора сегментов.

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

В зависимости от количества вносимых адресов и объёма данных этот импорт может быть относительно не быстр.

Импорт Телеграм

Это обычный импорт, но требуется:

Экспресс-Импорт

Этот вид импорта предназначен для очень быстрого (сотни тысяч в минуты) внесения реестров с адресами и данными персонализации, которые, в последствии, можно использовать в выпусках рассылок.

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

Если по каким-то причинам для выпуска рассылок по реестру не используется Экспресс-выпуск, а работа ведётся по сценарию "Импортировать данные, а потом сделать по ним выпуск", то использование Экспресс-импорта вместо обычного импорта как раз то, что лучше всего подходит для этого.

Внесённые в Экспресс-импорте подписчики и их данные сохраняются в отдельную новую группу-список, всегда создаваемую автоматически. Её название и, при желании, код можно указать в задании Экспресс-импорта.

В дальнейшем, именно по этой группе надо выпускать рассылки.

Группа, созданная при Экспресс-импорте ведёт себя в целом как обычная группа. С ней можно выполнять действия подсчёта количества адресов, получения списка, очистки списка, удаление группы, создания снимка.

Однако, такая группа не может участвовать в условиях отбора у групп-фильтров.

Отличить её можно по параметру expimp в group.list и group.get. Дополнительно, group.get, вызванный для одной группы, возвращает параметр caption для понимания структуры данных, а также count с количеством мемберов, например:

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

Для того, чтобы обычный импорт стал Экспресс-импортом достаточно указать в вызове member.import параметр "express": 1.

Обработку импорта прекратить вызовом track.set

Вызов импорта


{

  "action" : "member.import" 

или

  "action" : "member.import.probe" 

** данные импортирования

  ,"addr_type": "тип вносимых адресов подписчиков (email|msisdn|viber|csid|tg|vknotify|vknotify)" 

и одни из источников данных:

   ,"users.list": адреса и данные для импортирования -- непосредственно в JSON,  СSV или XSLX
                                                     -- подробнее в разделе "Форматы данных для импортирования и Экспресс-Выпуска" 

   ,"encoding" : "base64" -- не обязательно.
                          -- пусто или отсутствует - данные в users.list представлены непосредственно
                          -- base64                - данные в users.list закодированы в base64
или

   ,"users.url": "URL, по которому находятся список адресов и данных для импортирования

                  -- Данные забираются в момент вызова для member.import.probe и в момент начала импортирования для member.import

                  -- При оформлении импорта как действия по расписанию не забудьте, что эту ссылку можно кастомизировать
                  -- временем и указать количество и интервал попыток получения данных. Подробнее в разделе "Замечания" 

                  -- Внешние ссылки http / https / ftp / ftps / sftp или из хранилища загрузок rfs://upload/путь-до-файла

                  -- === Siebel
                  -- Для запросов по SOAP (например, к Siebel) схемы soap:// и soaps:// - обратитесь в Службу поддержки
                  -- для получения дополнительной информации

                  -- === Google Big Query
                  -- Для запросов из Google Big Query настройте внешнюю авторизация для схемы gbd:// (описано в вызовах authext.*)
                  -- Данные получаются полной выборкой из указанной в url вида gbd://authext:AUTHEXT_ID@DATASET_ID/TABLE_ID таблицы.
                  -- При использовании схемы gbd:// обязательно должно быть указано описание получаемых столбцов через параметр caption (описан ниже)

                  -- === amoCRM
                  -- Для получения данных из amoCRM настройте внешнюю авторизацию для типа amocrm и укажите источником адресов ссылку вида
                  --
                  -- amocrm://authext:AUTHEXT_ID@/
                  --
                  -- или с указанием дополнительных полей для внесения
                  --
                  -- amocrm://authext:AUTHEXT_ID@/?fields=phone,name

                  -- === Bitrix24
                  -- Для получения данных из Bitrix24 настройте внешнюю авторизацию для типа bitrix24 и укажите источником адресов ссылку вида
                  --
                  -- bitrix24://authext:AUTHEXT_ID@/
                  --
                  -- или вот так пока у вас настроена работа только с одним bitrix24
                  --
                  -- bitrix24:///
                  --
                  -- или с ограничение списка разделов откуда выбирать адреса - company,lead,contact (по умолчанию изо всех)
                  --
                  -- bitrix24://authext:AUTHEXT_ID@/?list=company,contact

или

  ,"uid": "идентификатор уже загруженных данных" 
          -- возвращается после первого вызова member.import.probe, используется далее вместо cамих данных

** прочие параметры

  ,"express" : 0|1 - признак Экспресс-Импорта

  ,"charset": "кодировка символов ( koi8-r | cp1251 | utf-8 | utf-7 | utf-16 | mac-cyrillic )" 
               -- указание кодировки символов не обязательно - работает автоопределение
               -- для XLSX и JSON смысла не имеет
               -- известная особенность - импорт CSV без явного указания кодировки
               -- если в первых нескольких килобайтах реестра нет ни одной русской буквы, то автопределение
               -- считает, что это cp-1251. Потом выясняется, что клиент всё же использовал utf-8.
               -- редко, но так бывает. Поэтому указывайте кодировку для CSV всё же явно.

  ,"separator": "разделитель символов ("," | ";" | "|" | "t")", -- где t - табуляция
               -- не обязательно - работает автоопределение
               -- для XLSX и JSON смысла не имеет

  ,"firstline": "использовать первую строку как строку конфигурации ( 1 | 0 )" 

  ,"basegroup.id" -- обязательный код базовой группы телеграм-бота для импорта Телеграм, для остальных - игнорируется

** явное описание столбцов для модели АВО (не обязательны)

  ,"caption":  [ -- по порядку следования столбцов в данных, приписывают каждый столбец одному вопросу одной анкеты
                 -- приоритетнее, чем описание из первой строки данных для импортирования
                 -- обязателен при получении данных из Google Big Query

                -- при addr_type = email  колонка с адресом - это анкета "member" вопрос "email" 

                -- при addr_type = msisdn колонка с номером телефона это анкета "member" вопрос "cellphone" 

                -- при addr_type = viber колонка с номером viber - это анкета "member" вопрос "viber" 

                -- при addr_type = csid  колонка с клиентским идентификатором - это анкета "member" вопрос "csid" 

                -- при addr_type = tg  колонка с клиентским идентификатором - это анкета "member" вопрос "tg" 

                -- при addr_type = vknotify  колонка с клиентским идентификатором - это анкета "member" вопрос "vknotify" 

                {

                 "anketa": "id  анкеты",

                 "quest": "id вопроса в анкете",

                  или

                 "ignore": "1" -- игнорировать столбец

                 -- вычислять при внесении. Содержимое колонки не заменит имеющиеся данные, а будет прибавлено к ним
                 -- для задания через первую строку данных для импортирования добавьте знак "=" перед названием колонки

                  "calc" : 1

                  или

                  "/status" : "0|1|active|" -- только для Телеграмм. колонка отсутствует или содержит 1, active или пусто - подписчик добавляется как активный, всё прочее - как отписанный

                 },

                ......

              ]

** явное описание столбцов для модели КД (не обязательны)

  ,"caption":  [ -- по порядку следования столбцов в данных. Приписывают каждый столбец одному вопросу одной анкеты
                 -- приоритетнее, чем описание из первой строки данных для импортирования

                -- при addr_type = email  колонка с адресом - это member.email

                -- при addr_type = msisdn колонка с номером телефона - это member.cellphone

                -- при addr_type = viber колонка с номером viber - это member.viber

                -- при addr_type = csid  колонка с клиентским идентификатором - это member.csid

                -- при addr_type = tg  колонка с клиентским идентификатором - это анкета "member" вопрос "tg" 

                -- при addr_type = vknotify  колонка с клиентским идентификатором - это анкета "member" вопрос "vknotify" 

                {

                 "datakey": "ключ данных колонки", -- как в member.set

                 "mode" : "режим внесения", -- как в member.set

                 "type" : "тип данных", -- как в member.set

                 "autodetect" : 0|1 -- не обязательно. пытаться автоматически преобразовывать вносимое значение-строку
                                    -- по умолчанию: 1 - для ключей base.*, 0- для прочих
                                    --
                                    -- если включено и существуют анкета и в ней вопрос с id как в datakey
                                    -- и это вопрос с выбором, и вносимое значение не подходит ни под один
                                    -- код ответа, то попробовать найти код ответа. Нужно считать, что вносимое значение
                                    -- - это название ответа в вопросе, если значение не подойдёт и не под одно название,
                                    -- то строка не будет внесена с ошибкой "неизвестный ответ" 
                                    --
                                    -- например, для вопроса Пол с ответами "Мужской" - "m" и "Женский" - "w" будет
                                    -- при внесении будут пониматься значения m и w, но и Мужской (преобразуется в m)
                                    -- и Женский (преобразуется в w)
                                    --
                                    -- также, при указании autodecet в строке можно указать несколько ответов (названий
                                    -- ответов) для вопроса с множественным выбором в том же формате, что и при АВО
                                    -- - через вертикальную черту. например "x1|x2|x4" 
                  или

                 "ignore": "1" -- игнорировать столбец

               -- вычислять при внесении. Содержимое колонки не заменит имеющиеся данные, а будет прибавлено к ним.
               -- для задания через первую строку данных для импортирования добавьте знак "=" перед названием колонки

                  "calc" : 1

                  или

                  "/status" : "0|1|active|" -- только для Телеграм, колонка отсутствует или содержит @1@, @active@ или пусто - подписчик добавляется как активный, всё прочее - как отписанный

                 },

                ......

              ]

** пробный импорт

  ,"action": "member.import.probe",

** импорт

  ,"action": "member.import",

  ,"if_exists" : "error|must|ignore" -- не обязательно, по умолчанию существующий подписчик обновляется, а отсутствующий - создаётся
                 -- error     - ошибка при наличии подписчика в базе
                 -- must      - ошибка при отсутствии адреса в базе
                 -- ignore    - игнорирование строки импорта при наличии подписчика в базе без сообщения об ошибке

  ,"newbie.confirm" : "подписчик должен подтвердить внесение в базу (1|0)",

                      -- по умолчанию - внесение без необходимости подтверждения подписчиком (0)

                      -- внесение email без подтверждения ограничено величиной member.noconfirm.rest
                      -- (узнать можно через sys.settings.get) при её исчерпании остальные адреса
                      -- вносятся с подтверждением.

                      -- если количество адресов в базе превышает member.tarif.limit, то величина member.noconfirm.rest
                      -- равна нулю, иначе она равна member.noconfirm.limit за вычетом количества уникальных email
                      -- адресов, внесённых в текущем месяце

                      -- на внесение номеров телефонов параметр newbie.confirm не влияет (они всегда вносятся без подтверждения)

  ,"auto_group" : {
                  -- автоматически создать группу-список для импортируемых адресов
                  -- или дополнить любую существующую группу-список импортируемыми адресами

                  -- для обычного импорта отсутствие всего параметра auto_group означает не создавать новую и не пополнять
                  -- существующую группу
                  -- наличие auto_group, но без id и без name, означает создание группы со стандартным кодом
                  -- и со стандартным названием

                  -- для Экспресс-импорта группа-список создаётся всегда и отсутствие всего параметра auto_group означает
                  -- только, что код и названия будут автоматические
                  -- указание в id существующей группы будет ошибкой

                  -- код созданной группы можно узнать, используя возвращаемый номер track.id и вызов track.get

                  "id" : "идентификатор группы-списка для пополнения" 
                         -- при отсутствии такой группы она создаётся автоматически
                         -- если параметр пуст или отсутствует, то используется стандартный
                         -- код вида importYYYYYMMDDhhmmss или importexYYYYYMMDDhhmmss

                 ,"name" : "название группы" 
                            -- название для создаваемой группы
                            -- если параметр пуст или отсутствует, то используется стандартное "Внесены <дата-время импорта>" 

                 },

   ,"clean_group" : 0|1 -- очищать (1) или нет (0) группу-список, указанную в auto_group перед началом импортирования
                      -- позволяет не дополнять группу, а полностью заменять её участников
                      --
                      -- сначала проверяются все возможные причины, по которым импорт может не начаться. Если они есть, то импорт заканчивается ошибкой и очистка не происходит
                      -- иначе импорт начинается с очистки списка участников, а внесение новых происходит только после этого
                      --
                      -- не путайте удаление адреса из списка участников с удалением адреса из базы - это разные вещи
                      --
                      -- при одновременном импортировании в одну группу несколькими вызовами, у которых хоть у одного clean_group=1, результат
                      -- зависит от порядка выполнения и может быть неожиданным. Для однозначного результата всегда выполняйте импорт с cleangroup=1
                      -- только по завершении всех предыдущих импортов в ту же самую группу и не запускайте новый импорт в такую группу до завершения текущего

-- Добавить/удалить импортируемые адреса в/из групп-списков
-- Не применимо
--     к Экспресс-Импорту - "express":1
--     если "no_member":1
--
-- Очерёдность
--    сначала удаляется из групп "member.group.remove" 
--    потом, вне зависимости от результата remove, добавляются в "member.group.add" 
--    дубли autogroup в member.group.add игнорируются
--    autogroup исполняется ПОСЛЕ remove/add

  ,"member.group.add" : [ код-группы-списка-1, код-группы-списка-2, .... ] -- не обязательно
                      -- группы-списки в которые добавить импортируемые адреса (не зависимо от того, поменял импорт данные или нет)

  ,"member.group.remove" : [ код-группы-списка-1, код-группы-списка-2, .... ] -- не обязательно
                      -- группы-списки из которых удалить импортируемые адреса (не зависимо от того, поменял импорт данные или нет)

  ,"head_attach" : {  -- не обязательно
                      --
                      -- тонкая настройка правил присоединения голов (по аналоги с member.set / member.merge)
                      -- действует на все колонки member.head.attach одинаково.
                      --

                    "newbie.confirm" : "режим подтверждения" 

                   ,"head_rule": { -- параметры, определяющие обработку голов при слиянии
                       "multi": error|none|transplant|replace|replace_same_type|decapitate -- для многоголовых пользователей

                       "single": error|none|transplant|replace|replace_same_type|decapitate -- для одноголовых

                       "newbie": attach|none|replace|replace_same_type - для новых, несуществующих идентификаторов (без голов)
                      }

                    ,"data_rule": "обработка данных при слиянии" -- как в member.merge
                   }

   ,"no_member" : 0|1 -- не создавать подписчика. не обязательно, по умолчанию 0 - создавать
                      -- специфическая настройка, что бы работал безопасный Экспресс-Выпуск по номерам адресов (описано в issue.send)
                      -- требуется что бы внести только адреса (и у них появится необходимый номер) без создания подписчика (который не требуется для Экспресс-Выпуска)
                      -- не поддерживается сочетание express и no_member.
                      -- не поддерживается сочетание no_member и auto_group.
                      -- в реестре должна быть только одна колонка

   ,"format" : " id-формата" -- дополнить данные каждого вносимого адреса данным из формата (игнорируется для КД пока форматы не получат поддержку КД)
                             -- отсутствие или пусто - не дополнять

   ,"sequence.event" : 0|1 -- будет ли внесение/изменение данных вызывать срабатывание событийных действий
                           -- отсутствие или пусто - вызова событий не будет

-- остановить и/или начать прохождение последовательности - аналог sequence.member.stop/start для участников импорта
-- касается всех участников импорта, вне зависимости поменялось у них что-то или нет
-- сначала для адреса останавливаются прохождения по тригерам sequence.stop
-- потом, вне зависимости от результата stop, запускаются прохождения по тригерам sequence.start
-- не применимо при Эксрпесс-Импорте
-- не применимо если no_member

   ,"sequence.stop" : [ номер-тригера-1, номер-тригера-2, .... ] -- не обязательно

   ,"sequence.start" : [ номер-тригера-1, номер-тригера-2, .... ] -- не обязательно

   ,"result" : [ способ возврата результата, смотрите общее описание ] -- данный вызов всегда асинхронный и способ "response" означает сохранение отчёта в хранилище отчётов
}

ответ

{
   <общие поля>

-- только для member.import.probe

  ,"uid": "идентификатор уже загруженных данных" 

  ,"charset": "кодировка символов ( koi8-r | cp1251 | utf-8 | utf-7 | utf-16 | mac-cyrillic )" 

  ,"separator": "разделитель символов ("," | ";" | "|" | "t")" 

  ,"firstline": "использовать первую строку как строку конфигурации ( 1 | 0 )" 

  ,"caption":  [ -- поля конфигурации

                {

               -- при импорте по модели АВО для колонки которой найдено соответствие анкете/вопросу

                 "source": "исходное значение кода колонки из CSV/XLSX" 

                 "name": "название анкета и вопроса",

                 "anketa": "id анкеты",

                 "quest": "id вопроса в анкете",

                 "quest.type" : "тип вопроса в анкете" 

                 "quest.subtype" : "под-тип вопроса" -- еcли применимо к type

                 "quest.width" : "длина поля"  -- еcли применимо к type

               -- при импорте по модели АВО для колонки которой не найдено соответствие

                 "name": "неопределенное значение из элемента строки конфигурации или null",

               -- при импорте по модели ДК

                 "datakey": "ключ данных колонки",

                 "mode" : "режим внесения",

                 "type" : "тип данных",

               -- при импорте по модели ДК для колонки с динамическим ключом данных

                 "dynamic": "1",

               -- для колонки, для которой конфигурации задано игнорирование

                  "ignore" : 1

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

                  "calc" : 1

                 }

              ]

  ,"rows" : [ -- данные первых 15 значащих строк разбитые по полям с учёном указанного разделителя

             [ "колонка-1-строки-1" ,"колонка-2-строки-1" ,"колонка-3-строки-1" ]

            ,[ "колонка-1-строки-2" ,"колонка-2-строки-2" ,"колонка-3-строки-3"]

            .......

           ]

  ,"cannot_import" : 0|1  -- может ли быть выполнен импорт с текущими параметрами запроса
                         -- 0 - может
                         -- 1 - нет

                         -- Именно этот параметр, а не описанный далее параметр warnings сигнализирует о возможности/невозможности импорта,
                         -- т.к. параметр warnings может содержать и не влияющие на запуск процесса импорта предупреждения - например, о том,
                         -- что в первых строках файла есть неверно указанные адреса подписчиков, что не говорит о том, что во всем
                         -- списке подписчиков будет так

   ,"warnings" : [ -- предупреждения о проблемах при анализе данных
                   -- в первую очередь проверяйте параметр cannot_import
                   -- если он показывает, что импорт невозможен, то тут содержится описание почему

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

                  { "id":"код ошибки", "explain": "доп. информация (скаляр, массив или хэш)" }

                  ....

                ]

-- только для member.import

  ,"queue_position" : "номер в очереди импортирования" 

  ,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

}

вверх

Массовое изменение данных подписчиков

Вызов предназначен для единообразного изменения данных сразу многих адресов по правилам, описанных в "Формате заполнения".

Запрос с указанием списка по умолчанию синхронный.

Запрос с указанием группы по умолчанию асинхронный. Используйте sync = 1, если вам реально нужен ответ с описанием того, как и какие адреса были обработаны.

Асинхронные запросы возвращают номер трекера для отслеживания.

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

Установка member.error в "0" удаляет запись о проблемах доставки и снимает блокировку адреса из-за ошибок доставки, если таковая была.

В данный момент вызов не позволяет менять данные по модели КД, так как это происходит через указание формата, а форматы пока не совместимы с КД, но вы можете использовать вызов импорта подписчиков.

Одновременно может выполняться только один такой запрос:

При асинхронном запуске обработку можно прекратить вызовом track.set


{

 "action" : "member.update" 

 ,"format" : код формата заполнения или универсального, из данных которого будут применены изменения

 ,"if_has" : что делать, если изменяемый пункт анкеты уже заполнен

             -- "overwrite" - заменить старое значение новым из формата

             -- "merge"     - объединить старое и новое значения

             -- "skip"      - оставить старое значение

 ,"if_hasnt" : что делать, если изменяемый пункт анкеты ещё не заполнен

             -- "set"  - заполнить указанным в формате значением

             -- "skip" - оставить пункт незаполненным

-- указание подписчиков одним из способов

 ,"email": "идентификатор подписчика" 

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификатора. не обязательно, система сама распознает email или msisdn

или

 ,"list" : [

            "идентификатор подписчика" 

           ,"идентификатор подписчика" 

            ........

           ]

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов в списке. не обязательно, система сама распознает email или msisdn

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 1 - синхронный

или

 ,"group" : код группы к участникам которой будут применены изменения

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный

или

 ,"group.filter" : [
                    фильтр отбора как у group.filter.set
                   ]

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

или

 ,"url" : ссылка на файл со списком адресов, по одному на строке, возможно сжатие zip

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов в списке. не обязательно, система сама распознает email или msisdn

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

или

 ,"stat.uni" : { -- адреса для обработки получаются из запроса Универсальной статистики

                 -- подразумевается unique = 1

                "filter" : [ условие выборки как у запроса в вызове stat.uni ]

               ,"have"   : [ не обязательно. условие отбора поле группировки в фильтре ]

               ,"cache"  : [ настройки кэширования как у запроса в вызове stat.uni ]

               ,"select" : [ ... ] -- используйте, только если не подходит значение по умолчанию [ "member.email" ]

               }

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов. не обязательно, система сама распознает email или msisdn

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный
}

ответ


{

  <общие поля>

-- для асинхронного запроса

,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

-- для синхронного запроса

,"list" : {
           "адрес-1" : 0|1 -- результат изменения - 1 - изменение применено, 0 - нет (например, адрес ошибочен или отсутствует)

          ,"адрес-2" : 0|1

           .................
          }

}

Выслать письмо-подтверждение

Вызов позволяет отправить письма, в которых будут работать специальные команды шаблонизатора для подтверждения внесения в базу ([% anketa_member_tag_a_confirm %]) и удаления из стоп-листов ([% param.url_unsub_cancel %] и [% param.url_unsub_sender_cancel %]).

Запрос с указанием списка по умолчанию синхронный.

Запрос с указанием группы по умолчанию асинхронный. Используйте sync = 1, если вам реально нужен ответ с описанием какие адреса как были обработаны.

Асинхронные запросы возвращают номер трекера для отслеживания прогресса отправки.

При высылке с использованием group/group.filter/stat.uni/url создаётся выпуск, в который группируются отосланные письма.

При асинхронном запуске обработку можно прекратить вызовом track.set


{

 "action" : "member.sendconfirm" 

-- одно из

 ,'confirm' : 1 -- высылка писем для подтверждения внесения в базу

 ,'unsubcancel' : 1 -- высылка писем для удаления из глобального стоп-листа

 ,'unsubsendercancel' : 1 -- высылка писем для удаления из стоп-листа по отправителю.  отправитель - тот что указан в черновике letter

-- указание шаблона письма

 ,"letter" : "код информационного письма" -- обязательно
                                          -- информационное письмо должно иметь заполненный адрес отправителя и не находиться на модерации

 ,"issue_name" : "название выпуска" -- название создаваемого выпуска. Если отсутствует или пусто, то будет использована тема письма из letter
                                    -- не использутеся для email и list

-- указание подписчиков одним из способов

 ,"email": "идентификатор подписчика" 

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификатора. Параметр необязателен, система сама распознает email или msisdn

или

 ,"list" : [

            "идентификатор подписчика" 

           ,"идентификатор подписчика" 

            ........

           ]

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов в списке. Параметр необязателен, система сама распознает email или msisdn

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 1 - синхронный

или

 ,"group" : код группы, участникам которой будут высланы повторные напоминания о подтверждении регистрации

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

или

 ,"group.filter" : [
                    фильтр отбора как у group.filter.set
                   ]

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

или

 ,"url" : ссылка на файл со списком адресов, по одному на строке, возможно сжатие zip.

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов в списке. Параметр необязателен, система сама распознает email или msisdn

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

или

 ,"stat.uni" : { -- адреса для обработки получаются из запроса Универсальной статистики

                 -- подразумевается unique = 1

                "filter" : [ условие выборки как у запроса в вызове stat.uni ]

               ,"have"   : [ параметр необязателен, условие отбора поле группировки в фильтре ]

               ,"cache"  : [ настройки кэширования как у запроса в вызове stat.uni ]

               ,"select" : [ ... ] -- используйте только если не подходит значение по умолчанию [ "member.email" ]

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов. Параметр необязателен, система сама распознает email или msisdn
              }
 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

 ,"track.info" : "дополнительная информация, которая будет потом доступна в track.list/get" -- строка 1024 байта. Параметр необязателен.
}

ответ


{

  <общие поля>

,"issue.id" : номер выпуска -- для group, group.filter, url и stat.uni высылаемые письма будут оформлены отдельным выпуском рассылки,
                            -- номер которого и будет возвращён. Это позволяет изучить статистику по высланным приглашениям

-- для асинхронного запроса

,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

-- для синхронного запроса

,"list" : {
           "адрес-1" : 0|1 -- результат высылки - 1 - выслано, 0 - нет (например, адрес ошибочен или отсутствует или не нуждается в подтверждении)

          ,"адрес-2" : 0|1

           .................
          }
}

вверх

Подтвердить внесение в базу

При успешном подтверждении подписчик становится доступным для участия в рассылках (при отсутствии других противопоказаний к этому).

Если требуется обратное - заново установить состояние "Требуется подтверждение внесения в базу" - используйте вызовы member.set или member.update с установкой member.lockconfirm в "1".


{

  "action" : "member.confirm" 

 ,"email" : "email-адрес подписчика" 

 ,"cookie" : "код подтверждения" 

}

ответ


{

 <общие поля>

 ,"error" : "error/member/wrongcookie" - неверный код. Если ошибка отсутствует, то подтверждение выполнено или не требовалось.

}

вверх

Информация об адресе

Вызовы возвращают информацию об адресе, не связанную с существованием подписчика и даже в случае, если подписчик уже удалён.

{
 "action" : "email.get" 

,"email": "идентификатор подписчика" 

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификатора. Параметр необязателен, система сама распознает email или msisdn.

 ,"with_stoplist" : 2 -- параметр необязателен, 2 - в информации о стоп-листе перечислить записи по отправителям
                      -- при отсутствии или любом другом значении - прежний формат с записями только из глобального стоп-листа
}

ответ

{
 <общие поля>
   "obj" : {

-- информация об адресе по структуре аналогичная ключу member вызова member.get

      "member" : {
         "id" : "номер адреса",  -- он же номер подписчика

         "addr_type" : "email|msisdn|viber|csid|push|vk|tg|vknotify|vknotify" -- тип адреса
         "email" : "адрес",

         "haslock" : "наличие блокировки", -- 0 - нет, 1 - отписался/в стоп листе, 4 - ошибки доставки, 5 = 1 + 4

-- информация об ошибках доставки

         "error" : {
            "lock" : 0 -- нет блокировки из-за ошибок доставки, 1 - есть

             -- при наличии, информация об ошибках

            ,"date" : "2015-09-12 18:53:39"   -- дата последней ошибки доставки (Ys) (удаляется при первой же успешной доставке)
            ,"str"  : "name=test.ru type=A: Host not found" -- описание последней ошибки доставки (удаляется при первой же успешной доставке)
            ,"error" : 1                      -- общее число ошибок доставки, произошедших подряд (устанавливается в 0 при первой же успешной доставке)

            ,"issue"  : 123 -- выпуск последней ошибки доставки
            ,"letter" : 123 -- письмо последней ошибки доставки

            ,"lock_issue"  : 456 -- выпуск, который привел к блокировке
            ,"lock_letter" : 456 -- письмо, которое привело к блокировке

         },

-- информация об отписке/стоп листе

        "lockremove" : 0|1,  -- адрес отписан или в стоп-листе (1) или нет (0)

-- обычный формат - записи только о глобальном стоп-листе
        "stoplist" : {
                       -- в стоп-листе владельца аккаунта, если ключ присутствует в stoplist
                       "owner" : {
                                  "dt" : "2015-11-17 15:02:45", -- дата внесения (Ys)
                                  "source" : "101.101.201.1"    -- источник
                                },

                       -- в стоп-листе, так как подписчик отписался, если ключ присутствует в stoplist
                       "member" : {
                                  "dt" : "2015-11-17 15:02:45", -- дата внесения (Ys)
                                  "source" : "101.101.201.1"    -- источник
                               }
                      }

-- расширенный формат - with_stoplist = 2 - записи и о глобальном стоп-листе и об отписке от отправителей
        "stoplist" : [
                      {  -- пример записи глобального стоп-листа
                       "dt" : "2015-11-17 15:02:45", -- дата внесения (Ys)
                       "source" : "101.101.201.1",   -- источник
                       "type" : "owner",             -- запись внесена владельцем аккаунта
                       "sender" : ""                 -- пусто - глобальный стоп-лист
                       },

                      {  -- пример записи стоп-листа по отправителю
                       "dt" : "2015-11-17 15:02:45", -- дата внесения (Ys)
                       "source" : "101.101.201.1",   -- источник
                       "type" : "member",            -- запись внесена из-за действий подписчика
                       "sender" : "test@test.ru"     -- не пусто - отписка от этого отправителя
                       }
                     ]

    }
  }
}

вверх

Проверка адресов

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

В ответе ключами списка являются адреса из исходного списка в неизменном виде. Нормализованный вид доступен в параметре email.

Если проверка SMTP не указана, то в ответе не будет частей, связанных с её результатами.

Стадии, на которых проверка SMTP закончилась ошибкой (параметр status):

  resolver - ошибка запроса DNS, описание ошибки в параметре message

  nodomain - домен не существует

  nomxa    - у домена нет ни одной записи MX или А

  connref  - не удалось соединиться с MX

  banner   - ошибка в первичном баннере

  helo     - ошибка в ответ на HELO

  mailfrom - ошибка в ответ на MAIL FROM

  rcptto   - ошибка в ответ на RCPT TO

Для понимания того, как работает SMTP и что значат все эти странные слова, полезно изучить RFC 5321.

При асинхронном запуске обработку можно прекратить вызовом track.set

{

  "action" : "email.test" 

 ,"result" : [ способ возврата результата, смотрите общее описание ] -- отчёт содержит поля ответа - email,total,syntax,delivery.lock(как "ok" или "error"),smtp.status

 ,"delivery.error" : 0|1 - выводить данные по ошибкам доставки

 ,"smtp.test" : "проверять доступность по smtp" -- не обязательное поле
                                                -- 0 - не проверять (по умолчанию)
                                                -- lite - проверять, но без проверки существования адреса
                                                -- full - проверять, включая проверку существования адреса
                                                --
                                                -- !!! Полная проверка "full" не всегда даёт результат
                                                -- Многие системы отвечают "существует" на любой адрес
                                                -- Многие системы примут такую проверку за спам-активность
                                                -- Используйте такую проверку, только если понимаете что делаете

 ,"smtp.timeout" : "таймаут в секундах" -- не обязательное поле, по умолчанию 15

 ,"auto_group" : { -- параметр необязателен
                  -- автоматически создать группу-список для не прошедших проверку подписчиков
                  -- или дополнить любую существующую группу-список не прошедшими проверку подписчиков

                  -- в группу заносят только те адреса, у которых нет синтаксических ошибок, но есть ошибки smtp и в базе уже есть подписчик с таким адресом

                  -- отсутствие всего параметра auto_group означает ни создавать новую, ни пополнять существующую группу
                  -- наличие auto_group, но без id и без name означает создание группы со стандартным кодом
                  -- и со стандартным названием. Код созданной группы можно узнать, используя возвращаемый номер track.id
                  -- и вызов track.get

                  "id" : "идентификатор группы-списка для пополнения" 
                         -- при отсутствии такой группы она создаётся автоматически
                         -- если параметр пуст или отсутствует, то используется стандартный
                         -- код вида importYYYYYMMDDhhmmss
l
                 ,"name" : "название группы" 
                            -- название для создаваемой группы.
                            -- если параметр пуст или отсутствует, то используется стандартное "Внесены <дата-время импорта>" 

                 },

  ,"clean_group" : 0|1 -- очищать (1) или нет (0) группу-список указанную в auto_group перед началом проверки. Параметр необязателен

-- указание адресов одним из способов

 ,"email": "идентификатор подписчика" 

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов. Параметр необязателен, система сама распознает email или msisdn

или

 ,"list" : [

            "идентификатор подписчика" 

           ,"идентификатор подписчика" 

            ........

           ]

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов. Параметр необязателен, система сама распознает email или msisdn

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 1 - синхронный

или

 ,"group" : код группы, участникам которой будут высланы повторные напоминания о подтверждении регистрации

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

или

 ,"group.filter" : [
                    фильтр отбора как у group.filter.set
                   ]

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

или

 ,"url" : ссылка на файл со списком адресов, по одному на строке, возможно сжатие zip.

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов. Параметр необязателен, система сама распознает email или msisdn

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

или

 ,"stat.uni" : { -- адреса для обработки получаются из запроса Универсальной статистики

                 -- подразумевается unique = 1

                "filter" : [ условие выборки как у запроса в вызове stat.uni ]

               ,"have"   : [ параметр необязателен, условие отбора - поле группировки в фильтре ]

               ,"cache"  : [ настройки кэширования как у запроса в вызове stat.uni ]

               ,"select" : [ ... ] -- используйте, только если не подходит значение по умолчанию [ "member.email" ]

               }

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов. Параметр необязателен, система сама распознает email или msisdn

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

 ,"track.info" : "дополнительная информация, которая будет потом доступна в track.list/get" -- строка 1024 байта. Параметр необязателен

}

ответ


{

    <общие поля>

   "list" : { -- ключ - оригинальное значение адреса из запроса

              " missing@CityCat.ru" : {
                                        "total" : "ok|bad" -- ok  - syntax=ok, delivery.lock=0 (если заказывалось), smtp.status=ok (если заказывалось)
                                                           -- bad - в других случаях

                                       ,"syntax" : "ok" -- результат синтаксической проверки адреса
                                                        -- ok - нормально
                                                        -- иначе код ошибки

                                       -- остальные поля не имеют смысла если syntax не "ok" 

                                       ,"email" : "missing@citycat.ru" -- нормализованная форма адреса

                                       ,"email.id" : "номер адреса в системе" 

                                       ,"delivery" : { -- если заказан вывод ошибок доставки
                                                      "str" : "host said: 550 SMTP error from remote mail server after end of data: 552 5.2.2 Mailbox size limit",
                                                      "lock" : "0",
                                                      "dt" : "2018-07-26 11:58:06",
                                                      "error" : "1" 
                                                      },

                                       ,"smtp"  : { -- если заказана проверка по smtp

                                              "status"  : "rcptto" -- стадия возникновения ошибки

                                             ,"domain"  : "citycat.ru" -- домен, для которого определялся первичный MX

                                             ,"mx"      : "smtp.citycat.ru" -- первичный MX, используемый для теста

                                             ,"ip"      : "81.9.34.192" -- ip-адрес использованного MX

                                             ,"ptr"     : [             -- список имён, соответствующих ip-адресу
                                                           "cat192.subscribe.ru" 
                                                          ]

                                             ,"code"    : "550" -- код SMTP-ошибки (000 - тайм-аут)

                                             ,"dsn"     : "5.1.1" -- Enchanced status code SMTP-ошибки (при наличии в ответе)

                                             ,"message" : "<missing@citycat.ru>... User unknown" 
                                                           -- текст SMTP-ошибки или ошибки DNS

                                                  }

                                       }
             ,"     PRO@subscribe.ru   " :  { "email" : "pro@subscribe.ru" 

                                             ,"syntax" : "ok" 

                                             ,"smtp" : {
                                                          "status" : "ok" 

                                                         ,"domain" : "subscribe.ru" 

                                                         ,"mx"     : "smtp.subscribe.ru" 

                                                         ,"ip"     : "81.9.34.192" 

                                                         ,"ptr"    : [
                                                                      "cat192.subscribe.ru" 
                                                                     ]
                                                       }

                                            }

             ,"123@test@test.ru  " :   { "email" : null

                                       ,"syntax" : "error/email/multydog" -- код ошибки

                                      }

         }

}

вверх

Удаление ошибок доставки

Удаляются записи об ошибках доставки указанных адресов.

С подписчика, при наличии, снимается блокировка из-за ошибок доставки.

Но наличие подписчика не обязательно (в отличии от вызова member.set, который работает именно с подписчиками)

При асинхронном запуске обработку можно прекратить вызовом track.set

{

  "action" : "email.cleanerror" 

-- указание адресов одним из способов

 ,"email": "идентификатор подписчика" 

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификатора. Параметр необязателен, система сама распознает email или msisdn

или

 ,"list" : [

            "идентификатор подписчика" 

           ,"идентификатор подписчика" 

            ........

           ]

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов в списке. Параметр необязателен, система сама распознает email или msisdn

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 1 - синхронный

или

 ,"group" : код группы, участникам которой будут высланы повторные напоминания о подтверждении регистрации

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

или

 ,"group.filter" : [
                    фильтр отбора как у group.filter.set
                   ]

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

или

 ,"url" : ссылка на файл со списком адресов, по одному на строке, возможно сжатие zip.

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов в списке. Параметр необязателен, система сама распознает email или msisdn

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

или

 ,"stat.uni" : { -- адреса для обработки получаются из запроса Универсальной статистики

                 -- подразумевается unique = 1

                "filter" : [ условие выборки как у запроса в вызове stat.uni ]

               ,"have"   : [ параметр необязателен, условие отбора - поле группировки в фильтре ]

               ,"cache"  : [ настройки кэширования как у запроса в вызове stat.uni ]

               ,"select" : [ ... ] -- используйте, только если не подходит значение по умолчанию [ "member.email" ]

               }

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов. Параметр необязателен, система сама распознает email или msisdn

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

 ,"track.info" : "дополнительная информация которая будет потом доступна в track.list/get" -- строка 1024 байта. Параметр необязателен
}

ответ

{

    <общие поля>

   "list" : { -- ключ - оригинальное значение адреса из запроса

             "email1" :  результат -- null - адрес синтаксически не верен
                                    -- 1 - ошибки доставки удалены, подписчик разблокирован (если был заблокирован)
                                    -- 0 - не было ошибок доставки
            ,"email2" : ...

         }

}

вверх

Обогащение данных подписчиков

Обогащение данных подписчиков это получение для интересующих вас адресов данных о их покупательском поведении.

Для подключения свяжитесь со Службой Поддержки.

Достаточно составить группу-список или группу-фильтр с интересующей вас аудиторией и указать связать эту группу с одной или несколькими тематиками (вызов sys.settings.get[tde.topic]).

Система будет сама обновлять в данных подписчика специальный ключ данных tde по мере появления информации.

По полученным данным, например, можно сегментировать аудиторию или запускать триггеры реагирующие на покупки (купил корм для кошек - выслать напоминание, что надо регулярно вакцинировать животное).

Информация хранятся в ключе данных tde в виде следующей структуры

 "код категории" : {
                    "count" : число сколько раз получено событие данной категории

                   ,"firstOccurrence" : дата Ys когда событие получено первый раз 

                   ,"lastOccurrence" : дата Ys когда событие получено последний раз

                   ,"topic" : "код категории" 
                   }

например

  "tde" : {
         "travellers_inexpensive" : {
            "count" : 2,
            "firstOccurrence" : "2021-12-06 18:18:53",
            "lastOccurrence" : "2021-12-13 18:36:38",
            "topic" : "travellers_inexpensive" 
         },
         "drugs_anticold" : {
            "count" : 1,
            "firstOccurrence" : "2022-03-19 17:54:00",
            "lastOccurrence" : "2022-03-19 17:54:00",
            "topic" : "drugs_anticold" 
         },
         "drugs_antiviral" : {
            "count" : 1,
            "firstOccurrence" : "2022-03-19 17:54:00",
            "lastOccurrence" : "2022-03-19 17:54:00",
            "topic" : "drugs_antiviral" 
         },
         "clothes_buyers" : {
            "count" : 2,
            "firstOccurrence" : "2022-05-12 18:16:08",
            "lastOccurrence" : "2022-10-04 14:41:27",
            "topic" : "clothes_buyers" 
         }
       ....

Полный лог появления записей по тематикам доступен через параметр tde в Универсальной Статистике.

Cписок доступных тематик

Автомобилисты drivers
Автомобилисты (отечественные авто) drivers_russian_cars
Автомобилисты (зарубежные авто) drivers_foreign_cars
Богатые (совершили дорогие покупки) rich_people
Беременные pregnant
Владельцы рыбок fish_owners
Владельцы собак dogs_owners
Владельцы грызунов rodents_owners
Владельцы птиц birds_owners
Владельцы котов и кошек cats_owners
Дачники cottagers
Женщины women
Кулинария cooking
Матери с дошкольниками mothers_w_preschoolers
Матери со школьниками mothers_w_schoolers
Мамы с детьми 0-3 год mothers_w_children_0_3
Мамы с детьми 14-18 лет mothers_w_children_14_18
Мамы с детьми 3-6 лет mothers_w_children_3_6
Мамы с детьми 6-9 лет mothers_w_children_6_9
Мамы с детьми 9-14 лет mothers_w_children_9_14
Принтеры printers
Придерживаются ЗОЖ healthy_lifestyle
Путешественники travellers
Путешественники бизнес-классом travellers_business_class
Путешественники по России travellers_rf
Путешественники по бюджетным направлениям travellers_inexpensive
Путешественники по дорогим направлениям travellers_expensive
Покупатели спорт-инвентаря sport_equipment_buyers
Покупатели успокоительных drugs_sedative
Покупатели уходовых средств для волос hair_care_product_buyers
Покупатели уходовой косметики для тела body_cosmetics_buyers
Покупатели уходовой косметики для лица facial_cosmetics_buyers
Покупатели укладочных средств для волос hair_styling_product_buyers
Покупатели фастфуда fast_food_buyers
Покупатели лекарств для ЖКТ drugs_gastric
Покупатели бытовой техники для кухни kitchen_appliances_buyers
Покупатели бюджетных гаджетов inexpensive_gadgets_buyers
Покупатели билетов в кино (оффлайн) cinema_ticket_buyers
Покупатели одежды clothes_buyers
Покупатели одежды масс-маркета mass_market_clothes_buyers
Покупатели мебели furniture_buyers
Покупатели витаминов vitamins
Покупатели витаминов для беременных vitamins_for_pregnant
Покупатели витаминов для мужчин vitamins_for_men
Покупатели витаминов для волос vitamins_for_hairs
Покупатели витаминов для женщин vitamins_for_women
Покупатели витаминов для детей vitamins_for_children
Покупатели витаминов для кожи vitamins_for_skin
Покупатели противовирусных препаратов drugs_antiviral
Покупатели противогрибковых препаратов drugs_antifungal
Покупатели противопростудных лекарств drugs_anticold
Покупатели премиальных гаджетов premium_gadgets_buyers
Покупатели премиальных аксессуаров premium_accessories_buyers
Покупатели премиальной одежды premium_clothes_buyers
Покупатели премиальной косметики premium_cosmetics_buyers
Покупатели препаратов от кашля drugs_antitussive
Покупатели препаратов для сердечно-сосудистой системы drugs_cardiovascular
Покупатели препаратов для иммунной системы drugs_for_immunity
Покупатели подписок на кино / сериалы online_cinema_subscribers
Покупатели парфюма perfume_buyers
Покупатели декоративной косметики для лица facial_makeup_buyers
Покупатели антибиотиков drugs_antibiotics
Покупатели антигеммороидальных препаратов drugs_antihemorrhoidal
Покупатели аксессуаров масс-маркета mass_market_accessories_buyers
Покупатели крупной бытовой техники domestic_appliances_buyers
Покупатели косметики для ухода cosmetics_buyers
Рукоделие handiwork
Рыбалка / Охота fishing_hunting
Ремонт apartment_renovation
Студенты students
Чистовая отделка apartment_renovation_finishing
Черновой ремонт apartment_renovation_rough

вверх

Группы адресов - Сегментирование аудитории

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

Группы-списки - это списки адресов, формируемые вами в явном виде. Участие адреса в такой группе не зависит от его персональных данных и статистики. Выпуск и подсчёт статистик по таким группам несколько быстрее, так как список адресов предопределён.

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

Фильтр группы-фильтра поддерживает оба формата хранения - и АВО, и КД.

Cписок групп


{

 "action" : "group.list" 

-- параметры фильтрации, должен быть хотя бы один параметр
--
-- если выбрана последняя порция списка, то ответ содержит "last_page" : 1
--
-- доступны поля
--
-- group.gid           -- символический код группы
-- group.name          -- название группы
-- group.addr_type     -- тип адресов -- email|msisdn|viber|csid|push|vk|tg|vknotify|any
-- group.create.date   -- дата и время создания -- Ys, null
-- group.update.date   -- дата и время последнего изменения -- Ys, null
-- group.reltype       -- число
-- group.relref        -- число
-- group.dictnode      -- номер узла словаря
-- group.type          -- тип группы -- list или filter
-- group.addr_type     -- тип адресов -- email|msisdn|viber|csid|push|vk|tg|vknotify|any
-- group.expimp        -- группа от Экспресс-импорта (!=0) или обычная (==0)

,"filter" : [ фильтр в синтаксисе stat.uni ]

,"order" : [ сортировка ответа в синтаксисе stat.uni ]

,"skip" : количество пропускаемых записей от начала списка, по умолчанию 0

,"first" : количество выбираемых записей после skip, по умолчанию 50, не более 50

}

ответ


{

 <общие поля>

,"list" : [

            {

             "id" : "код группы" 

            ,"name" : "название" 

            ,"type" : "тип группы" -- list или filter

            ,"addr_type" : "тип адресов" -- email|msisdn|viber|csid|push|vk|tg|vknotify

            ,"create.time" : "дата и время создания" -- Ys, null

            ,"update.time" : "дата и время последнего изменения" -- Ys, null

            ,"expimp" : "группа Экспресс-Импорта" --  0 -пусто-отсутствует - это группа не от Экспресс-импорта
                                                  -- -1 -группа от Экспресс-импорта, но данные уже удалены
                                                  -- иначе - Экспресс-импорт и данные на месте

            ,"reltype" : ...

            ,"relref" : ...
            }

            ...

           ]

}

Создать группу

При создании группы типа filter она первоначально получит пустой набор правил отбора.

До явной установки желаемых правил отбор по такой группе всегда будет давать пусто.


{

  "action" : "group.create" 

 ,"id" : "смысловой код группы. символы a-zA-Z0-9_" 
         -- например "clients2011" 
         -- параметр необязателен, при отсутствии будет назначен автоматически

 ,"name" : "название группы" -- параметр обязателен

 ,"type" : "list | filter" -- параметр обязателен, тип группы

                           -- list - группа является заранее создаваемым списком

                           -- filter - группа является набором фильтров для отбора по всей базе

 ,"addr_type" : "email|msisdn|viber|push|csid|vk|tg|vknotify" -- параметр обязателен. Тип адресов на работу, с которыми будет ориентирована группа

 ,"reltype" : ...

 ,"relref" : ...
}

ответ


{

 <общие поля>

 , "id" : "код созданной группы" 

}

вверх

Прочитать группу

Обратите внимание, что при вызове со списком групп, сообщения об ошибочных кодах групп будут возвращены не в errors (как это было бы при одиночном вызове), а в warnings, так как в целом считается, что вызов закончился успешно.


{

  "action" : "group.get" 

 ,"with_filter" : 0 | 1 -- вернуть в ответе, также и фильтр группы

 ,"id" : "код группы" - для одной группы

-- или

 ,"id" : [ "код группы", "код группы", ... ]  - для списка групп

-- или

 ,"id" : [ "*" ] - для всех групп
}

ответ


{

 <общие поля>

-- для одной группы

 ,"obj" : {

             ,"id" : "код группы" 

             ,"name" : "название группы" 

             ,"type" : "тип группы" -- list или filter

             ,"addr_type" : "тип адресов" -- email|msisdn|viber|csid|push|vk|tg|vknotify

             ,"filter" : [ -- если запрошено
                          фильтр группы как в вызове group.filter.get
                         ]

             ,"create.time" : "дата и время создания" -- Ys, null

             ,"update.time" : "дата и время последнего изменения" -- Ys, null

             ,"expimp" : "группа Экспресс-Импорта" --  0 -пусто-отсутствует - это группа не от Экспресс-импорта
                                                   -- -1 -группа от Экспресс-импорта, но данные уже удалены
                                                   -- иначе - Экспресс-импорт и данные на месте

             ,"caption" : [
                            -- если группа от Экспресс-импорта, то тут описана структура данных
                            -- в том же формате, что используется в вызове импорта
                          ]

             ,"count" : { -- для группы от Экспресс-импорта
                         "total" : "количество уникальных адресов" 
                        }

             ,"reltype" : ...

             ,"relref" : ...
          }

-- или для нескольких групп

 ,"list" : [
            {
             содержимое obj для одной группы
            }

           ,{
             содержимое obj для другой группы
            }

            .....
           ]

 ,"warnings" : [ -- может отсутствовать
                массив аналогичный по структуре массиву errors
                содержит ошибки для тех кодов групп из списка, которые ошибочны по тем или иным причинам
               ]

}

вверх

Изменить группу

Не указанные параметры не изменяются.


{

  "action" : "group.set" 

 , "id" : "код группы" 

 ,"name" : "название группы" - параметр необязателен

 ,"reltype" : ...

 ,"relref" : ...

--  необязательный

 ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ


{

 <общие поля>

}

если "return_fresh_obj" : "1" 

ответ -- как на запрос чтения "action" : "group.get" соответствующей анкеты

вверх

Удалить группу


{

  "action" : "group.delete" 

 , "id" : "код группы" 

}

ответ


{

 <общие поля>

}

вверх

Получить правила фильтрации группы

Только для групп с типом filter


{

  "action" : "group.filter.get" 

 ,"id" : "код группы" 

 ,"with_in_group" : 0|1 -- включить в ответ информацию об использовании условий in_group/!in_group
                        -- параметр необязателен, по умолчанию 0

 ,"expand" : 0|1 -- развернуть фильтр
                 -- параметр необязателен, по умолчанию 0
                 -- На месте условия in_group/!in_group подставляется дерево условий включаемой группы-фильтра и добавляются параметры x-op и x-v для понимания того, что было подставлено
                 -- условия групп-списков не подставляются
                 -- на месте условия PRF подставляется дерево условий включаемой группы-фильтра и добавляются параметры x-which и x-pid для понимания что было подставлено
                 -- При каких-либо затруднениях разворачивания фильтра не производится и условие остаётся в оригинальном виде

 ,"with_check" : 0|1 -- проверь фильтр
                     -- ошибки будут сообщены через warnings
                     -- можно, например, узнать не удалена ли группа из in_group
}

ответ


{

 <общие поля>

 ,"update.time" : "дата-время изменения фильтра" -- Ys

 ,"obj" : [

               массив, описывающий фильтр

             ]

 ,"in_group" : {
      "count" : 0, -- сколько раз используется in_group или !in_group
      "uniq" : 0,  -- сколько уникальных групп используется
      "list" : [], -- список уникальных групп
      "usage : { "group" : 123, ... }, -- какая группа сколько раз использовалась
      "depth" : 0, -- максимальный уровень вложенности
      "max_reusage" : 0,  -- максимально раз использования одной группы
      "tree" : ""  -- строка представляющая собой дерево вложенности вызовов in_group/!in_group
                   -- форматирована переводами строк и отступами для обозначения уровня вложенности
   },
}

вверх

Установить правила фильтрации группы

Только для групп с типом filter


{

  "action" : "group.filter.set" 

 ,"id" : "код группы" 

 ,"filter" : [

              массив, описывающий фильтр

             ]

}

ответ


{

 <общие поля>

}

вверх

Совпадение одного адреса с фильтром группы

Вызов в основном полезен для отладки сложных фильтров - по результату label можно видеть ход выполнения условий, а по safe - результат работы итератеров has.

{
 "action" : "group.filter.match" 

,"id" : кодгруппы

,"email": "идентификатор подписчика" 

,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификатора. Параметр необязателен, система сама распознает email или msisdn
}

Ответ

{
 <общие поля>

 "match" : 0 | 1 | null -- с фильтром не совпало, совпало, ошибка фильтра

,"safe" :  { ... } -- данные, сохранённые итератерами has в фильтре

,"label" : { ... } -- метки, сохранённые в фильтре при проходе по условиям
}

вверх

Снимок группы / Расширить группу-список

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

Так как источником может быть группа-фильтр или просто фильтр, то вы получаете неизменный "снимок" состояния группы на момент вызова.

Это полезно, например, если требуется провести несколько статистических анализов (не надо для каждого анализа заново искать подписчиков по фильтру и их состав не изменен) или для быстрого выхода рассылки с очень сложным фильтром (можно заранее сделать снимок, скажем ночью, и потом днём очень быстро по нему выпустить рассылку).

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

При асинхронном запуске обработку можно прекратить вызовом track.set

{
 "action" : "group.snapshot" 

  "to" : { -- получатель данных

          "id" : "код группы" -- группа-список, в которую будут внесены адреса
                              -- обратите внимание, что это не вызов импорта списков !
                              -- если адреса, указанные в источниках email, list, stat.uni на момент
                              -- вызова отсутствуют в базе, то они не будут созданы и добавлены,
                              -- для этого есть импорт подписчиков

         ,"clean" : 0|1  -- полностью очистить группу перед началом внесения

         ,"resync" : 0|1 -- после внесения новых участников удалить старых, не попадающих более под условия источника
                         -- имеет смысл с group/group.filter/stat.uni, эквивалентно clean=1 для email/list, но более долго по исполнению
         }

 ,"from" : { -- источник данных одно из

         "email" : "адрес подписчика" 

        ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов. Параметр необязателен, система сама распознает email или msisdn

        -- или

          "list" : [

                    "адрес подписчика" 

                   ,"адрес подписчика" 

                    ........

                   ]

         ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов. Параметр необязателен, система сама распознает email или msisdn

         ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 1 - синхронный

        -- или

          "group" : код группы

         ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

        -- или

          "group.filter" : [
                            фильтр отбора как у group.filter.set
                           ]

         ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

        -- или

         "stat.uni" : { -- адреса для обработки получаются из запроса Универсальной статистики

                 -- подразумевается unique = 1

                "filter" : [ условие выборки как у запроса в вызове stat.uni ]

               ,"have"   : [ параметр необязателен, условие отбора - поле группировки в фильтре ]

               ,"cache"  : [ настройки кэширования как у запроса в вызове stat.uni ]

               ,"select" : [ ... ] -- используйте, только если не подходит значение по умолчанию [ "member.email" ]

               }

         ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов. Параметр необязателен, система сама распознает email или msisdn

         ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный}
   }

 ,"track.info" : "дополнительная информация, которая будет потом доступна в track.list/get" -- строка 1024 байта. Параметр необязателен

 ,"sequence.event" : 0|1 -- будет ли добавление в группу генерировать событие для триггеров
                        -- отсутствие или пусто - событий не будет

}

ответ

{

 <общие поля>

-- для асинхронного запроса

,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

-- для синхронного запроса -- ничего дополнительного

}

вверх

Очистить группу-список

Вызов удаляет из участников группы-списка заданные адреса.

Сами адреса в базе остаются ! Удалением адресов из базы занимается вызов member.delete.

{
  "action" : "group.clean" 

 ,"id" : "код группы" -- группа-список, из которой будут удалены адреса

-- указание подписчиков одним из способов

 ,"all" : 1 -- все адреса, быстрый синхронный вызов
            -- для получения трекинга или асинхронности, используйте источник "group" c таким же значением что и "id" (более медленно)

или

 ,"email" : "адрес подписчика" -- вызов синхронный

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов. Параметр необязателен, система сама распознает email или msisdn

или

 ,"list" : [

            "адрес подписчика" 

           ,"адрес подписчика" 

            ........

           ]

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов. Параметр необязателен, система сама распознает email или msisdn

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 1 - синхронный

или

 ,"group" : код группы участники которой будут удалены

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

или

 ,"group.filter" : [
                    фильтр отбора как у group.filter.set
                   ]

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

или

 ,"url" : ссылка на файл со списком адресов. по одному на строке. возможно сжатие zip.

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

или

 ,"stat.uni" : { -- адреса для обработки получаются из запроса Универсальной статистики

                 -- подразумевается unique = 1

                "filter" : [ условие выборки как у запроса в вызове stat.uni ]

               ,"have"   : [ параметр необязателен, условие отбора - поле группировки в фильтре ]

               ,"cache"  : [ настройки кэширования как у запроса в вызове stat.uni ]

               ,"select" : [ ... ] -- используйте, только если не подходит значение по умолчанию [ "member.email" ]

               }

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|id -- тип идентификаторов. Параметр необязателен, система сама распознает email или msisdn

 ,"sync" : 0|1 -- изменение синхронности запроса, по умолчанию 0 - асинхронный

 ,"track.info" : "дополнительная информация которая будет потом доступна в track.list/get" -- строка 1024 байта. Параметр необязателен
}

ответ

{

 <общие поля>

-- для асинхронного запроса

,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

-- для синхронного запроса -- ничего дополнительного

}

вверх

Анкеты

Анкеты предназначены для описания структуры и иерархии данных для формата АВО.

Список анкет


{

 "action" : "anketa.list" 

-- параметры фильтрации, должен быть хотя бы один параметр
--
-- если выбрана последняя порция списка, то ответ содержит "last_page" : 1
--
-- доступны поля
--
-- anketa.id --  "уникальный идентификатор анкеты" 
--

,"filter" : [ фильтр в синтаксисе stat.uni ]

,"order" : [ сортировка ответа в синтаксисе stat.uni ]

,"skip" : количество пропускаемых записей от начала списка -- по умолчанию 0

,"first" : количество выбираемых записей после skip -- по умолчанию 50, не более 50

}

ответ


{

 <общие поля>

,"list" : [

            {

             "system" : "системная, да, нет (1, 0)" -- системные анкеты не доступны для изменения

             ,"id" : "уникальный идентификатор" 

             ,"name" : "название",

            }

            ...

           ]

}

Чтение анкеты


{

 "action" : "anketa.get" 

,"id"     : код-анкеты

}

ответ


{

 <общие поля>

,"obj" : {

          "id" : код-анкеты,

          "param" : {

                     "name"     : "название анкеты",

                     "system"   : 0|1 -- анкета системная, попытки изменения закончатся ошибкой

                    },

          "order" : [ -- порядок вопросов

                     "код-вопроса3",

                     "код-вопроса8",

                     .....

                     "код-вопроса4" 

                    ]

          "quests" : { -- вопросы анкеты

             "код-вопроса" : {

                             "id" : код-вопроса,

                             "@"  : номер по порядку,

                             "name" : формулировка вопроса,

                             "type" : тип вопроса,

                             --  для типа "dt" 

                             "subtype" : под-тип вопроса
                                        --  yd  - Дата и время с точностью от года до дня
                                        --  yh  - Дата и время с точностью от года до часа
                                        --  ym  - Дата и время с точностью от года до минуты
                                        --  ys -  Дата и время с точностью от года до секунды

                             --  для типа "free" 

                             "width" : ширина поля в байтах для типа free

                             --  для типа "1m" или "nm" 

                             "answers" : { -- ответы вопроса

                                          "код ответа1" : "название ответа1",

                                          "код ответа2" : "название ответа2",

                                           .....

                                          "код ответа3" : "название ответа3",

                                         },

                             "order" : [ -- порядок ответов

                                        "код ответа3",

                                        "код ответа1",

                                        .....

                                        "код ответа2" 

                                       ],

                            "form" : {
                                       -- свойства вопроса при использовании анкеты в форме как анкеты для первичного приёма данных
                                     }
                            },

             ............

         },

}

вверх

Удаление анкеты


{

    "action" : "anketa.delete" 

   ,"id" : "уникальный идентификатор анкеты" 

}

ответ


{

    <общие поля>

}

вверх

Создание анкеты


{

     "action" : "anketa.create" 

    ,"name" : "Название анкеты",

     необязательные:

    ,"copy_from" : "уникальный идентификатор анкеты (не нужен для создаваемой с нуля, а не копируемой анкеты)" 

    ,"id" : "уникальный код анкеты" 

    -- не обязательно

    ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ


{

  <общие поля>

,"id" : "код анкеты" 

}

если "return_fresh_obj" : "1" 

ответ -- как на запрос чтения "action" : "anketa.get" соответствующей анкеты

вверх

Сохранение анкеты


{

    "action" : "anketa.set" 

    ,"id" : "идентификатор анкеты" 

    ,"name" : "название анкеты" 

    необязательный

    ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 
}

ответ


{

    <общие поля>

}

если "return_fresh_obj" : "1" 

ответ -- как на запрос чтения "action" : "anketa.get" соответствующей анкеты

вверх

Добавления нового вопроса анкеты


{

   "action" : "anketa.quest.add" 

  ,"anketa.id" : "уникальный идентификатор анкеты вопроса" 

-- добавление одного вопроса

  ,obj : { -- описание вопроса

          "name" : "Текст вопроса" 

         ,"type" : "Тип вопроса" 
                 -- free - свободный ввод
                 -- dt   - дата и время
                 -- 1m   - выбор одного из списка
                 -- nm   - выбор нескольких из списка
                 -- int  - целое число

         -- параметры, определяемые типом вопроса:

         -- для type=free:

              ,"width" : "количество символов для свободного ввода" -- обязателен,  > 0

         -- для  type=dt:

              ,"dtsubtype" : "точность для даты и времени" -- обязателен
                              -- yd - от года до дня.     в данных записывается и выводится в формате "YYYY-MM-DD" 
                              -- yh - от года до часа.    в данных записывается и выводится в формате "YYYY-MM-DD hh" 
                              -- ym - от года до минуты.  в данных записывается и выводится в формате "YYYY-MM-DD hh:mm" 
                              -- ys - от года до секунды. в данных записывается и выводится в формате "YYYY-MM-DD hh:mm:ss" 

         -- для type=1m или nm (выбор из списка):

              ,"answers" : { -- ответы вопроса

                            "код ответа 1" : "название ответа1" 

                           ,"код ответа 2" : "название ответа2" 

                           .....

                           ,"код ответа N" : "название ответаN" 

                          }

             ,"order" : [ -- порядок ответов

                           "код ответа 3" 

                          ,"код ответа 8" 

                          .....

                          ,"код ответа 4" 
                        ]

         -- необязательные для любого типа

         ,"id" : "уникальный идентификатор вопроса" 

         -- свойства вопроса при использовании анкеты в форме как анкеты для первичного приёма данных
         -- не обязательно
        ,"form" : {

             -- Видимость в форме

            "hidden" : 0|1 -- 0 - по умолчанию - поле в форме будет полем ввода
                              1 - поле в форме будет иметь тип hidden и значение default

            -- Обязательность заполнения

           ,"mustbe" : 0|1 -- 0 - по умолчанию - поле не обязательно для заполнения (игнорируется для hidden=1)
                           --     при первоначальном отображении формы в поле будет значение default
                           -- 1 - поле обязательно для заполнения

            -- Значение по умолчанию

            ,"default" : "значение по умолчанию" -- для всех вопросов кроме 1m и nm

            ,"default" : [код ответа] -- для вопросов 1m

            ,"default" : [код ответа1 , код ответа 2, ... ] -- для вопросов nm

            -- в какой вопрос какой анкеты-хранилища копируется результат при подтверждении заполнения формы
            -- параметры или оба отсутствуют или оба указаны и не пустые
            -- анкета не может быть системной или показывать на саму себя
            -- тип вопроса, в который будет идти копирование должен совпадать с типом вопроса в obj
            -- для вопросов 1m и nm все возможные ответы текущего вопроса должны быть в ответах вопроса в который будет идти копирование

            ,"trans.ank"   : "код анкеты" 
            ,"trans.quest" : "код вопроса" 
          }
        }

-- добавление нескольких вопросов, любая ошибка отменяет все изменения. Будут добавлены или все вопросы или ни

  ,obj : [
          { описание вопроса-1 }
         ,{ описание вопроса-2 }
         ,...............
         ]

   -- не обязательно

  ,"return_fresh_obj" : 0|1  -- вернуть новый объект анкета -- да (1), нет
}

ответ


{

  <общие поля>

 ,"obj" : { объект анкета как из ответа anketa.get } -- если "return_fresh_obj" : "1" 

-- в зависимости от вида параметра obj в вызове

 ,"id" : "id-добавленного вопроса" 

-- или

 ,"id" : [ -- порядок id соответствует порядку объектов в параметре obj в вызове
          "id-добавленного вопроса-1" 
         ,"id-добавленного вопроса-2" 
         .......
         ]

}

вверх

Изменение вопроса анкеты


{

   "action" : "anketa.quest.set" 

  ,"anketa.id" : "уникальный идентификатор анкеты вопроса" 

-- добавление одного вопроса

  ,"obj" : { -- описание вопроса

            "id" : "уникальный идентификатор вопроса" 

           ,"name" : "Текст вопроса" 

           -- параметры, определяемые типом вопроса:

           -- для type=free:

                ,"width" : "количество символов для свободного ввода, обязателен,  > 0" 

           -- для type=1m или nm:

                ,"answers" : { -- ответы вопроса

                              "код ответа 1" : "название ответа 1" 

                             ,"код ответа 2" : "название ответа 2" 

                             .....

                             ,"код ответа N" : "название ответа N" 

                             }

               ,"order" : [ -- порядок ответов

                           "код ответа 3" 

                          ,"код ответа 8" 

                          .....

                          ,"код ответа 4" 
                          ]

        ,"form" : {
           -- свойства вопроса при использовании анкеты в форме как анкеты для первичного приёма данных
           -- параметр необязателен
           -- при указании "form" в запросе, все параметры вопроса, связанные с ним, заменяются на новые указанные или удаляются
        }

-- изменение нескольких вопросов, любая ошибка отменяет все изменения. Будут изменены или все вопросы, или не все.

  ,obj : [
          { описание вопроса-1 }
         ,{ описание вопроса-2 }
         ,...............
         ]

  -- не обязательно

  ,"return_fresh_obj" : 0|1  -- вернуть новый объект анкета -- да (1), нет (0, по умолчанию)

}

ответ


{

    <общие поля>

 ,"obj" : { объект анкета как из ответа anketa.get } -- если "return_fresh_obj" : "1" 

-- в зависимости от вида параметра obj в вызове

 ,"id" : "id-добавленного вопроса" 

-- или

 ,"id" : [ -- порядок id соответствует порядку объектов в параметре obj в вызове
          "id-добавленного вопроса-1" 
         ,"id-добавленного вопроса-2" 
         .......
         ]
}

вверх

Удаление вопроса анкеты

Удаление нескольких вопросов транзакционно - удаляются или все (нет ошибок), или ни один (хоть одна ошибка).


{

     "action" : "anketa.quest.delete" 

    ,"anketa.id" : "уникальный идентификатор анкеты вопроса" 

    ,"id" : "уникальный идентификатор вопроса" 

     -- или

    ,"id" : [ "id1", "id2", ..... ]

--- необязательный

    ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ


{

    <общие поля>

}

если "return_fresh_obj" : "1" 

ответ -- как на запрос чтения "action" : "anketa.get" соответствующей анкеты

вверх

Изменение позиции вопроса анкеты


{

     "action" : "anketa.quest.order" 

     ,"anketa.id" : "уникальный идентификатор анкеты вопроса" 

     ,"order" : [ -- новый порядок вопросов

          "код-вопроса3" 

         ,"код-вопроса8" 

          .....

         ,"код-вопроса4" 

     ]

    -- необязательный

    ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ


{

    <общие поля>

}

если "return_fresh_obj" : "1" 

ответ -- как на запрос чтения "action" : "anketa.get" соответствующей анкеты

вверх

Удаление ответа вопроса анкеты

Для вопросов типа "1m" и "nm".


{

    "action" : "anketa.quest.response.delete" 

   ,"anketa.id" : "уникальный идентификатор анкеты вопроса" 

   ,"quest.id" : "уникальный идентификатор вопроса" 

   ,"id" : "уникальный идентификатор ответа" 

   -- необязательный

   ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ


{

    <общие поля>

}

если "return_fresh_obj" : "1" 

ответ -- как на запрос чтения "action" : "anketa.get" соответствующей анкеты

вверх

Изменение позиции ответа вопроса

Для вопросов типа "1m" и "nm".


{

    "action" : "anketa.quest.response.order" 

    ,"anketa.id" : "уникальный идентификатор анкеты вопроса" 

    ,"id" : "уникальный идентификатор вопроса" 

    ,"order" : [ -- новый порядок ответов

         "код-ответа3" 

        ,"код-ответа8" 

        .....

        ,"код-ответа4" 

              ]

    -- необязательный

    ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ


{

    <общие поля>

}

если "return_fresh_obj" : "1" 

ответ -- как на запрос чтения "action" : "anketa.get" соответствующей анкеты

Выпуски рассылки

Способы выпуска

данное api - выпуск массовых рассылок и индивидуальных cообщений через вызов issue.send данного общего api

шлюз smtp - выпуск индивидуальных писем через протокол SMTP. Для этого свяжитесь со Службой поддержки

потоковое api - выпуск массовых и индивидуальных писем через специализированное api. Для этого свяжитесь со Службой поддержки

Все способы выпуска имеют общую интегрированную статистику по доставкам, открытиям и переходам, доступную через данное общее api (вызов stat.uni).

Варианты выпуска рассылок

Все рассылки (email, sms, viber, push, vk, tg, vknotify) выпускаются через вызов issue.send.

Для выпуска веб-пуш (push) необходимо сначала собрать подписчиков на своём сайте, установив и настроив скрипты как описано в "Подписка на веб-пуш".

Для выпуска ВКонтакте (vk) необходимо сначала зарегистрировать сообщество через веб-интерфейс.

Для выпуска Телеграм (tg) необходимо сначала зарегистрировать бота через веб-интерфейс или напрямую через создание внешней авторизации.

Есть пять вариантов использования issue.send:

1) Выпуск по группе-списку - это выпуск по заранее определённому и сохранённому списку получателей, неизменному если вы его не измените сами.

2) Выпуск по группе-фильтру - это выпуск по динамически отбираемому каждый раз сегменту получателей. Например, "Те кто получил письмо за последние 24 часа и ни разу не кликнул" - условие отбора зависит от времени и каждый раз результат будет разный.

3) Экспресс-выпуск - это выпуск по списку с данными для персонализации без его внесения в базу. Список и данные персонализации задаются каждый раз в вызове или указывается откуда их забрать.

Экспресс-выпуск предназначен для рассылки общей для многих получателей информации. Разовые письма типа "Спасибо за регистрацию" или "Ваш код на сайте" это не Экспресс-выпуск, это транзакционные personal.

Типичная ошибка - использование Экспресс-выпуска для таких индивидуальных писем: система автоматически это замечает и такие выпуски получают очень низкий приоритет и выходят в последнюю очередь.

Если один Экспресс-выпуск - выпуск по списку на много адресов вы разделите на Экспресс-выпуск на маленькие порции этого спискаю, то получится только медленнее. Кроме описанного выше снижения приоритета, в каждый выпуск добавятся затраты на его запуск. Самое долгое - проверка на попадания в спам по нескольким сервисам - это занимает от 20 до 120 секунд и мало от нас зависит, так как это сторонние сервисы. В итоге вы получите такую проверку и затраты времени на неё столько раз, на сколько кусков разрежете один выпуск.

Существует возможность использования Экспресс-Выпуска, что бы разрешиаь третьим лицам выпуски без сообшения им адресов получаетелей.

- загрузите адреса в систему без создания подписчика (импорт с параметром no_member:1)

- создайте саблогин с ограниченными правами для своего подрядчика

- сообщайте ему только номера адресов - email.id - можно получить через stat.uni

- подрядчик осуществляет Экспресс-Выпуск использую номера адресов с добавлением нужных данных персонализации

4) "Выпуск по группе Экспресс-Импорта" - как Экспресс-Выпуск, но реестре получателей не указывается в задании на выпуск, а берётся загруженый в Экспресс-Импорте.

5) Транзакционные письма - это выпуск одному конкретному получателю с данными персонализации. Именно этот тип выпуска предназначен для писем вида "Спасибо за регистрацию" или "Ваш код на сайте". Для этих выпусков имеется отдельный механизм, который позволяет выпускать их очень быстро. Рекомендуется выпускать Транзакционные письма по заранее сохранённому черновику - это исключает некоторые проверки, что позволяет формировать письмо ещё быстрее.

Группа-спискок Группа-фильтр Экспресс-выпуск Транзакционное письмо
Адреса получателей Заранее созданные в базе список Динамический поиск по базе адресов данные которых удовлетворяют фильтру Указываются при выпуске или забираются по ссылке Указывается при выпуске
Данные для персонализации Из базы Из базы Указываются при выпуске Из базы если не указаны при выпуске
Количество переменных Не ограничено Не ограничено Не ограничено Не ограничено
Шаблонизатор Продвинутый Продвинутый Продвинутый Продвинутый
Собственные функции-плагины к шаблонизатору Да Да Да Да
Сложные вложенные структуры данных Да, в модели ДК Да, в моделе ДК Да Да
Подтверждение от владельца адреса Требуется Требуется Требуется Не обязательно
Разбор жалоб на спам Согласно договору Согласно договору Согласно договору С особым пристрастием
Лимит в месяц Не ограничено в пределах тарифа Не ограничено в пределах тарифа Не ограничено в пределах тарифа Первоначально равен максимально допустимому количеству адресов в базе. Далее в зависимости от репутации

Отправить тестовый выпуск

Вызов issue.send.test

Полный аналог "Отправить выпуск" issue.send за исключением того, что любой указанный sendwhen заменяется на test

Основная причина существования - иметь отдельное право на выпуск только тестов. Например, сотрудник "дизайнер" должен не мочь отправлять реальные выпуски (отбираем право issue.send), но тесты для себя должен быть отправить способен (оставляем ему право issue.send.test).

Отправить выпуск

Вызов issue.send

Этот вызов асинхронный - получение положительного ответа означает, что задание на рассылку поставлено в очередь. Используйте возвращаемый track.id для отслеживания выпуска.

Это не гарантирует выпуск - он может не состоятся по причинам, которые не проверить на момент вызова (например, недоступность на момент преобразования ссылки, для которой производится преобразование для учёта переходов).

Индивидуальные данные персонализации для каждого адреса берутся из сохранённых в базе (группа-список, группа-фильтр) или из указанных прямо при выпуске (Экспресс-выпуск, Транзакционные письма) и доступны через префикс anketa шаблонизатора.

Данные формата АВО автоматически доступны как преобразованные в формат ДК.

Данные персонализации для каждого получателя могут быть дополнены внешними данными (формат ДК) в процессе выпуска. Подробнее в разделе "Внешние данные для персонализации"

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

Для каждого сообщения (email, sms , viber, push, vk, tg, vknotify) система отслеживает статус доставки - доставлено/не доставлено (и с какой ошибкой), для каждой sms - дополнительно размер, предполагаемый оператор и стоимость доставки, для каждого vknotify - стоимость доставки. Это доступно через объект deliv вызова stat.uni (из этого следует что из deliv можно так же получить просто список участников выпуска).

Для каждого успешно доставленного email, viber, push система отслеживает факты открытия и факты перехода по каждой ссылке (если учёт переходов не был отключён при выпуске). Время каждого открытия/перехода и ip-адрес доступны через объекты read и click вызова stat.uni.

Дополнительно различаются открытия/переходы для html и amp-версий email-сообщений (read.source,click.source).

Также система отслеживает вид устройства, операционную систему, браузер и его версию, если произошло чтение или переход из письма. Для чтения также отслеживается его длительность. Это доступно через объект gadget вызова stat.uni.

При установке на страницах сайта специального счётчика, возможно отслеживание пути по сайту и достижения целевых страниц после перехода из письма - подробности в разделе "Целевые Страницы".

Каждое сообщение имеют уникальный идентификатор состоящий из номера выпуска и номера письма в выпуске. Поэтому вы можете получить описанную выше статистику с точностью до каждого email/sms/viber/push/vk/tg/vknotify каждого выпуска для каждого адресата.

Кроме системного уникального номера письма каждому письму можно присвоить клиентскую метку (custid), состоящую из одного или нескольких полей. Для этого настраивается список ключей данных, которые запоминаются в каждом выпуске для каждого получателя (отсутствующие в конкретном выпуске или пустые данные не в счёт). Они доступные через Универсальную статистику и позволяют присваивать письмам удобные для анализа клиента метки. Для настройки этой возможности обратитесь в Службу поддержки.

Благодаря поддержке нескольких идентификаторов у одного подписчика вам доступны разнообразные сценарии мультиканального взаимодействия с ним. Например, "Выслать письмо, а тем кто за 10 дней так и не прочёл и не сделал ни одного перехода, выслать смс".

При получении параметра выпуска из нескольких источников приоритет имеет параметр:
- указанный непосредственно в issue.send
- если параметр не указан или пуст, то параметр берётся из класса выпуска (issue.send, параметр class.id)
- если класс не указан, или параметр в нём не указан или пуст, то параметр берётся из черновика выпуска (issue.send, параметр draft.id)
- если черновик не указан, то берётся значение параметра по умолчанию
- если черновик указан, но параметр в нём не указан или пуст, то параметр берётся из класса черновика (issue.draft.set, параметр class.id)
- если класс не указан, или параметр в нём не указан или пуст, то берётся значение параметра по умолчанию

Исключение - так как параметр group обязателен в issue.send, то из другого источника он получен быть не может.

Исключение - параметр extra. Cуммируется по ключам первого уровня по всем источникам. Приоритет одноимённых ключей первого уровня как описано выше для параметров.

Исключение - список прикрепляемых файлов. Списки, заданные в черновике и выпуске, суммируются. Из двух одноимённых файлов приоритет у того, что указан в выпуске.

Содержимое выпуска из черновика - текст, тема, отправитель и прочее (кроме attaches) - полностью заменяют собой аналогичные параметры letter выпуска и имеет абсолютный приоритет.

Для того чтобы частично заменять в выпусках такие параметры черновика, используйте параметр weak_draft, указывая его одновременно с draft.id. Допустимые значения - пусто (отсутствует) - как по умолчанию в аккаунте (т.е. черновик сильнее параметров), 0 - черновик сильнее параметров, несмотря на настройку акканута, 1 - черновик слабее параметров, несмотря на настройку аккаунта и параметры содержимого выпуска, указанные в вызове заменяют аналогичные из черновика.

Транзакционные письма в течении календарных суток группируются в выпуски на основе номера черновка, использованного DKIM, номера формы или триггера (если письмо вызвано ими). Возможен альтернативный вариант - группировка только на основе первых трёх меток выпуска - настраивается через параметр issue.personal.groupby вызовы sys.settings.set


{
   "action" : "issue.send" или "issue.send.test",

   "name" : "Название выпуска", -- параметр необязателен, при отсутствии используется тема письма или имя отправителя sms
                                -- из-за особенностей учёта выходов Транзакционных писем personal данный параметр
                                -- для них будет работать не так, как ожидается и, в целом, бесполезен

   "label" : "метка" или [ "метка1", "метка2".. ], -- набор произвольных меток выпуска, позволяющих фильтровать/классифицировать
                                                   -- от 0 до 10 строк не длиннее 128 символов
                                                   -- параметр необязателен

* параметры содержимого выпуска

    "letter" : {

*** для sms

                * содержимое сообщения

                или указание параметров

                "from.name" : "Имя отправителя" -- обязателен и имя отправителя должно быть в списке уже промодерированных имён (вызовы issue.smssender.*)

               ,"message": {

                           "sms"  : "sms cообщение" 

                          }

               ,"basedraft.id" -- не обязательно. ни на что не влияет в _содержимом_, но выпуск записыватся как вышедший по нему. так же из него учитываются  всякие фильтры, списки отписки, списки участия и прочии параметры.

                или черновик, из которого их взять. Приоритет описан выше.

                "draft.id": "номер черновика, содержимое которого даст выпуск" -- должен иметь sms-версию

*** для viber

                * содержимое сообщения

                или указание параметров

                "button.url"   : "ссылка с кнопки" -- параметр обязателен, если нужна кнопка после сообщения

               ,"button.text" : "надпись на кнопке" -- параметр необязателен, по умолчанию "Узнать" 

               ,"message": {

                          "viber"  : "viber cообщение" -- текст без форматирования

                          }

               ,"attaches": [  -- не обязательно, одна и только одна ссылка на возможную картинку отображаемую между текстом и кнопкой

                              -- при выпуске по черновику, прикрепляемые файлы из черновика добавляются к указанным в списке
                              -- если одноимённый файл или ccылка тут отсутствуют

                             { "url"  : "Внешние ссылки http / https / ftp / ftps / sftp" },

                             -- или

                             { "url"  : "персонализированный url - для каждого свой" }, -- подробнее в разделе "Персонализация выпуска" 

                            ]

              ,"basedraft.id" -- не обязательно. ни на что не влияет в _содержимом_, но выпуск записыватся как вышедший по нему. так же из него учитываются  всякие фильтры, списки отписки, списки участия и прочии параметры.

                или черновик из которого их взять.  Приоритет описан выше.

                "draft.id": "номер черновика, содержимое которого даст выпуск" -- должен иметь viber-версию

 *** для push в браузеры

                * содержимое сообщения

                или указание параметров

                "subject" : "тема" -- обязательно

               ,"click.url"   : "ссылка для клика" 

               ,"message": {

                            "push"  : "текст cообщения" -- текст с переводами строк

                           }

               ,"icon.url" : "ссылка на иконку" --  https://, обычно 192х192.

               ,"image.url" : "ссылка на картинку" -- https://

               ,"require_interaction" : 0|1 -- для закрытия сообщения ожидать нажатия подписчиком. по умолчанию 1 - ожидать
                                            -- глобально для аккаунта можно изменить параметром issue.push.require_interaction
                                            -- вызова sys.settings.set

               ,"actions": [  -- не обязательно, одна или две кнопки описываемые структурой
                                   {
                                    "action" : "действие кнопки", -- ссылка https://
                                                                  -- или ключевое слово "close" - просто закрывает сообщение.
                                                                  -- или ключевое слово "clickurl" - при выпуске подставляется ссылка из параметра click.url

                                    "title" : "текст кнопки", -- рекомендация: до 36 символов.

                                    "icon" : "ссылка иконки кнопки" -- не обязательно
                                                                    -- https:// на том же сайте, где установлен service-worker, размер стороны 16-24 px
                                   }, ...
                               ]
               ,"basedraft.id" -- не обязательно. ни на что не влияет в _содержимом_, но выпуск записыватся как вышедший по нему. так же из него учитываются  всякие фильтры, списки отписки, списки участия и прочии параметры.

                или черновик из которого их взять.  Приоритет описан выше.

                "draft.id": "номер черновика, содержимое которого даст выпуск" -- должен иметь push-версию

*** для vk

                * содержимое сообщения

                или указание параметров

                "subject" : "тема" -- обязательно

               ,"message": {

                            "vk"  : "текст cообщения" -- текст, возможно с переводами строк
                                                        -- дополнительно распознаются команды:
                                                        --  * подстановки картинок <img src="ссылка">
                                                        --  * подстановки видео <video src="ссылка">
                                                        --  * разбиения текста на части <hr />
                                                        -- таким образом, в одном сообщении можно
                                                        -- скомбинировать разные части, которые обычно
                                                        -- отправляются по отдельности
                           }

               ,"basedraft.id" -- не обязательно. ни на что не влияет в _содержимом_, но выпуск записыватся как вышедший по нему. так же из него учитываются  всякие фильтры, списки отписки, списки участия и прочии параметры.

                или черновик из которого их взять.  Приоритет описан выше.

                "draft.id": "номер черновика, содержимое которого даст выпуск" -- должен иметь vk-версию

*** для tg

                * содержимое сообщения

                или указание параметров

                "subject" : "тема" -- обязательно

               ,"message": {

                            "tg"  : "текст cообщения" -- текст MarkdownV2
                                                        -- не забывайте правильно экранировать управляющие символы
                                                        --
                                                        -- для прикрепления/встраивания изображений и файлов используйте
                                                        --
                                                        -- ![описание](медиа-ссылка)
                                                        --
                                                        -- и они будут по порядку следования в тексте автоматически оформлены
                                                        -- соответствующими сообщениями Телеграм
                                                        -- "описание" в целом не обязательно. тип содержимого определяется по
                                                        -- расширению файла и может быть уточнён указанием
                                                        -- mediatype=document|animation|audio|video|voice|photo|venue
                                                        --
                                                        -- Ещё можно указать "Контакт" 
                                                        --
                                                        --          ![ИмяКонтакта](+номертелефона)
                                                        --
                                                        -- "Место встречи" (venue) и "Координаты" (location)
                                                        --
                                                        -- ![](59.972611, 30.311311)
                                                        -- ![SendSay office;SPb, Professora Popova, 23D](59.972612,30.311311?mediatype=location)
                                                        -- ![SendSay office;SPb, Professora Popova, 23D](https://yandex.ru/maps/2/saint-petersburg/?ll=30.311398%2C59.972617&mediatype=venue)
                                                        -- ![SendSay office;SPb, Professora Popova, 23D](https://www.google.com/maps/search/59.972612,30.311311?mediatype=venue)
                                                        -- ![SendSay office;SPb, Professora Popova, 23D](https://www.google.com/maps/place/59°58'21.4"N+30°18'40.7"E?mediatype=venue)
                                                        -- ![SendSay office;SPb, Professora Popova, 23D](https://google.ru/maps/@59.9723963,30.311142,18.5z?mediatype=venue)
                                                        --
                                                        --
                                                        -- особенность при подстановке значений с каким либо оформлением вокруг
                                                        -- например вот такое
                                                        --
                                                        -- _[% переменная %]_
                                                        --
                                                        -- при пустом значении переменной приведёт в Телеграм совсем не к тому, что ожидается
                                                        -- ошибка это или особенность, не известно
                                                        -- для обхода добавляйте пробел после начального или перед конечным управляющим символом
                                                        --
                                                        -- _[% переменная %] _
                                                        --                 ^^^ пробел добавлен
                           }

                ,"settings" : { -- не обязательные параметры для специальных вещей

                   -- Для всех деталей смотрите основную документацию Telegram
                   --
                   -- InlineKeyboardMarkup: https://core.telegram.org/bots/api#inlinekeyboardmarkup
                   --
                   -- ReplyKeyboardMarkup: https://core.telegram.org/bots/api#replykeyboardmarkup

                   --*-- Кнопка запроса гео-положения
                   --
                   -- ответ будет сохранён в ключи данных telegram.logitude и telegram.latitude
                   -- "telegram" : {
                   --        "longitude" : "30.12222",
                   --        "latitude" : "59.12345" 
                   --              }

                   "reply_markup" : { "keyboard" : [ [ { "text": "Сообщить, где я" ,"request_location": true } ] ] }

                   --*-- Кнопка запроса номера телефона
                   --
                   -- ответ будет сохранён в ключи данных telegram.phone_number, telegram.last_name, telegram.first_name
                   -- "telegram" : {
                   --         "phone_number" : "79110000000",
                   --         "last_name" : "Дед",
                   --         "first_name" : "Ленин" 
                   --              }

                   "reply_markup": { "keyboard" : [ [ { "text": "Сообщить телефон","request_contact": true } ] ], "one_time_keyboard":true }

                   --*-- Отображение клавиатуры типа ReplyKeyboardMarkup
                   --
                   -- Данные никак не обрабатываются (не считая передачи далее, если настроено проксирование).
                   -- Но если текст кнопки это команда боту, то она будет выполнена.

                   "reply_markup": { "keyboard" : [ [ { "text": "/help"}, {"text": "просто текст кнопки"} ] ], "one_time_keyboard":true} }

                   --*-- Удаление клавиатуры типа ReplyKeyboardMarkup

                   "reply_markup": { "remove_keyboard":true }

                   --*-- Клавиатура типа InlineKeyboardMarkup
                   --
                   -- Сразу после нажатия кнопки бот отвечает "Вы выбрали ТекстКнопки" с целью зафиксировать в чате выбранный ответ
                   --
                   -- Код кнопки сохраняется в указанном "datakey" 
                   --
                   -- Если в коде присутствуют команды, то они выполняются, а значение сохраняется без самой команды
                   -- Поиск и обработка команд производится стандартным способом
                   --
                   -- Для каждой кнопки клавиатуры формируется ссылка для учёта нажатий пользователя, как при нажатии на ссылку - считаются клики и это доступно в Универсальной статистике
                   --
                   -- Формат ссылки: tgkey://<имя бота>/?code=<код кнопки>&text=<текст кнопки>
                   --
                   -- Пример tgkey://Name_bot/?code=12345&text=Yes

                   "reply_markup": { "inline_keyboard" :
                                       [
                                         [ -- первый ряд клавиатуры
                                           { "text" :"Male" , "callback_data": "m" }  -- кнопка
                                          ,{ "text" :"Female" , "callback_data": "f" } -- другая кнопка
                                        ]
                                       ,[ -- второй ряд клавиатуры
                                           { "text" :"Kid" , "callback_data": "k" } -- ещё кнопка
                                          ,{ "text" :"Cat" , "callback_data": "c" } -- и ещё кнопка
                                       ]
                                       ....
                                      ]
                                  }
                  , "datakey": ["base.gender"] -- ключ данных для сохранения значения параметра callback_data нажатой кнопки
                 }

               ,"basedraft.id" -- не обязательно. ни на что не влияет в _содержимом_, но выпуск записыватся как вышедший по нему. так же из него учитываются  всякие фильтры, списки отписки, списки участия и прочии параметры.

                или черновик из которого взять текст и настройки.  Приоритет описан выше.

                "draft.id": "номер черновика, содержимое которого даст выпуск" -- должен иметь tg-версию

*** для vknotify

                Содержимое сообщений должно быть зараннее согласовано с VK !

                Только выпуски personal !

                * содержимое сообщения

                или указание параметров

                "from.name" : "Имя отправителя" -- обязателен

               ,"message": {

                           "vknotify"  : "vknotify cообщение" -- текст до 1000 символов

                          }

               ,"basedraft.id" -- не обязательно. ни на что не влияет в _содержимом_, но выпуск записыватся как вышедший по нему. так же из него учитываются  всякие фильтры, списки отписки, списки участия и прочии параметры.

                или черновик, из которого их взять. Приоритет описан выше.

                "draft.id": "номер черновика, содержимое которого даст выпуск" -- должен иметь vknotify-версию

*** для email

                * содержимое письма

                или указание параметров (дополнительно смотрите параметр letter.zip ниже

                "subject" : "Тема письма" -- обязательно не пусто

               ,"from.name" : "Имя отправителя" 

               ,"from.email" : "Адрес отправителя (email)" -- обязательно не пустой для текстовых писем; адрес должен быть в списке уже подтверждённых адресов (вызовы issue.emailsender.*)
                                                           --
                                                           -- Использование в качестве адреса отправителя адреса, принадлежащие Почте Mail.ru (mail.ru, bk.ru, inboc.ru, list.ru, mail.ua) - запрещено настройками Mail.Ru
                                                           -- Рассылка с таким адресом не выйдет. Черновик - не сохранится.
                                                           ---Более подробную информацию вы можете прочитать по адресу https://corp.mail.ru/ru/press/releases/9593/

               ,"reply.name" : "Имя для обратного адреса для ответа" 

               ,"reply.email" : "Обратный адрес для ответа (email)"  -- адрес должен быть в списке уже подтверждённых адресов (вызовы issue.emailsender.*)
                                                                     -- возможен учёт и статистика ответов получателей
                                                                     -- для этого необходимо активировать через Службу поддержки настройку "Перехват ответов на письма выпуска" 
                                                                     -- статистика доступна через deliv.replyed.* вызова stat.uni

               ,"to.name" : "Имя получателя" -- в этом поле уместна персонализация для подстановки имени и фамилии получателя

               ,"message": { -- одна или обе не пустые версии письма

                           "html" : "html-версия письма" 

                          ,"amp" : "amp-версия письма" -- подробнее про особенности работы можно прочитать в нашей статье
                                                       -- https://docs.sendsay.ru/email-campaigns/create-your-campaign/amp-campaign

                          ,"text" : "текстовая версия письма" 
                          }

               ,"basedraft.id" -- не обязательно. ни на что не влияет в _содержимом_, но выпуск записыватся как вышедший по нему. так же из него учитываются  всякие фильтры, списки отписки, списки участия и прочии параметры.

                или черновик из которого их взять.  Приоритет описан выше

                "draft.id": "номер черновика содержимое которого даст выпуск", -- должен иметь html и/или текстовую версию

                * генерация текстовой версии из html

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

                "autotext" : 0|1|ширина -- 0 - отключено (по умолчанию)
                                        -- 1 - включено, ширина строки 80 символов
                                        -- ширина - включено, ширина строки - указанное число

                * прикреплённые файлы письма

                -- по умолчанию, mime-тип файла определяется по расширению

                -- по умолчанию, кодировкой текстовых файлов text/* считается utf-8

                -- следующие имена файлов зарезервированы для внутренних нужд и не должны использоваться
                --
                --  все начинающиеся с _
                --
                --  message.text
                --  message.sms
                --  message.viber
                --  message.push
                --  message.vk
                --  message.tg
                --  message.vknotify
                --
                --  index.html
                --  index.htm
                --  index.shtml
                --  index.shtm
                --
                --  attach.html
                --  attach.htm
                --  attach.shtml
                --  attach.shtm
                --
                --  message.html
                --  message.htm
                --  message.shtml
                --  message.shtm

               ,"attaches": [

                              -- при выпуске по черновику, прикрепляемые файлы из черновика добавляются к указанным в списке
                              -- если одноимённый файл или ccылка тут отсутствуют

                             {
                              "name" : "имя файла",

                             ,"content": "содержимое файла закодированное base64",

                             ,"encoding" : "base64",

                             ,"mime-type" : "тип аттача", -- параметр необязателен, заменяет тип установленный по расширению имени аттача

                             ,"charset" : "набор символов аттача", -- параметр необязателен, заменяет используемое по умолчанию utf-8
                             },

                             {
                              "name" : "имя файла",

                             ,"content": "содержимое файла",

                             ,"mime-type" : "тип файла", -- параметр нобязателен, заменяет тип установленный по расширению имени файла

                             ,"charset" : "набор символов файла" -- не обязательно, заменяет используемое по умолчанию utf-8
                             },

                             {
                              "url"  : "url откуда забрать. Внешние ссылки http / https / ftp / ftps / sftp или из хранилища загрузок rfs://upload/путь-до-файла " 

                             ,"mime-type" : "тип аттача", -- не обязательно, заменяет тип установленный по расширению имени аттача или полученный в ответ на запрос

                             ,"charset" : "набор символов аттача", -- не обязательно, заменяет используемое по умолчанию utf-8 или полученный в ответ на запрос
                             },

                             { "url"  : "персонализированный url откуда забирать - для каждого свой" }, -- подробнее в разделе "Персонализация выпуска" 

                             -- генерация персонального pdf-документа на ходу. подробнее в разделе "Персонализация выпуска" 

                             { "pdf"  : "номер черновика"}

                             -- генерация персонального excel-документа на ходу. подробнее в разделе "Персонализация выпуска" 

                             { "xlsx"  : "номер черновика"}

                             ...

                            ]
               }

*  текст выпуска и картинки из архива (для email). не для Транзакционных выпусков

  -- В архиве ищется индексный файл, который даст текст выпуска - это первый регистронезависимый файл *.htm(l) с наименьшим уровнем вложенности и наименьшим среди одноуровневых именем.
  -- Остальное содержимое каталога, в котором он находится сохраняется в Хранилище картинок.
  -- Остальные части архива игнорируются.
  -- В содержимом индексного файла относительные ссылки в img-src и ссs-url() дополняются веб-базой каталога, куда было сохранено содержимое.
  -- Полученный результат записывается в  letter-> message->html (т.е. это будет email).
  -- Остальные параметры по-прежнему передаются через явно указанный в вызове letter (например, тема).
  -- Если в вызове уже есть letter и в нём есть есть message, то это ошибка.

  ,"letter.zip" : "содержимое архива закодированное в base64" --  zip-архив, содержащий текст выпуска (в utf-8) и сопровождающие картинки для оформления

* получатели выпуска

   ,"group" : "id группы | masssending - если это Экспресс-Выпуск | personal - Транзакционное письмо",
              --
              -- Eсли фильтр группы описан в формате КД и в нём используется оператор has с параметром save,
              -- то один участник группы может попасть в рассылку несколько раз в зависимости от количества
              -- срабатываний has/save и параметра выпуска "multiple".
              -- В описании фильтра формата КД описано когда и чем это полезно.

   -- Выпуск Транзакционного письма (personal) в каналах Телеграм и ВКонтакте не может быть произведён
   -- "просто так", необходимо указывать какой бот или сообщество использовать.
   -- Для этого при personal необходимо указать дополнительный параметр-подсказку в выпуске или в черновике (для выпуска из триггеров, например)

  ,"basegroup.id" : "группа бота или сообщества" -- tg_xxxx или vk_xxx

* параметры способа выпуска

   ,"sendwhen": "Когда выпустить (now - сейчас | later - отложенный | delay - задержанный | save - отложить на хранение | test - тестовый)",
                -- при использовании в "Действиях по расписанию" допустимы только now и test
                -- Тестовый выпуск не доступен для Экспресс-выпуска, Транзакционных писем
                -- Одновременно может быть не более 100 отложенных (later), задержанных (delay) и на хранении (delay) выпусков. Если этого мало, то свяжитесь со Службой поддержки.

   ,"weak_draft": "не обязательно. описано выше" 

   ,"dkim.id" : "номер проверенного dkim-ключа или 0" 
                 -- если строго "0", то будет использоваться общий системный ключ
                 -- если пусто/не задано/невалиден, то будет взят из черновика
                 -- если в черновике строго "0", то будет использоваться общий системный ключ
                 -- если в черновике не задан или на момент выпуска выбранный ключ из черновика окажется не проверенным,
                 ---- шаги только при выпуске по email 
                 --   то будет использоваться самый свежий из ключей имеющихся для домена отправителя (from.email из вызова или черновика)
                 --   если ключа точно по домену не будет, то домен будет сокращён на один уровеньт и поиск будет повторён. и так до домена второго уровня  включительно
                 --   если ключа по домену и его сокращённым вариантам не будет или на момент выпуска выбранный ключ по домену окажется не проверенным,
                 --   то будет искаться любой валидный DKIM для любого возможно настроенного поддомена для домена отправителя (это не стандартный шаг, если такой поиск нужен, то попросите Службу Поддержки его активировать)
                 --   если такой есть то будет использоваться самый свежий по времени настройки
                 ----- /конец шагов только при выпуске по email 
                 ---то если всё ещё ничего не найдено, то будет использоваться значение установленное через sys.settings.set
                 -- если и его нет или не проверено, то будет использоваться общий системный ключ
                 --
                 -- если dkim-ключ будет подобран, то он
                 -- при выпуске по email - даст клиентскую dkim-подпись письма и повляется на служебные адреса и служебные домены (если домен дкима кастомизированный - см. issue.dkim.*)
                 -- при выпуске не по email - только влияние на служебные домены (для учёта переходов, чтений, раздачи картинок)

   -- фильтр включения в выпуск - только адреса, попадающие под этот фильтр будут пропущены в выпуск
   -- в Экспресс-выпуске фильтр всегда работает по данным реестра, даже если получатель есть в базе как подписчик

   ,"issue_include_filter" : "код группы" 

    -- или

   ,"issue_include_filter" : [ фильтр как group.filter.set ]

   -- фильтр исключения из выпуска - адреса, попадающие под этот фильтр будут исключены из выпуска
   -- в Экспресс-выпуске фильтр всегда работает по данным реестра, даже если получатель есть в базе как подписчик

   ,"issue_exclude_filter" : "код группы" 

   -- или

   ,"issue_exclude_filter" : [ фильтр как group.filter.set ]

   -- Группы списки для исключения получателей

   ,"group.exclude" : [ "id-группы1", "id-группы2" .... ]
                     -- Если адрес получателя присутствует хотя бы в одной из указанных групп-списков,
                     -- то он исключается из выпуска
                     -- Это работает быстрее чем issue_exclude_filter
                     -- Эта возможность позволяет, например, реализовать "стоп-лист по тематикам" - в письме
                     -- размещается дополнительная ссылка "Не хочу получать эту тематику" ведущая на Форму
                     -- А в выпусках "этой тематики" указывается исключить группу-список "Заполнившие Форму" 

-- список попавших в выпуск

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

-- список отписавшихся из выпуска

   ,"unsub_list" : "код группы-списка отписавшихся из выпуска"  -- В указанную группу-список накапливаются адреса,
                                                                -- нажавшие в выпуске "ссылку тематической отписки" 
                                                                -- Каждый адрес вносится только один первый раз
                                                                -- При последующих выпусках по этому классу
                                                                -- участники - это группы-списка из него исключаются
                                                                -- Ссылка тематической отписки оформляется как [% param.url_unsub_topic %]
                                                                -- Также можно использовать ссылку "Отмены тематической отписки",
                                                                -- оформляемую как [% param.url_unsub_topic_cancel %]
                                                                -- Подписчики, нажавшие её в каком либо имеющимся у них старом письме
                                                                -- (новые они не получают из-за нахождения в группе-списке отписки),
                                                                -- удаляются из группы отписки и со следующего выпуска по этому
                                                                -- классу могут опять получать письма (если нет других препятствий)

-- игнорирование стоп-листа

   ,"ignore_stoplist" : 0|1                                   -- игнорировать пользовательский стоп-лист при выпуске
                                                              -- доступно только при заключении отдельного соглашения

   ,"class.id" : "идентификатор класса выпуска" -- возможный источник параметров выпуска для тех, что не указаны непосредственно в issue.send

   ,"campaign.id" : "код кампании" 

-- или

    ,"campaign.id" : [ "код кампании 1", "код кампании 2" ...]

                   -- Код кампании в которую будет включён выпуск
                   -- пусто или не указан - будет включён в кампанию, если она указана в черновике
                   -- 0 - не будет включён ни в какую кампанию, даже указанную в черновику
                   --     не может быть указан с другими кодами если их несколько

                   -- Код кампании (указанный или полученный из черновика) игнорирует если выпуск выходит
                   -- в рамках АБ-тестирования или триггерной рассылки

                   -- При успешном создании задания отложенного выпуска, он автоматически вносится в кампании указанные в campaign.id
                   -- При выходе отложенного выпуска ранее заданный campaign.id игнорируется и выпуск вносится только в те кампании,
                   -- в которых на момент выпуска состоит задание отложенного выпуска, сработавшее в кроне естественным путём (cron.runonce не считается)

* при отложенном выпуске (later) указывается дополнительно дата, час и минута, не ранее которых выпускать

    -- Если на такие же дату-время уже запланирован выпуск по такой же группе, то новое задание принято не будет.
    -- ИСКЛЮЧЕНИЕ - Экспресс-выпуск и Транзакционные письма.
    -- Выпуски по этим группам могут быть запланированы на одну и ту же дату в любом количестве.

    "later.time" : "YYYY-MM-DD hh:mm" -- рекомендуемая точность - 5 минут. Т.е. mm из набора 0,5,10,15,20,25,30,35,40,45,50,55
                                      -- другие значение mm допустимы, но фактически при выпуске могут быть округлены системой
                                      -- до ближайшего большего значения из указанного набора
                                      -- время не должно быть в прошлом

* при задержанном выпуске (delay) указывается дополнительно на сколько минут задержать выпуск.

    -- Так как "задержка" реализуется созданием отложенного выпуска на "текущее время + delay.time",
    -- то действуют ТАКИЕ ЖЕ ОГРАНИЧЕНИЯ, что и при использовании later.time

    "delay.time" : "mm" 

* для Email - при выпуске теста (test) указываются адреса получателей. не более пяти.

    "mca": [

            "e@mail.1",

            "e@mail.2",

             ......

           ]

* для Email - параметры преобразования ссылок для учёта перехода по ним в системе
            -- каждая ссылка в выпуске обсчитывается отдельно (даже повторяющиеся),
            -- но на это можно повлиять через параметр data-do-link-same

    "relink" : 0|1 -- Преобразовывать ссылки автоматически. По умолчанию - 1 - преобразовывать
                   -- 1 - да
                   -- 0 - нет
                   -- не указано, пусто, null - по умолчанию
                   --
                   -- при сочетании c relink из черновика/класса значения 0, 1 и пусто перебивают указание в черновике/классе,
                   -- а при отсутствии в вызове или указании null - значение из черновика/класса сильнее

    "relink.param" : {
                      -- если relink = 1, а какой то из параметров relink.param не указан,
                      -- то считается что: link=1 и test=1 соответственно для всех рассылок кроме personal
                      -- для personal это: link=1 и test=0 - объяснение ниже

                      "link" : 0|1 -- преобразовывать ссылки тега <A>
                                   -- 1 - да, 0 - нет
                                   -- преобразуются ссылки со схемами: http(s)://, ftp(s)://, viber://, fb-messenger://, skype://

                      "test": 0|1 -- проверять существование адресов (только если адрес подлежит преобразованию)
                                  -- 1 - да, 0 - нет
                                  -- при включённой проверке выпуск не выйдет если ссылка
                                  -- не действительна (ответ не 2xx, не 301, 302, 303, 307, 308 или 401 или тайм-аут 30 секунд)
                                  -- при положительных ответах 3хх дальнейшая проверка по пути редиректа не производится

                                  -- настройка "test"=1 не действует на домены vk.com, vkontake.ru, ok.ru, my.mail.ru, facebook.com, fb.com, instagram.com, twitter.com,
                                  -- pinterest.com, youtube.com, rutube.com, rutube.com, linked.in, odnoklassniki.ru, ok.ru, telegram.me, t.me, apps.apple.com,
                                  -- itunes.apple.com, play.google.com
                                  -- и их мобильные версии с префиксом "m" и версии с префиксом "www" 
                                  -- ссылки с их использованием не проверяются на существование из-за непредсказуемости результата
                                  -- указывайте таким ссылкам явно data-do-link-test=1 для осуществления проверки

                                  -- никогда не проверяются ссылки с персонализацией и со схемами viber://, fb-messenger://, skype://

                                  -- при выпуске А/B-тестирования всегда устанавливается test=0, так как такая проверка типичный источник
                                  -- проблем, когда одна часть теста выходит, а другая - нет
                                  -- указывайте ссылкам явно data-do-link-test=1 для осуществления проверки при А/B-тестировании

                                  -- Обратите внимание, что массовая отсылка выпусков Персональных gисем, может вызвать
                                  -- проблему выпуска из-за неудавшейся проверки адресов. Причина - система анализа частоты
                                  -- запросов сайта, для которой проверяется ссылка, может воспринять многократные постоянные
                                  -- запросы несколько подряд раз за короткое время (а именно так это будет выглядеть при массовой
                                  -- отсылке Персональных писем) как атаку на сайт и не давать проверить ссылку.
                                  -- Поэтому по умолчанию для personal проверка ссылок не производится.
                                  -- Включить её для всего выпуска вы всегда можете, задав параметр test в явном виде.
                                  -- Включить её для отдельных выпусков вы всегда можете атрибутом data-do-link-test

                      -- Глобальные настройки из relink.param можно переопределить для любого
                      -- конкретного тега A, указав в них нестандартные атрибуты
                      -- "data-do-link-relink" и "data-do-link-test", влияющие на преобразование
                      -- и тестирование по следующим правилам:
                      --
                      -- Атрибут отсутствует или пуст - учитывается глобальная настройка
                      -- Атрибут = 1 - делать действие, несмотря на глобальную настройку
                      -- Атрибут = 0 - не делать действие, несмотря на глобальную настройку
                      --
                      -- В отличие от параметра "test", зависящего от "link",
                      -- явное указание атрибута data-do-link-test="1" всегда вызывает проверку ссылки
                      -- несмотря на то, что преобразование для неё может и не проводиться
                      -- из-за настроек link/data-do-link-relink

                      -- несмотря на указание test=1, проверка не будет производиться, если через sys.settings.get
                      -- установлено issue.link.notest = 1 (по умолчанию = 0). Только явное указание в теге А параметра data-do-link-test = 1
                      -- заставит всё же провести проверку

                      -- В некоторых случаях например, блок товара с картинкой-названием-описанием с одной и той же ссылкой с каждого элемента,
                      -- может не требоваться различать, какую именно ссылку нажали (карта кликов становится проще).
                      -- Для того чтобы одинаковые ссылки получили в выпуске общий учёт переходов, надо явно указать в теге А
                      -  в атрибуте data-do-link-same="метка объединения", где "метка объединения" любое не пустое значение.
                      -- Одна и та же ссылка, встреченная в любом месте выпуска и имеющая одну и ту же метку объединения, будет использовать
                      -- один и тот же учёт переходов в этом выпуске.
                      -- Разные ссылки могут иметь одинаковую метку - ссылки будут объединяться, но каждая с такими же как она.
                      -- Метка нигде не запоминается - она только подсказывает при формировании выпуска, что ссылки различать не надо.
                     }

* для Viber - параметры преобразования ссылок для учёта перехода по ним

-- Аналогично как для Email, но действует только на ссылку button.link, поэтому не имеет варианта image и не применимы замечания про атрибуты data-do-relink-*

    "relink" : 0|1 -- Преобразовывать ссылки автоматически. По умолчанию - 1 - преобразовывать
                   -- 1 - да
                   -- 0 - нет
                   -- не указано, пусто, null - по умолчанию
                   --
                   -- при сочетании c relink из черновика/класса значения 0, 1 и пусто перебивают указание в черновике/классе
                   -- а при отсутствии в вызове или указании null - значение из черновика/класса сильнее

    "relink.param" : {
                      "link" : 0|1

                      "test" : 0|1
                     }

* для Push - параметры преобразования ссылок для учёта перехода по ним

-- Не имеет варианта image и не применимы замечания про атрибуты data-do-relink-*

    "relink" : 0|1 -- Преобразовывать ссылки автоматически. По умолчанию - 1 - преобразовывать
                   -- 1 - да
                   -- 0 - нет
                   -- не указано, пусто, null - по умолчанию
                   --
                   -- при сочетании c relink из черновика/класса значения 0, 1 и пусто перебивают указание в черновике/классе
                   -- а при отсутствии в вызове или указании null - значение из черновика/класса сильнее

    "relink.param" : {
                      "link" : 0|1  -- действует на click.url

                      "test": 0|1 -- действует на click,url, icon.url, image.url, actions.*.action
                     }

* для VK - параметры преобразования ссылок для учёта перехода по ним

-- Не имеет варианта image и не применимы замечания про атрибуты data-do-relink-*

    "relink" : 0|1 -- Преобразовывать ссылки автоматически. По умолчанию - 1 - преобразовывать
                   -- 1 - да
                   -- 0 - нет
                   -- не указано, пусто, null - по умолчанию

                   --
                   -- при сочетании c relink из черновика/класса значения 0, 1 и пусто перебивают указание в черновике/классе
                   -- а при отсутствии в вызове или указании null - значение из черновика/класса сильнее

    "relink.param" : {
                      "link" : 0|1

                      "test": 0|1
                     }

* для TG - параметры преобразования ссылок для учёта перехода по ним

-- Не имеет варианта image и не применимы замечания про атрибуты data-do-relink-*

    "relink" : 0|1 -- Преобразовывать ссылки автоматически. По умолчанию - 1 - преобразовывать
                   -- 1 - да
                   -- 0 - нет
                   -- не указано, пусто, null - по умолчанию

                   --
                   -- при сочетании c relink из черновика/класса значения 0, 1 и пусто перебивают указание в черновике/классе
                   -- а при отсутствии в вызове или указании null - значение из черновика/класса сильнее

                   -- не преобразуются ссылки домена t.me

    "relink.param" : {
                      "link" : 0|1

                      "test": 0|1
                     }

* для SMS - параметры преобразования ссылок для учёта перехода по ним

-- Ссылки преобразуются через сокращатель, что дополнительно снижает длину (и, значит, цену) сообщения
-- Доступна статистика переходов для тех, ссылок которыми воспользовались получатели

    "relink" : 0|1 -- Преобразовывать ссылки автоматически
                   -- 1 - да
                   -- 0 - нет
                   -- не указано, пусто, null - по умолчанию
                   --
                   -- По умолчанию 0 - в отличии от других форматов !
                   --
                   -- при сочетании c relink из черновика/класса значения 0, 1 и пусто перебивают указание в черновике/классе
                   -- а при отсутствии в вызове или указании null - значение из черновика/класса сильнее

    "relink.param" : {
                      "link" : 0|1 -- по умолчанию 0

                      "test" : 0|1 -- по умолчанию 0
                      }

* для Email - параметр преобразования ссылок для отслеживания перехода по ним с помощью сторонних сервисов

    "link.qsid": "Параметр для отслеживания переходов по ссылкам через сторонние сервисы" 
                  -- добавляется ко всем ссылкам через query-string
                  -- значение должно быть уже url-encoded
                  -- при выпуске по черновику, если этот параметр пуст, то берётся link.qsid из черновика
                  -- обычно содержит utm-метки и используется для отслеживания через сервисы типа Google Analitics
                  -- не имеет ни какого отношения к параметра relink*
                  -- если к какой-то ссылке параметр добавлять не требуется, то укажите в теге нестандартный
                  -- параметр data-no-qsid со значением 1

* для Viber - параметр преобразования ссылок для отслеживания перехода по ним с помощью сторонних сервисов
     -- Аналогично как и для Email, но действует только на ссылку button.link

    "link.qsid": "Параметр для отслеживания переходов по ссылкам через сторонние сервисы" 

* для Push - параметр преобразования ссылок для отслеживания перехода по ним с помощью сторонних сервисов
     -- Аналогично как и для Email, но действует только на ссылку click.url

    "link.qsid": "Параметр для отслеживания переходов по ссылкам через сторонние сервисы" 

* для VK - параметр преобразования ссылок для отслеживания перехода по ним с помощью сторонних сервисов
     -- Аналогично как и для Email

    "link.qsid": "Параметр для отслеживания переходов по ссылкам через сторонние сервисы" 

* для TG - параметр преобразования ссылок для отслеживания перехода по ним с помощью сторонних сервисов
     -- Аналогично как и для Email

    "link.qsid": "Параметр для отслеживания переходов по ссылкам через сторонние сервисы" 

* список получателей и данные персонализации для Экcпресс-выпуска

  -- подробнее в разделе "Форматы данных для импортирования и Экспресс-выпуска" 
  -- данные персонализации могут быть дополнены внешними данными в процессе выпуска
  -- подробнее в разделе "Внешние данные для персонализации" 

  -- Экспресс-выпуск предназначен для рассылки общей для многих получателей информации. Разовые письма типа "Спасибо за регистрацию" или "Ваш код на сайте" это не сюда, это Транзакционные personal.
  -- Типичная ошибка - это использование Экспресс-выпуска для таких индивидуальных писем - система автоматически это замечает, и такие выпуски получают очень низкий приоритет и выходят в последнюю очередь.

    "users.list": адреса и данные персонализации  -- непосредственно в СSV или XLSX
                                                  -- рекомендуется CSV - XLSX всё равно преобразуется внутри в CSV для того, чтобы подключить многопоточную обработку, а преобразование хоть и быстрое
                                                  -- (тысячи строк  с секунду), но на большом реестре всё равно займёт заметное время

или

  -- Экспресс-выпуск предназначен для рассылки общей для многих получателей информации. Разовые письма типа "Спасибо за регистрацию" или "Ваш код на сайте" это не сюда, это Транзакционные personal.
  -- Типичная ошибка - это использование Экспресс-выпуска для таких индивидуальных писем. Система автоматически это замечает, и такие выпуски получают очень низкий приоритет и выходят в последнюю очередь.

    "users.list" : {
                    "encoding" : "base64" 
                   ,"content" : "адреса и данные персонализации в base64" -- вариант для CSV и XLSX которые не получается передать в двоичном виде
                   },

или

  -- Экспресс-выпуск предназначен для рассылки общей для многих получателей информации. Разовые письма типа "Спасибо за регистрацию" или "Ваш код на сайте" это не сюда, это Транзакционные personal.
  -- Типичная ошибка - это использование Экспресс-выпуска для таких индивидуальных писем. Система автоматически это замечает, и такие выпуски получают очень низкий приоритет и выходят в последнюю очередь.

    "users.list" : [  -- данные в формате JSON-массив
                    { данные одного получателя }
                    ..............
                   ]

или

  -- Экспресс-выпуск предназначен для рассылки общей для многих получателей информации. Разовые письма типа "Спасибо за регистрацию" или "Ваш код на сайте" это не сюда, это Транзакционные personal.
  -- Типичная ошибка - это использование Экспресс-выпуска для таких индивидуальных писем. Система автоматически это замечает, и такие выпуски получают очень низкий приоритет и выходят в последнюю очередь.

    "users.list" : {  -- данные в формате JSON-объект-ABO
                    "caption" : [ описание столбцов ]
                   ,"rows" : [
                              [ данные одного получателя ]
                              ..............
                             ]
                   }

или

  -- Экспресс-выпуск предназначен для рассылки общей для многих получателей информации. Разовые письма типа "Спасибо за регистрацию" или "Ваш код на сайте" это не сюда, это Транзакционные personal.
  -- Типичная ошибка - это использование Экспресс-выпуска для таких индивидуальных писем. Система автоматически это замечает, и такие выпуски получают очень низкий приоритет и выходят в последнюю очередь.

    "users.url": "url"  -- по которому находятся список адресов с данными для персонализации
                        -- внешние ссылки http / https / ftp  / ftps / sftp или из хранилища загрузок rfs://upload/путь-до-файла

                        -- Для запросов по SOAP (например, к Siebel) схемы soap:// и soaps:// - обратитесь с Службу поддержки
                        -- для получения дополнительной информации

                        -- Для запросов из Google Big Query настройте внешнюю авторизация для схемы gbd:// (описано в вызовах authext.*)
                        -- Данные получаются полной выборкой, из указанной в url вида gbd://authext:AUTHEXT_ID@DATASET_ID/TABLE_ID таблицы.
                        -- Одна из колонок таблицы должна называться email - она послужит источником адресов получателей и доступно при персонализации текста письма под стандартным именем anketa.member.email.
                        -- Название остальных колонок неважно, но оно должно начитаться с латинской буквы и продолжаться латинскими буквами и цифрами.
                        -- Для использования данных в персонализации используйте подстановки вида [% anketa.t.НАЗВАНИЕ-КОЛОНКИ-ТАБЛИЦЫ %] для всех колонок таблицы кроме email.

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

   ,"users.url.remove" : "none|always|onerror|onok" -- параметр необязателен
                                                 -- применимо только к rfs:// и http(s)://
                                                 --    none - не удалять. По умолчанию
                                                 --    always - всегда
                                                 --    onerror - при завершении с ошибкой
                                                 --    onok - при завершении без ошибок

* параметры списка получателей для Экспресс-выпуска

    "only_unique": 0|1 -- Следить за уникальностью адресов в списке
                       -- 1 - да,  повторно встреченные адреса исключаются из выпуска
                       -- 0 - нет, повторно встреченные адреса участвуют в рассылке
* параметры объединения Экспресс-выпусков

    "accumulate" : 0|1 -- только для Экспресс-выпуска
                      -- если Экспресс-выпуск с такими же именем (параметр "name") когда-либо уже выпускался в текущем интервале накопления,
                      -- то этот запускаемый выпуск будет оформлен не как новый, а как продолжение уже вышедшего.
                      -- полезно в случае, если ваши CRM не может выдать весь реестр для рассылки за раз,
                      -- а только порциями.
                      -- с учётом параллельности приёма и выпуска заданий на рассылку, настоятельно рекомендуется
                      -- выпустить сначала первую порцию и потом уже последующие можно запускать параллельно

    "accumulate_by" : "month|week|day" -- интервал накопления. Параметр необязателен. По умолчанию month, если не изменено через sys.settings.set issue.accumulate_by
                                       -- календарная неделя, приходящаяся на два месяца, - это две разные недели для обеспечения правильного учёта месячного лимита трафика
                                       -- без разницы, какой именно интервал накопления был (и был ли вообще) у выпуска, который будет выбран базовым
                                       -- например, первого числа в понедельник выпуски с любым интервалом накопления будут использовать один и тот же выпуск с нужным именем как базовый,
                                       -- так как он будет удовлетворять и условию, что он в один и тот же день, и условию, что в одну и ту же неделю, и условию, что в один и тот же месяц

* учёт множественных совпадений итератор в фильтре

    "multiple" : 0|1 -- что делать с множественными совпадениями итератора
                     -- 0 - по умолчанию, выпустить одно письмо
                     -- 1 - по одному письму на совпадение
                     -- В описании фильтра формата КД описано когда и чем это полезно.

* получатель для Транзакционных писем

    "email" : "адрес получателя или номер телефона получателя" 
                                 -- данные для персонализации берутся из базы
                                 --
                                 -- данные персонализации могут быть дополнены внешними данными в процессе выпуска
                                 -- подробнее в разделе "Внешние данные для персонализации" 
                                 --
                                 -- при этом способе указания получателя он проверяется на возможность отсылки ему письма
                                 -- (синтаксическая верность адреса, стоп-лист, ошибки доставки, отписался) и вы сразу получите
                                 -- в ответ описание ошибки, если она есть
                                 -- при  других способах задания получателя, проверка на возможность выпуска происходит
                                 -- только при формировании рассылки

   "customer.id" : "клиентский идентификатор письма" -- параметр необязателен, до 255 байт
                                                     -- ваш идентификатор письма (например, номер вашего клиента)
                                                     -- возвращается в callback и доступен через Универсальную статистику stat.uni
                                                     -- доступен в шаблонизаторе как anketa.customer.id

или

    "users.list": один адрес и данные персонализации  -- непосредственно в JSON (два варианта) или в СSV или XLSX
                                                      -- подробнее в разделе "Форматы данных для импортирования и Экспресс-выпуска" 
                                                      -- при указании только адреса данные персонализации берутся из базы
                                                      -- при указании более чем одного адреса - ошибка выпуска
                                                      -- данные персонализации могут быть дополнены внешними данными в процессе выпуска
                                                      -- подробнее в разделе "Внешние данные для персонализации" 

* Ограничение на размер тиража

 -- параметр необязателен, по умолчанию - "100%" 

 ,"users.slice" : "число" - тираж составит не более "число" первых получателей. Число - целое число больше нуля

 или

 ,"users.slice" : "процент%" - тираж составит указанный процент от всех получателей. Процент - целое число больше нуля и меньше 101

* Ограничение количества контактов на получателя

 -- ограничивает число высылаемых сообщений для каждого получателя
 --
 -- если за последний interval получателю уже сформировано max и более сообщений, то он исключается из текущего выпуска
 --
 -- если у выпуска есть класс (указан или получен из черновика), то в расчёт лимита берутся только выпуски, вышедшие по такому же классу
 --
 -- если у выпуска нет класса , то в расчёт лимита берутся вообще все выпуски
 --
 -- должны быть заданы оба параметра сразу или ни одного
 --
 -- параметр необязателен, по умолчанию не ограничено

 ,"contact_rate" : {
                    "interval" : "часы"  -- за сколько последних часов проверять. Число от 1 или -1 за всё время
                    -- или
                    "interval" : "днейd" -- за сколько последних дней проверять. День начинается в 00:00:00. Число от 1 с окончанием "d" 
                                         -- например:
                                         -- 1d - за сегодня, т.е. с 00:00:00 сегодня
                                         -- 2d - за вчера и сегодня, т.е. с 00:00:00 вчера

                   ,"max"      : "число" -- лимит сообщений. число от 1
                  }

* Высылка письма в наиболее подходящее для каждого получателя время индивидуально на основе его прошлой активности

 -- для активации данной возможности просто обратитесь в Службу поддержки

 -- например,
 --   day и 4   - учитывая статистику дней недели, выбрать наилучшее время в ближайшие 4 часа
 --   week и 12 -учитывая суммарную статистику недели, выбрать наилучшее время в ближайшие 12 часа
 --
 -- если используется day и в указанном интервале вообще не было активности, то алгоритм переключается на week, при этом interval устанавливается в 24, если он был более 24
 --
 -- если в указанном интервале лучшими являются несколько часов, то выбирается самый ранний
 --
 -- не совместимо с выпуском по часовым поясам tz_observance
 --
 -- заданы оба параметра сразу или ни одного
 --
 -- параметр default необязателен и не учитывается без source и interval

 ,"tz_best" : {
               "source" : "day" или "week" -- лучшее время определять с учётом дня недели выпуска рассылки (day) или по суммарным данным всех дней недели (week). По умолчанию - day

              ,"interval" : число от 1 до 24 для week и от 1 до 168 для day -- число ближайших от выпуска часов, среди которых искать наилучший (считая и час выпуска). По умолчанию - 4

              ,"default"  -- от 00 до 23
                          -- час начала для тех, у кого время не определилось
                          -- параметр необязателен, по умолчанию - текущий час для выпуска "сейчас" 
                          -- или час запуска для отложенного выпуска - т.е. для них письма будут
                          -- отправляться сразу при выпуске
                          -- если час указан, то начнут отправлять им письма, начиная с указанного часа сегодня,
                          -- а если он уже полностью прошёл - с этого часа завтра
              }

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

  -- При совместном использовании с tz_observance, ограничение на частоту отправки действует независимо в каждом
  -- часовом поясе.

  -- Для SMS частота автоматически ограничивается в каждом часовом поясе отдельно и без tz_observance.

 ,"tz_limit" {

   "default" : {

      -- ограничение на частоту отправки сообщений
      -- за rate минут будет передано в доставку в не более number сообщений
      -- начиная со второго раза, number будет увеличиваться на increase каждый раз от текущего значения

      -- Если параметр отсутствует или пуст или обе величины равны 0,
      -- то ограничения нет.

      "batch" : { -- или отсутствуют или указаны оба значения
                 "number" : "сколько cообщений" -- обязательно, >= 200
                ,"rate"   : "за сколько минут"  -- обязательно, >= 0

                 -- не обязательно, по умолчанию 0

                ,"increase" : число -- увеличение number на указанное целое число, >= 0

                -- или

                ,"increase" : "число%" -- увеличение number на указанное число процентов, >= 0
                                       -- процент принимается с точностью до сотых
                                       -- при малых number, если наращивание даёт менее 1 получателя
                                       -- то принимается увеличение на 1 получателя
                                       -- процент равный нулю - отсутствие наращивания
                }

     }
  }

* Учёт часового пояса получателя

  -- Параметр необязателен.

  -- Для не SMS при отсутствии учёта часового пояса всё выходит сразу (если нет прочих настроек).

  -- Для SMS при отсутствии явно заданного учёта часового пояса он учитывает на основании номера телефона.

  -- Не совместимо с tz_best.

  -- При выпуске с учётом временной зоны, письма будут заранее сформированы, но их первая попытка
  -- доставки начнётся не ранее, чем во временной зоне получателя начнётся указанный час
  -- по его локальному времени.
  --
  -- Источником данных может быть один или несколько пунктов анкеты подписчика.
  --
  -- Берётся первое определившееся время по порядку указания источников.
  --
  -- Если часовой пояс не удалось определить ни по одному из источников, то письмо начинает
  -- доставляться как описано в default (с учётом tz_limit).
  --
  -- В качестве указания на часовой пояс понимаются названия большинства стран и крупных городов
  -- в английском и русском написании (регистр не важен).
  --
  -- А также числовые смещения относительно UTC в виде:
  --
  --           +hhmm  -hhmm  UTC+hhmm  UTC-hhmm
  --           +hh    -hh    UTC+hh    UTC-hh
  --           +h     -h     UTC+h     UTC-h
  --           hh
  --           h
  --
  -- Используйте вызов sys.settings.get, чтобы проверить понимает ли система ваш способ записи.
  -- Если нет - обратитесь в Службу поддержки за расширением нашего списка городов и стран.
  --
  -- Во избежание путаницы с пониманием часовых поясов, настоятельно рекомендуется планировать
  -- такие рассылки не менее чем за 24 часа и учитывать особенности России - текущий час в Москве
  -- по всех других часовых поясах страны (за исключением Калининграда) уже прошёл и настанет
  -- там через через 15-23 часа в зависимости от удалённости.

  -- При совместном использовании с tz_limit ограничение на частоту отправки действует независимо в каждом
  -- часовом поясе.

  -- Настройка может быть указана и будет учтена по убыванию приоритета: при выпуске, в классе выпуска, в черновике, в классе черновика, глобально (sys.settings.set)

  -- Если требуется отключить соблюдение местного времени, то в более приоритетном источнике необходимо задать tz_observance.source с не существующим ключом данных.

 ,"tz_observance" : { -- не обязательно

                    "hour" : "час" -- от 00 до 23
                                   -- час по местному времени для начала доставки писем
                                   -- не обязательно, по умолчанию - час из default

                   ,"source" : [ -- параметр обязателен. Источники данных о временной зоне подписчика в его анкете.
                                 -- один и более.
                                 -- рекомендуется заполнять и использовать base.tz - поле "Часовой пояс" анкеты "Базовая" 
                                 -- при отсутствии своих данных, можно использовать member.last.tz - автоматически
                                 -- обновляющееся системой значение на основании последнего клика или чтения
                                "ключ-данных" 
                               ,"ключ-данных" 
                               ,"ключ-данных" 
                               ........
                               ]

                   ,"default"  -- от 00 до 23
                               -- час начала для тех, у кого местное время не определилось
                               -- параметр необязателен, по умолчанию - текущий час для выпуска "сейчас" 
                               -- или час запуска для отложенного выпуска - т.е. для них письма будут
                               -- отправляться сразу при выпуске
                               -- если час указан, то письма начнут отправлять, начиная с указанного часа сегодня,
                               -- а если он уже полностью прошёл - с этого часа завтра
                   }

* время жизни сообщения для Email

 ,"ttl" : число -- время в секундах хранения сообщения при невозможности доставить сразу (ошибки доставки 4xx)
                -- параметр необязателен. По умолчанию 4 дня
                -- не рекомендуется задавать менее 1 часа. Если всё же попробуете - расскажите нам, как получилось.

* время жизни сообщения для Push

 ,"ttl" : число -- время в секундах хранения сообщения при невозможности отобразить сразу
                -- параметр необязателен. По умолчанию 12 часов

* забота о переменных

,"care_vars" : [ список ключей данных ] -- не обязательно
                                        -- указанные в списке переменные ProScript проверяются для каждого письмо на пусто-непусто
                                        -- если пусто, то письмо отменяется с причиной "care for var VAR" 
                                        -- проверяются только переменные название которых состоих из символов @a-zA-Z.[_]@
                                        -- cписок из выпуска имеют приоритет над черновиком
                                        -- cписок ни как не проверяется на использование или не использование его переменных в их тексте
                                        -- или наличие в данных персонализации
                                        -- проверка реализуется через специальный ProScript вставляемый без перевода строки перед началом
                                        -- текста сообщения. при успешной провеке он не оставляется после себя ни какого текста.

* данные произвольной структуры *одинаковые для всех подписчиков*

 -- эти данные трактуются в по модели КД

 -- эти данные будут доступны для шаблонизатора выпуска как переменные с именем, совпадающим с именем ключа хэша

 -- эти данные могут быть дополнены из черновика. указанные здесь имеют больший приоритет.

 -- эти данные могут быть дополнены внешними данными в процессе выпуска - подробнее в разделе "Внешние данные для персонализации" 

 -- в настоящий момент зарезервированы имена:
 --   anketa - данные о конкретном подписчике
 --   lenta  - данные для подстановки rss/atom каналов
 --   date   - интерфейс к функциям работы с датой и временем
 --   form   - форма опроса

 -- во избежание пересечений с новыми стандартными именами рекомендуется
 -- свои имена начинать с большой буквы

,"extra" : {
            "Key1" : [ 12, 34 ,56 ] -- пример использования [% Key1[2] %]

           ,"City" : { -- пример использования [% City.rus.long %]
                      "rus" : {
                               "long"  : "Санкт-Петербург" 
                              ,"short" : "СПБ" 
                     ,"eng" : {
                               "long"  : "Leningrad" 
                              ,"short" : "LED" 
                              }
                     }

           }

,"track.info" : "дополнительная информация, которая будет потом доступна в track.list/get" -- строка 1024 байта. Параметр необязателен

,"reltype" : ...
,"relref" : ...
}

ответ

{

    <общие поля>

   ,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*
                       --
                       -- также можно получать callback по достижению трекером конечного состояния - т.е. по завершения выпуска
                       --
                       -- после выпуска Транзакционного письма полученный письмом номер "letter" будет в отчёте track.get
                       -- для данного track.id. Используя letter в stat.uni, можно получить информацию по данному конкретному
                       -- Транзакционному письму.

}

Отложенные выпуски рассылок

Список отложенных выпусков


{

  "action" : "issue.later.list",

 ,"from" : "YYYY-MM-DD" -- от даты (не обязательно)

 ,"upto" : "YYYY-MM-DD" -- до даты (не обязательно)

 ,"group" : [ -- фильтр по группам (не обязательно)

               код-группы-1

              ,код-группы-2

               ...

             ]

 ,"channel" :  "email|sms|viber|push|vk|tg|vknotify"  -- фильтр по каналу выпуска (не обязательно).

 ,"draft" : номер черновика" -- фильтр по черновику (необязательно)
                             -- параметр отсутствует - не фильтровать
                             -- параметр присутствует, но пустой - все выпуски не по черновикам

}

ответ


{

  <общие поля>

 ,"list" :  [

              {

                 "id" : номер выпуска

                ,"create.date" : "YYYY-MM-DD hh:mm:ss" -- дата постановки задания

                ,"track.id" : "номер трекера выпуска" 

                ,"status" : "статус задания" -- hold  - отложено на хранение
                                             -- later - отложено для выпуска
                                             -- now   - выпуск начал выходить
                                             -- error - ошибка выпуска

                -- если статус "отложено из-за ошибки" 

                ,"status.reason" : код ошибки

                -- если статус "отложено для выпуска" 

                ,"later.time" : "YYYY-MM-DD hh:mm" -- запланированная дата выпуска

-- базовые параметры выпуска

                ,"name" : название выпуска

                ,"label" : [ ... ] -- метки выпуска

                ,"group" : код группы для которой отложен выпуск

                ,"letter" : {
                              "draft.id" : код черновика используемого для выпуска

                             ,"subject" : "тема выпуска" -- для sms совпадает с именем отправителя
                             ,"from.email" : адрес отправителя
                             ,"from.name" : имя отправителя
                            }

                 ,"channel" : "канал выпуска" 

                 ,"users.slice" : ...
                 ,"tz_best" : ...
                 ,"tz_limit" : ...
                 ,"tz_observance" : ...

                 ,"reltype" : ...
                 ,"relref" : ...

                ,"thumbnail" : [ список ссылок, аналогично issue.later.get ]
              },

             ...

            ]

}

Прочитать отложенный выпуск


{
 "action" : "issue.later.get",

 "id" : номер выпуска,
}

ответ


{

  <общие поля>

 ,"obj" : {
                 "id" : номер выпуска

                ,"create.date" : "YYYY-MM-DD hh:mm:ss" -- дата постановки задания

                ,"track.id" : "номер трекера выпуска" 

                ,"status" : "статус задания" -- hold  - отложено на хранение
                                             -- later - отложено для выпуска
                                             -- now   - выпуск начал выходить
                                             -- error - ошибка выпуска

                -- если статус "отложено из-за ошибки" 
                ,"status.reason" : код ошибки

                ,"track.id" : номер - номер асинхронного запроса выданный выпуску при issue.send

                <......... параметры issue.send использованные при задании выпуска .......>

-- все эти параметры совпадают c issue.send, и значения obj можно использовать для создания нового выпуска
-- необходимо будет добавить action
-- уточнить значения sendwhen и later.time
-- и настоятельно рекомендуется удалить "лишнее" : id, create.date, status, status.reason, format, name, issue.date,
-- но для просто смены даты выпуска или отсылки немедленно используйте специализированный вызов issue.later.send
-- для изменения прочих параметров выпуска есть вызов issue.later.set

-- при выпуске по черновику перед вызовом issue.send необходимо проверить параметры
-- subject, from.name и from.email черновика, так как они необязательны в черновике,
-- но обязательны при выпуске

           -- ссылки на изображения черновика
           -- только для html черновиков
           -- в настоящий момент поддерживаются картинки 800x600
           -- надо учитывать что ссылка может вести на отсутствующее изображение
           -- например, из-за сбоя при её формировании

           ,"thumbnail" : [
                          {
                           "url" : "ссылка на изображение" 
                          ,"width" : ширина в пикселах,
                          ,"height" : высота в пикселах
                          },

                         ...............

                         ]
         }
}

Изменение даты выхода отложенного выпуска

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

Исключение - Экспресс-выпуск и Транзакционные письма. Выпуски по этим группам могут быть запланированы на одну и ту же дату в любом количестве.


{

 "action" : "issue.later.send",

 "id" : номер выпуска,

-- или

 "issue.date" : "YYYY-MM-DD hh:mm" -- Новая дата выпуска. Дата должна быть в будущем
                                   -- Если параметр отсутствует, то выпуск выйдет сразу

-- или

 "issue.date"  : "now" -- выпустить сейчас

-- или

 "issue.date"  : "hold" -- отложить на хранение. Выпуск не выйдет, пока не будет задана новая дата выпуска

}

ответ


{

  <общие поля>

}

Изменение отложенного выпуска

Атомарно заменяет отложенный выпуск полностью.

Для просто изменения даты выхода есть issue.later.send

Параметр sendwhen должен быть later, delay или save.

{
 "action" : "issue.later.set" 

,"id" : номер отложенного выпуска

.... параметры как issue.send ....
}

ответ

{
 -- как issue.send, кроме track.id который не меняется
}

Удаление отложенного выпуска

Трекер выпуска остаётся и помечается как "Действие отменено".


{

 "action" : "issue.later.delete",

 "id" : номер отложенного выпуска

}

ответ


{

  <общие поля>

}

Управление текущими выпусками рассылок

Список формирующихся и доставляющихся рассылок

{
 "action" : "issue.running.list" 
}

ответ

{
  <общие поля>

  "list" : [
      {
         "id" : "идентификатор задания",

         "issue.id" : "номер выпуска если уже назначен",

         "gid" : "код группы" 

         -- состояние формирования выпуска

         "processing" : 1, -- выпуск формируется

         "track.id" : "номер трекера, если назначен",

         "track.status" : "состояние трекера, если известно",

          -- состояние доставки выпуска

         "delivering" : 1, -- выпуск доставляется

         "later" : "дата и время" -- запланированное время запуска последней
                                  -- части растянутого выпуска (Ys)

         "paused" : 1, -- в доставке есть приостановленный пользователем тираж

         "spam" : 1, -- в доставке есть тираж, приостановленный из-за спама
      },

     ...
     ]
}

Приостановить выпуск рассылки

Приостанавливается формирование и доставка всех имеющихся на момент вызова выпусков или указанного одного выпуска.

Вызов делает буквально то, что описано. Новые выпуски будут продолжать приниматься и выпускаться. Для влияния на них используйте issue.paused вызова sys.settings.get/set.

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

{
 "action" : "issue.running.pause" 

-- одно из

 ,"id" : "all" -- все

-- или

 ,"id" : "идентификатор задания" -- как в issue.running.list
}

ответ

{
  <общие поля>
}

Возобновить выпуск рассылки

Возобновляется формирование и доставка всех имеющихся приостановленных на момент вызова выпусков или указанного одного приостановленного выпуска.

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

{
 "action" : "issue.running.resume" 

-- одно из

 ,"id" : "all" -- все

-- или

 ,"id" : "идентификатор задания" -- как в issue.running.list
}

ответ

{
  <общие поля>
}

Досрочно начать выпуск всех отложенных частей рассылки

Досрочно начинается доставка всех отложенных частей (неважно на какое время) для всех имеющихся на момент вызова выпусков или указанного одного выпуска.

{
 "action" : "issue.running.start" 

-- одно из

 ,"id" : "all" -- все

-- или

 ,"id" : "идентификатор задания" -- как в issue.running.list
}

ответ

{
  <общие поля>
}

Прекратить выпуск рассылки

Прекращается без возможности возобновления формирование и доставка всех имеющихся на момент вызова выпусков или указанного одного выпуска.

Трекер становится "Отменено".

{
 "action" : "issue.running.delete" 

-- одно из

 ,"id" : "all" -- все

-- или

 ,"id" : "идентификатор задания" -- как в issue.running.list
}

ответ

{
  <общие поля>
}

Настройка отправителей рассылок

Список адресов email-отправителей

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

При каждом вызове issue.emailsender.set на используемый в нём адрес высылается письмо со ссылкой подтверждения.

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

Если адрес используемый как "адрес отправителя" или "обратный адрес" не добавлен в список отправителей вообще, то его можно использовать, если его домен точно равен домену подтверждённого DKIM, используемого в выпуске. Это упрощает работу, когда адресов отправителей много или они генерируются автоматически по какому-то правилу.

{

  "action" : "issue.emailsender.list" 

}

ответ

{

 <общие поля>

,"list" : [

            {

             "id" : уникальный идентификатор

            ,"name" : "адрес отправителя" 

            ,"label" : "имя отправителя" 

            ,"onmoderation" : 0|1 -- 0 - адрес одобрен, 1 - адрес ждёт подтверждения

            ,"create.date" : "дата и время создания" -- Ys, null

            ,"update.date" : "дата и время последнего изменения" -- Ys, null
            }

            ...

           ]

}

Чтение адреса email-отправителя

{

  "action" : "issue.emailsender.get" 

  ,"id" : идентификатор

}

ответ

{
  <общие поля>

  "obj" : {

            ,"id" : идентификатор

            ,"name" : "адрес отправителя" 

            ,"label" : "имя отправителя" 

            ,"onmoderation" : 0|1

            ,"create.date" : "дата и время создания" -- Ys, null

            ,"update.date" : "дата и время последнего изменения" -- Ys, null
          }

}

Создание или изменение адреса email-отправителя

{
  "action" : "issue.emailsender.set" 

  ,"obj" : {

             "name" : "адрес отправителя" -- обязательно при создании

            ,"label" : "имя отправителя" -- необязательно

           }

  -- необязательные

  ,"id" : идентификатор -- если не указан, создается новый

  ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ

{

 <общие поля>

 ,"id" : идентификатор отправителя

 ,obj  { ... } -- объект в формате issue.emailsender.get если "return_fresh_obj" : 1

}

Удаление адреса email-отправителя

{

  "action" : "issue.emailsender.delete" 

  ,"id" : идентификатор

}

ответ

{

 <общие поля>

}

Список имён sms-отправителей

{

  "action" : "issue.smssender.list" 

}

ответ

{

 <общие поля>

,"list" : [

            {

             "id" : уникальный идентификатор

            ,"name" : "отправитель" 

            ,"onmoderation" : 0|1 -- 0 - имя одобрено, 1 - имя ещё на модерации

            ,"create.date" : "дата и время создания" -- Ys, null

            ,"update.date" : "дата и время последнего изменения" -- Ys, null
            }

            ...

           ]

}

Чтение имени sms-отправителя

{

  "action" : "issue.smssender.get" 

  ,"id" : идентификатор sms-отправителя

}

ответ

{
  <общие поля>

  "obj" : {

             "id" : идентификатор sms-отправителя

            ,"name" : "отправитель" 

            ,"onmoderation" : 0|1

            ,"create.date" : "дата и время создания" -- Ys, null

            ,"update.date" : "дата и время последнего изменения" -- Ys, null
          }

}

Создание или изменение имени sms-отправителя

Создание или изменение имени автоматически попадает на модерацию.

Если имя состоит из одних цифр, то его длина не должна превышать 15 символов.

Иначе имя может содержать от 1 до 13 символов больших и маленьких латинских букв и цифр.

{
  "action" : "issue.smssender.set" 

  ,"obj" : {

       "name" : "отправитель" 

           }

  -- необязательные

 ,"id" : идентификатор sms-отправителя -- если не указан, создается новый

 ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ

{

 <общие поля>

 ,"id" : идентификатор sms-отправителя

 ,"obj" : { ... } -- объект в формате issue.smssender.get если "return_fresh_obj" : 1

}

Удаление имени sms-отправителя

{

  "action" : "issue.smssender.delete" 

 ,"id" : идентификатор sms-отправителя

}

ответ

{

 <общие поля>

}

Мультиканальные выпуски

Мультиканальный выпуск позволяет выпускать рассылку с учётом наличия у подписчика идентификаторов email и телефона в их различных сочетаниях.

Типичная задача - "Выпустить в данной группе письмо для тех, у кого указан email. Если email-а нет, но есть телефон, то тогда послать viber, а если нет, то тогда послать sms" (вы экономите sms, отправляя вместо него письмо и viber тем, кому это возможно). Другая задача - "Выпустить в данной группе по всем телефонам и email-ам" (вы оповещаете подписчиков максимально широким способом).

Мультиканальный выпуск происходит по одной группе (по одному и тому же списку при Экспресс-выпуске), но по разным каналам доставки в зависимости от указанного способа выпуска и наличия у получателя email и/или номер телефона.

При Экспресс-выпуске для указания наличия у получателя email, используйте колонку с названием member.email, а для sms - member.cellphone, для прочих типов - по коду типа (например, member.tg). Обе колонки должны присутствовать одновременно. Это отличается от привычного для обычного выпуска или импорта правила, что только в реестре может быть только одна такая колонка одновременно.

Используйте пустое значение в соответствующей ячейке, если у получателя нет адреса или телефона.

Выпустить мультивыпуск

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

Параметры оставленные без описания совпадают с одноимёнными из issue.send

{
 "action" : "issue.send.multi" 

 ,"name" : "название мультивыпуска" -- параметр необязателен
                                    -- из-за особенностей учёта выходов Транзакционных писем personal данный параметр
                                    -- для них будет работать не так как ожидается и, в целом, бесполезен

 ,"group" :

 ,"sendwhen":

 ,"group.exclude" :

 ,"campaign.id" :

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

 ,"delay.time" :

 ,"mca":

 ,"users.slice" :

 ,"users.list" :

 ,"users.url" :

 ,"users.url.remove" :

-- автоподбор для personal

 ,"email" : "адрес получателя" 
          -- если тип указанного адреса не подходит под какой-то из указанных в issue канал доставки,
          -- то будут взяты все идентификаторы (головы), связанные с данным адресом и из них
          -- выбран первый подходящий по типу и доступный для рассылки
          -- если ничего подходящего нет, то данный канал доставки будет пропущен

-- точное указание соответствия для personal

 ,"email" : [ "адрес получателя" , "адрес получателя" , "адрес получателя" ... ]
           -- адреса будут использованы по порядку по одному для каждого соответствующего канала из issue
           -- если тип указанного адреса не подходит под свой канал доставки или не доступен для рассылки,
           -- то данный канал доставки будет пропущен

 ,"only_unique" :

 ,"multiple" :

 ,"extra" :

 "issue" : [ -- два или более элемента описывающие режим и текст сообщения для используемых каналов доставки

   { -- email

    "mode" : "all" или "rest" -- режим выпуска.
                              -- all  - по всем кто может получать данный канал
                              -- rest - по тем кто не попал в предыдущие каналы и могут получать данный
                              --
                              -- необязательно для первого элемента "issue" - для него всегда all
                              -- обязательно для второго и последующих

    ,"letter" : {
                 "draft.id" 

                 --------------

                 ,"subject" :

                 ,"from.name" :

                 ,"from.email" :

                 ,"reply.name" :

                 ,"reply.email" :

                 ,"to.name" :

                 ,"message" :

                 ,"attaches" :

                 ,"autotext" :
                }

    ,"dkim.id" :

    ,"relink" :

    ,"relink.param" :

    ,"link.qsid":

    ,"tz_limit" 

    ,"tz_observance" 
   }

  ,{ -- viber

    "mode" : "all" или "rest" 

    ,"letter" : {
                 "draft.id" 

                 --------------

                "button.url"   :

               ,"button.text" :

               ,"message" :

               ,"attaches":

    ,"relink" :

    ,"relink.param" :

    ,"link.qsid":

    ,"tz_limit" 

    ,"tz_observance" 
   }

  ,{ -- sms

    "mode" : "all" или "rest" 

    ,"letter" : {
                 "draft.id" 

                 --------------

                ,"from.name" 

                ,message : { }
                }

    ,"tz_limit" 

    ,"tz_observance" 
   }

 ]

}

ответ

{

 <общие поля>

 "id" : номер мульти-выпуска -- если номер выпуска null, то это означает, что при выпуске personal c указанием получателя в "email" ни одному каналу доставки
                             -- не удалось подобрать адрес получателя. Следовательно мультивыпуск не создан, так как не может быть выпущена ни одна рассылка.
                             -- причины по каждому каналу перечислены в "list" 

,"list" : [ -- информация по каждому элементу "issue" в том же порядке как заданы в запросе

           {
            "channel"  : "email|sms|viber" -- канал выпуска

           ,"track.id" : "номер трекера созданного  выпуска для данного канала" 
           }

           -- или

           {
            "channel"  : "email|sms|viber" -- канал выпуска

           ,"errors" :  [ ... ] -- возможно при выпуске personal
                                -- означает, что для данного канала выпуска не нашлось подходящего идентификатора получателя
                                -- или все найденные не доступны для рассылки или в режиме rest и без него нашли что выпускать
                                -- и, следовательно, для данного канала выпуск рассылки не был запущен вообще
           }

          ]

}

Специфические для вызова ошибки

Общая фатальная ошибка - данный канал указан в issue повторно. N - индекс в issue

{ "id" : "wrong_arg", "explain" : "issue/channel", "detail" : N }

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

{ "id" : "channel/excluded", "explain" : "rest" }

Cписок мультивыпусков

{

 "action" : "issue.multi.list" 

}

ответ

{

  <общие поля>

 ,"list" : [

             {

                "id"   : идентификатор мультивыпуска

               ,"name" : "название мультивыпуска" 

               ,"dt"   : "дата мультивыпуска" 

               ,"group" : "группа мультивыпуска" 

             }

             ...

           ]

}

Чтение мультивыпуска

{

 "action" : "issue.multi.get" 

 ,"id" : идентификатор мультивыпуска

}

ответ

{

  <общие поля>

 ,"obj" : {

                "id"   : идентификатор мультивыпуска

               ,"name" : "название мультивыпуска" 

               ,"dt"   : "дата мультивыпуска" 

               ,"group" : "группа мультивыпуска" 

               ,"issue" : [
                           -- описание выпусков вышедших в рамках данного мультивыпуска
                           {
                             "id" : номер выпуска

                            ,"dt" : "дата выпуска (Ys)" 

                            ,"channel" : "email|sms|viber" -- канал выпуска

                            ,"members" : "число получателей" 
                            },

                           ........

                          ]

           }
}

Персонализация выпуска

Данные для персонализации

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

Для Экспресс-выпуска данные для персонализации указываются совместно со списком получателей.

Для Транзакционных писем данные могут быть взяты или из хранимых в системе или указаны при выпуске.

Для прочих рассылок данные персонализации берутся из сохранённых в системе при импорте подписчиков (member.import) и индивидуально внесённых для отдельных адресов (member.set).

Для управления персонализацией создан язык ProScript - простой, но эффективный язык персонализации с богатыми возможностями.

Для простой подстановки данных воспользуйтесь командами подстановки [% anketa.ключ-данных %].

В руководстве о ProScript, размещённом на сайте https://sendsay.ru, описаны более продвинутые возможности - условные операторы, циклы, вызовы функций, работа с датой и временем.

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

Также при выпуске можно указать уникальные для всего выпуска данные для персонализации - параметр выпуска extra.

Раздача промокодов

Для подстановки промокода в выпуск используются команда подстановки [% get_promocode() %]
или [% get_promocode("url") %]

Если url не задан, то коды берутся из файла promocodes.txt который должен быть загружен в корень раздела "Загрузки"

Для максимального быстродействия рекомендуется размещать файлы с кодами в разделе "Загрузки" - получить их оттуда значительно быстрее чем сходить наружу по https-ссылке.

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

Повторное использование в шаблоне подстановки кода с теми же параметрами допустимо - даст тот же код.

Если подстановка кода внутри IF, который НЕ сработал, то код всё равно расходуется.

При использовании команды подстановки и отсутствии файла выпуск не выйдет.

Одна строка файла воспринимается как один промокод (хоть даже с пробелами внутри).

Пустые строки игнорируются.

Строки, начинающиеся с # игнорируются.

Коды длиннее 64 байт игнорируются.

Коды одноразовые.

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

Если получателю не достанется промокода - он отсеивается из выпуска с кодом доставки -100019

Статистика раздачи кодов доступна в Универсальной статистике через объект promocode.

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

Если письмо не было доставлено, то промокод остаётся выданным.

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

Персональные Excel-документы

Каждый получатель выпуска рассылки может иметь в письме лично для него сформированные и персонализированные его данными электронные таблицы Excel.

Для этого при выпуске просто указывается, что требуется прикреплять индивидуальные Excel-файлы, и на базе какого html-черновика их строить.

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

Правила формирования

Из персонализированного html-кода черновика извлекается каждая таблица и размещается в одной таблице на лист документа.

Таблицы со всеми пустыми ячейками игнорируются.

Вложенные таблицы игнорируются.

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

Свойствами документа, листа, строки и ячейки можно управлять через атрибуты data-xlsx-* тегов table/tr/th/td как описано далее.

Свойства документа

Свойства документа устанавливаются в любом теге table. Повторное указание свойства в другой таблице заменяет предыдущее.

data-xlsx-title - заголовок

data-xlsx-subject - тема

data-xlsx-author - автор

data-xlsx-manager - менеджер

data-xlsx-company - компания

data-xlsx-category - категория

data-xlsx-keywords - ключевые слова

data-xlsx-comments - комментарий

data-xlsx-status - статус

Свойства листа

Свойства листа устанавливаются в теге table

data-xlsx-name - название листа (по умолчанию порядковый номер таблицы с единицы)

data-xlsx-infocus - 1 если этот лист должен быть отображён при открытии документа. По умолчанию - первый лист

data-xlsx-selection - пусто или координаты ячейки, которая будет выбрана при открытии документа (два числа через запятую - строка, столбец. счёт от нуля)

data-xlsx-autofilter - пусто или диапазоны выделения для автофильтра (диапазон - две координаты через запятую) т.е. строка чисел через запятую, которая разбивается на четвёрки (диапазоны), которые разбиваются на пары (координаты). Пример: data-xlsx-autofilter="2,3,6,8,34,6,38,9" - два диапазона: от 2,3 до 6,8 и от 34,6 до 38,9

Свойства строки

Свойства строки устанавливаются в теге tr.

data-xlsx-row_height - высота строки (по умолчанию - автоподбор)

Свойства ячейки

Свойства ячейки устанавливаются в тегах th и td.

data-xlsx-contents - если задано, то заменяет содержимое, полученное из html-ячейки

data-xlsx-type - тип ячейки. String (по умолчанию), number, data_type. Для data_type содержимое должно быть вида YYYY-MM-DD hh:mm:ss

data-xlsx-display_format - формат отображения для number и data_type. Для data_type по умолчанию "DD/MM/YYYY HH:MM:SS"

data-xlsx-formula - формула для вычисления содержимого ячейки при просмотре документа

data-xlsx-сolumn_width - ширина строки (по умолчанию - автоподбор)

data-xlsx-format-bold - жирный шрифт (1, по умолчанию для th) или нет (0, по умолчанию для tr)

data-xlsx-format-italic - наклонный шрифт (1) или нет (0, по умолчанию)

data-xlsx-format-color - цвет шрифта. Прямое указание вида "#RRGGBB" или одно из black, white, red, lime, blue, yellow, magenta, cyan, brown, green, navy, purple, silver, gray, pink, orange

data-xlsx-format-size - размер шрифта

data-xlsx-format-align - горизонтальное выравнивание содержимого ячейки: none (по умолчанию), left, center, right, fill, justify, center_across

data-xlsx-format-valign - вертикальное выравнивание содержимого ячейки: bottom (по умолчанию), top, vcenter, vjustify

data-xlsx-format-text_wrap - разрешить (1) перенос длинного текста или нет (0, по умолчанию)

data-xlsx-format-pattern - фон ячейки: none (по умолчанию), solid, medium_gray, dark_gray.light_gray, dark_horizontal, dark_vertical, dark_down, dark_up, dark_grid, dark_trellis, light_horizontal, light_vertical, light_down, light_up, light_grid, light_trellis, gray_125, gray_0625

data-xlsx-format-fg_color - цвет переднего плана ячейки (имеет смысл, только если pattern не none)

data-xlsx-format-bg_color - цвет фона ячейки (имеет смысл, только если pattern не none)

data-xlsx-format-border - четыре стиля бордюра для сторон ячейки через запятую в порядке лево, право, верх, низ: none, thin, medium, dashed, dotted. thick, double, hair, medium_dashed, dash_dot, medium_dash_dot, dash_dot_dot, medium_dash_dot_dot, slant_dash_dot

data-xlsx-format-border_color - четыре цвета бордюра для сторон ячеек через запятую

Персональные PDF-документы

Каждый получатель выпуска рассылки может иметь в письме лично для него сформированные и персонализированные его данными pdf-файлы.

Для этого при выпуске просто указывается, что требуется прикреплять индивидуальные pdf, и на базе какого html-черновика их строить.

Система автоматически сгенерирует из черновика персонализированный документ и превратит его в уникальный для каждого pdf-файл.

Для подключения этой возможности обратитесь в Службу поддержки.

Персональные прикрепляемые файлы

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

Такая ссылка различается по наличию команд(ы) ProScript в ссылке на прикрепляемый файл.

Например

http://test.ru/secret/file/[% anketa.member.email %].png

Система автоматически получит каждый файл и прикрепит его к нужному письму.

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

Если невозможность получить файл должна приводить к отмене высылки письма, то начните url в со знака "!".

!http://test.ru/secret/file/[% anketa.member.email %].png

Тогда при ошибке письмо выслано не будет, а в доставке получит статус -4 - письмо отменено.

Если начать url с "!?", то всё будет как и при "!", но если url будет полностью пустым, то это не будет ошибкой и такой файл будет проигнорирован.

Для подключения этой возможности обратитесь в Службу поддержки.

Обратите внимание, что:

Вставка QR-кода

В текст письма можно вставить штрих-код.

Для вставки QR-кода используйте функцию шаблонизатора qr_code().

[% qr_code(txt, p) %]

где

txt - текст для генерации QR-кода

p - настройки, параметр необязателен

Например

[% p = { "Ecc" => "L", "ModuleSize" => 5 } %]

<img src="[% qr_code(anketa.info.qrvalue, p) %]"/>

Настройки QR code

Version - размер изображения в точках QR-кода. От этого зависит сколько байт информации поместится на изображении. По умолчанию 0 - автоопределение. Таблица допустимых значений размера в точках и сколько помещается в байтах приведена ниже. Не забывайте, что не-ASCII символы занимают более одного байта (например, русская буква - это два байта)

Ecc - уровень коррекции ошибок. 'M', 'L', 'H' or 'Q. по умолчанию 'M'

ModuleSize - размер блока - количество пикселей на элементарную точку кода QR (количество точек определяет Version). По умолчанию 4

Version Точек QR Сколько бит информации позволяет закодировать Максимальное количество байт которое можно закодировать
1 21x21 112 14
2 25x25 208 26
3 29x29 336 42
4 33x33 496 62
5 37x37 672 84
6 41x41 848 106
7 45x45 976 122
8 49x49 1216 152
9 53x53 1440 180
10 57x57 1704 213
11 61x61 2008 251
12 65x65 2296 287
13 69x69 2648 331
14 73x73 2896 362
15 77x77 3296 412
16 81x81 3600 450
17 85x85 4032 504
18 89x89 4480 560
19 93x93 4992 624
20 97x97 5328 666
21 101x101 5688 711
22 105x105 6232 779
23 109x109 6856 857
24 113x113 7288 911
25 117x117 7976 997
26 121x121 8472 1059
27 125x125 9000 1125
28 129x129 9520 1190
29 133x133 10112 1264
30 137x137 10960 1370
31 141x141 11616 1452
32 145x145 12304 1538
33 149x149 13024 1628
34 153x153 13776 1722
35 157x157 14472 1809
36 161x161 15288 1911
37 165x165 15912 1989
38 169x169 16792 2099
39 173x173 17704 2213
40 177x177 18648 2331

Вставка штрих-кода

В текст письма можно вставить штрих-код формата Code128.

Такой штрих-код используется на многих платёжных документах и понимается платёжными терминалами.

Для вставки штрих-кода используйте функцию шаблонизатора barcode_code128().


[% barcode_code128(код) %]

[% barcode_code128(код, параметр1, значение1, параметр2, значение2, ... ) %]

Пример с настройками по умолчанию проверенный на реальных платёжных терминалах.


<img src="[% barcode_code128(anketa.barcode.T) %]" alt="[% anketa.barcode.T %]" width="280">

где anketa.barcode.T - это переменная, содержащая код для штрих-кода. Замените её имя на настоящее, используемое у вас.

Вы можете влиять на внешний вид штрих-кода с помощью следующих параметров:

 width            (автоматически) - Ширина изображения в пикселах
 height           (автоматически) - Высота изображения в пикселах
 border           (0)             - Размер чёрного бордюра вокруг штрих-кода
 scale            (1)             - Масштаб одного штриха кода в пикселах
 font             ("medium")      - Шрифт текста ("giant", "large", "medium", "small", "tiny")
 show_text        (1)             - Повторить цифры штрих-кода текстом под изображением
 font_margin      (2)             - Отступ в пикселах вокруг текста
 font_align       ("center")      - Выравнивание текста ("left", "right", "center")
 top_margin       (2)             - Отступ в пикселах выше штрих-кода
 bottom_margin    (2)             - Отступ в пикселах ниже штрих-кода и текста
 left_margin      (5)             - Отступ в пикселах ниже штрих-кода и текста
 right_margin     (5)             - Отступ в пикселах ниже штрих-кода и текста
 padding          (20)            - Размер в пикселах белой рамки до и после штрих-код

Внешние данные персонализации выпуска

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

Для персонализации выпуска данные из анкет подписчиков и данные общих параметров (extra) можно дополнить данными из внешнего источника, получаемыми динамически по ходу выпуска рассылки.

Это реализуется путём использования специальной функций шаблонизатора в тексте и/или шаблоне выпуска или указанием в специальном ключе параметра выпуска extra:

Кроме указания в тексте выпуска, возможно указать, какие внешние данные подключить прямо в api-вывзове выпуска рассылки. Это описано ниже.

Указание в шаблоне

[% external_anketa("url","ignore_error","0|1","timeout","секунды","dk","dk1 dk2 dk3") %]

если ответ json

[% external_extra("url","ignore_error","0|1","timeout","секунды","prefix","префикс") %]

[% external_extra("url","method","get","ignore_error","0|1","timeout","секунды","prefix","префикс") %]

если ответ csv

[% external_extra("url","format","csv","ignore_error","0|1","timeout","секунды","prefix","префикс") %]

[% external_extra("url","format","csv","method","get","ignore_error","0|1","timeout","секунды","prefix","префикс") %]

Кавычки у всех параметров обязательны.

Сами параметры (кроме url) - необязательны

- ignore_error - игнорировать или нет ошибки обработки. По умолчанию любая ошибка прекращает формирование выпуска рассылки.

- timeout - время на получение ответа. по умолчанию 3 минуты, максимально - 10 минут.

- prefix - полученные данные добавляются в общие параметры не как есть, а под указанным ключjм.
для json по умолчанию пусто - добавляются как есть
для csv по умолчанию "csv" - добавляются под ключом csv

- method - http-метод вызова. По умолчанию post, так как передаются данные о том, что за выпуск формируется. Возможно указать "get"

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

Указание при выпуске в extra

Кроме указания в тексте выпуска, возможно указать, какие внешние данные подключить прямо в api-вывзове выпуска рассылки.

{
 "action" : "issue.send" 

 .....

 "extra" : {

  ..........

            --- указание дополнительных ссылок для внешних данных для общих параметров
            --- аналогично функции шаблонизатора текста выпуска [% external_extra() %]
            --- однако обрабатывает до него и можно использовать шаблонизатор
            --- доступны данные "extra" с уже добавленным туда ключjм param содержащим информацию о выпуске
            --  + туда с обработкой с каждого url добавляются его данные
            --- т.е. при обработке url-1 для шаблонизатора доступны данные issue.send:extra,
            --- а при обработке url-2 для шаблонизатора уже доступны данные issue.send:extra + данные url-1

            "external_extra" : [

                     {
                      "url" : "урл-1" 
                     ,"method" : ..
                     ,"ignore_error" : ...
                     -- и прочие возможные параметры external_extra()
                     -- обязателен так же только url
                     }

                    ,{
                      "url" : "урл-2" 
                      .....
                     }

                     .....
             ]

  ..........

            --- указание дополнительных ссылок для внешних анкетных данных
            --- аналогично функции шаблонизатора текста выпуска [% external_anketa() %]
            --- и по порядку вызов идут до url, полученных из external_anketa()

            "external_anketa" : [

                     {
                      "url" : "урл-1" 
                     ,"method" : ..
                     ,"ignore_error" : ...
                     и прочие возможные параметры external_extra()
                     }

                    ,{
                      "url" : "урл-2" 
                      .....
                     }

                     .....
             ]

 }
}

Вызов получения внешних данных

Вызовы указанных адресов производятся методом POST с передачей cgi-параметра request, содержащего json-utf8 данные о текущем выпуске.

Ошибка получения данных, или разбора полученных данных, или не соответствия типа json фатальна - выпуск прекращается.

Это можно отменить параметром "ignore_error","1".

При вызове передаются (если не указан метод get) следующие данные, позволяющие идентифицировать, что это за выпуск:

 {
  "some.id" : "......." - некая строка до 32 символов, однозначно идентифицирующая выпуск (это НЕ номер выпуска, который он получит позже)

 ,"login" : "..." - аккаунт, для которого формируется выпуск

 ,"group.id" : "..." - код группы, для которой формируется выпуск

 ,"track.id" : 123 - номер трекера для track.get, присвоенный выпуску

 ,"draft.id" : 123 - номер черновика, на основе которого формируется выпуск или null

 ,"class.id" : 123 - номер классы выпуска

 ,"seq.id" :  123 - номер последовательности, вызвавшей этот выпуск или null

 ,"variant.id" : 123 - номер варианта А/Б-тестирования, вызвавшего этот выпуск или null

 ,"form.id" : 123 - номер формы, вызвавшей этот выпуск или null

 ,"campaign.id" : 123 - номер кампании, в рамках которой выходит этот выпуск или null

 ,"extra":  { ... } -- данные параметра extra вызова issue.send или null

- только для external_anketa

  -- без параметра dk

 ,"users.list" : [
                  список адресов, для которых ожидаются дополнительные анкетные данные
                 ]

  -- c параметром dk

  -- данные персонализации для адресов для ключей данных, указанных в dk,
  -- позволяет производить более подробный анализ на стороне клиента
  -- (например, выдавать промокоды не всем в списке, а только в зависимости от какой-то переменной)

  -- в dk через пробел перечисляются ключ данных, информация по которым интересует
  -- не забывайте ключ member.email для получения собственно адреса :)

 ,"users.list" : [
                  [ "email1-dk1-data", "email1-dk2-data", ... ]
                  [ "email2-dk1-data", "email2-dk2-data", ... ]
                 ,[ ... ]
                  .........
                 ]
 }

Если вам не нужны данные о текущем выпуске, то можно указать использование метода get.

Ответ в json

В ответ ожидается json-utf8, описывающий объект.

Этот объект будет включён в общие параметры выпуска или непосредственно, или под ключом, указанным в параметре "prefix".

Ответ в csv

Указывается параметром "format":"csv".

В ответ ожидается csv в utf8.

Результат-массив будет включён в общие параметры выпуска под ключом "csv", если иной не указан в параметре "prefix".

Первая строка ответа очень похожа на такую же строку в импорте подписчиков и разбирается как строка-заголовок, содержащая ключи данных для каждой колонки. Остальные строки - данные для формирования объектов, одна строка один объект.
В отличии от импорта ключи данных не ограничены глубиной два ("x.y") и не проверяются на соответствие имеющимся кодам анкет и кодам ответов этих анкет.

Разделитель колонок подбирается автоматически.

a|b.c|b.d
1|2|3
4|5|6

получится

"csv" : [
         {
           "a" : 1
          ,"b" :
             {
              "c" : 2
             ,"d" : 3
             }
         }
         {
           "a" : 4
          ,"b" :
             {
              "c" : 5
             ,"d" : 6
             }
         }
]

Дополнение общих параметров

Общие параметры, указываемые в ключе extra вызова issue.send, дополняются результатами обработки external_extra().

В выпуске может быть несколько использований external_extra() - все они будут обработаны один раз перед началом формирования писем по порядку расположения в тексте. Место расположения в данный момент неважно. Дополненные параметры действуют с самого начала текста.

Результаты всех external_extra() сначала объединяются между собой по ключам первого уровня. Более поздние данные имеют приоритет и заменяют собой имеющиеся.

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

Пример для выпуска с двумя вызовами:

данные extra из issue.send

{
 "nol" : "ноль" 
}

данные первого вызова external_extra()

{
 "odin" : "адын" 

,"dva" : "ещё адын" 

,"abv" : {
          "a" : "А" 
         ,"b" : "Б" 
         ,"v" : "В" 
         }

,"gde" : {
          "g" : "Г" 
         ,"d" : "Д" 
         ,"e" : "Е" 
         }
}

данные второго вызова external_extra()

{
 "nol" : "два" 

,"dva" : "два" 

,"gde" : {
          "х" : "Х" 
         }
}

результат

{
 "nol" : "ноль"       -- данные их extra приоритетнее

,"odin" : "адын"      -- нет во втором вызове external_extra()

,"dva" : "два"        -- есть во втором вызове external_extra()

,"abv" : {            -- нет во втором вызове external_extra()
          "a" : "А" 
         ,"b" : "Б" 
         ,"v" : "В" 
         }

,"gde" : {            -- есть во втором вызове external_extra() - просто замена без попыток объединения ключей
          "х" : "Х" 
         }
}

Дополнение анкетных данных

Не работает в тестовых копиях.

Анкетные данные пользователей дополняются результатами обработки external_anketa().

В выпуске может быть несколько использований external_anketa() - все они будет обработаны один раз перед началом формирования писем по порядку расположения в тексте. Место расположения в данный момент не важно. Дополненные параметры действуют с самого начала текста.

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

Однако ошибка при получении данных для первой группы фатальна и выпуск прекращается.

Для одного выпуска вызовов каждого external_anketa() может быть несколько и они могут поступать одновременно - в зависимости от количества получателей выпуск формируется в несколько потоков, а получатели обрабатываются порциями. В данный момент обычный размер порции составляет 1000 адресов.

В параметре users.list передаются адреса текущей группы получателей или указанные данные на основе списка из параметра dk.

Ответ должен быть json-массивом содержащим json-объекты или null.

Внешние данные для пользователя могут быть любой структуры - не обязательно двухуровневые объекты как у анкетных данных.

Данные приписываются пользователю по порядку их расположения в массиве - первому адресу в users.list добавляются данные первого элемента массива ответа, второму - второго и так далее.

Элементом массива может быть null - соответствующий получатель не получит дополнительных данных.

Массив в ответе может быть короче исходного - оставшиеся адреса не получат дополнительных данных.

Массив в ответе может быть длиннее исходного - лишние элементы игнорируются.

Пример:


users.list                       ответ

[                                [
 "1@test.ru"                      { данные для 1@test.ru }

,"2@test.ru"                     ,null -- нет данных для 2@test.ru

,"3@test.ru"                      { данные для 3@test.ru }
                                 ]
,"4@test.ru"                      -- нет данных для 4@test.ru так как ответ короче
]

Результаты всех external_anketa() для каждого получателя сначала объединяются между собой по ключам первого уровня. Более поздние данные имеют приоритет и заменяют собой имеющиеся.

Итоговый результат объединяется по ключам второго уровня с данными персонализации получателя. Данные уже имеющиеся в анкете менее приоритетны и заменятся внешними при их наличии. Внешние данные для анкеты member игнорируются.

Пример для выпуска:

данные анкеты одного пользователя

{
 "member" : {
             "email" : "test@test.ru" 
            ,"id"    : 123
            }

,"info" : {
           "firstname" : "Имя" 
          ,"lastname"  : "Фамилия" 
          }

,"param" : {
            "raz" : "Раз" 
            "dva" : "Два" 
           }
}

данные вызова external_anketa() для этого пользователя

{

 "member" : {
             "email" : "xxxx@test.ru" 
            }

,"info" : {
          ,"lastname"  : "фамилиЁ" 
          }

,"param" : [ "Три", "Четыре" ]

,"secret" : "12345" 
}

результат

{
 "member" : {
             "email" : "test@test.ru"  -- не заменено, так как member не изменяем
            ,"id"    : 123
            }

,"info" : {
           "firstname" : "Имя" 
          ,"lastname"  : "фамилиЁ" -- внешние данные при объединение объектов по ключам второго уровня
          }

,"param" : [ "Три", "Четыре" ] -- полностью заменено данными первого уровня так как внешние данные не объект

,"secret" : "12345" -- новые внешние данные
}

вверх

Данные о товарах Яндекс.Маркет (формат YML)

Если у вас есть данные о ваших товарах в формате YML (Яндекс.Маркет), то вы можете сделать их доступными в выпуске одной командой

[% external_extra("url","format","yml") %] -- простой вариант когда в выпуск подключается один YML

[% external_extra("url","format","yml","prefix","xxx") %]  -- когда в выпуск подключается больше одного YML их надо снабдить разными префиксами

(url может быть задан только константой)

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

В резлуьтате ваши данные о товарах станут доступны в шаблонизаторе с префиксом yml.

Каждый конкретный товар будет доступен как

[% yml.<id>.* %] -- если prefix не был указан

[% yml.xxx.<id>.* %] -- если указан не пустой prefix (в примере xxx)

где <id> - параметр id тега offer описывающего товар

все значения параметров тега offer и значения всех тегов внутри offer доступны как одноимённые параметры товара

все значения тега param доступны как yml.<id>.param.<p>

где <p> транслитерированное значение параметра name тега param

Товары у пустым url при импорте игнорируются

Дополнительно секция категорий

Категории товаров доступны через

[% yml.CATEGORY.<id>.* %]

где <id> - параметр id тега category описывающего категории рубрикатора товаров

Дополнительно секция подарков

Описание подарков доступно через

[% yml.GIFT.<id>.* %]

где <id> - параметр id тега gift описывающего подарки

Дополнительно секция акций

Описание акций доступно через

[% yml.PROMO.<id>.* %]

где <id> - параметр id тега promo описывающего акцию

Пример одного товара подробнее

<offer id="123" type="vendor.model" available="true" bid="1" group_id="136010368">
  <url>http://www.xxxxxx.ru/xxxx</url>
  <price>1749.0000</price>
  <currencyId>RUR</currencyId>
  <caategoryId>490</categoryId>
  <market_category>Дом и дача/Дом и интерьер/Текстиль/Шторы</market_category>
  <picture>http://media.xxxxx.xx/products/641by641/13/60/10/XXXXXXXXXXXXXX.jpg</picture>
  <delivery>true</delivery>
  <local_delivery_cost>0</local_delivery_cost>
  <typePrefix>Шторы, занавески</typePrefix>
  <vendor>La Interieurs</vendor>
  <vendorCode>136010368</vendorCode>
  <model>Занавеска с вышивкой по низу</model>
  <description>- Качество VALEUR SURE. Качественная отделка. Со сборкой 60 мм (3 варианта высоты). 91% полиэстера, 9% льна. Красивая вышивка по низу. Простои уход: стирка при 40°, не нужно гладить. Расстояние от отделки до низа 30 см (для размеров 240 и 260 см). Размер в см.</description>
  <sales_notes>Минимальный заказ 1500 руб</sales_notes>
  <manufacturer_warranty>true</manufacturer_warranty>
  <param name="country_of_origin">Франция</param>
  <param name="Пол">OTHER</param>
  <param name="Возраст">OTHER</param>
  <param name="Цвет">белый</param>
  <param name="Размер" unit="FR">240 x 175 см</param>
</offer>

Будет доступен через yml.123

Его параметр type тего offer через yml.123.type

[% id = "123" %]
[% yml.$id.type %]

Его значение тега model через yml.123.model

[% id = "123" %]
[% yml.$id.type %]

Его значение Размер через yml.123.param.Razmer

[% id = "123" %]
[% yml.$id.param.Razmer %]

Его значение дополнительного параметра unit у параметра Размер через yml.123.param.Razmer_unit

[% id = "123" %]
[% yml.$id.param.Razmer_unit %]

Пример структуры со всеми секциями

{
   "299337" : {
      "available" : "false",
      "categoryId" : "40",
      "currencyId" : "RUR",
      "delivery" : "true",
      "delivery-options" : {
         "option" : {
            "cost" : "0",
            "days" : "32" 
         }
      },
      "description" : "\n",
      "id" : "299337",
      "manufacturer_warranty" : "true",
      "model" : "Galaxy S10 8/128Gb",
      "param" : {
         "DiagonalEkrana" : "6.1ʺ",
         "EmkostAkkumulyatora" : "3400мАч",
         "KolichestvoYader" : "8",
         "Razreshenie" : "Quad HD+",
         "StandartySotovoySvyazi" : "LTE",
         "Tip" : "Изогнутый с двух сторон",
         "Tsvet" : "Аквамарин" 
      },
      "pickup" : "false",
      "picture" : "https://shop.mts.ru/upload/iblock/06a/s10green.jpg/resize/1500x1500/",
      "price" : "68990",
      "sales_notes" : "Кэшбэк 3500 через сервис МТС Cashback",
      "store" : "false",
      "type" : "vendor.model",
      "typePrefix" : "Смартфон",
      "url" : "https://shop.mts.ru/product/smartfon-samsung-g973-galaxy-s10-8-128gb-akvamarin",
      "vendor" : "Samsung" 
   },

   "CATEGORY" : {
      "11" : {
         "name" : "Смартфоны",
         "parent" : null,
         "partnum" : "11",
         "url" : null
      },
      "40" : {
         "name" : "Samsung",
         "parent" : "11",
         "partnum" : "40",
         "url" : null
      }
   },

   "GIFT" : {
      "1234567" : {
         "id" : "1234567",
         "name" : "Samsung Galaxy Buds",
         "picture" : "https://cdn1.savepice.ru/uploads/2019/2/22/1be40e49b646a53117e887a530419793-full.jpg" 
      }
   },

   "PROMO" : {
      "03" : {
         "description" : "Получи беспроводные наушники Galaxy Buds в подарок",
         "end-date" : "2019-03-07 23:59:59",
         "id" : "03",
         "promo-gifts" : [
            "1234567" 
         ],
         "purchase" : {
            "product" : {
               "299313" : 1,
               "299319" : 1,
               "299325" : 1,
               "299331" : 1,
               "299337" : 1,
               "299343" : 1,
               "299349" : 1,
               "299355" : 1,
               "299361" : 1,
               "299367" : 1
            },
            "required-quantity" : "1" 
         },
         "start-date" : "2018-02-21 00:00:00",
         "type" : "gift with purchase" 
      }
   }
}

вверх

Динамический контент

Описание динамического контента

Динамический контент позволяет вам задавать содержимое части выпуска или всего выпуска как текст забираемый готовым со страницы какого-либо сайта или собираемый на основе новостей из различных видов источников с помощью Лент Новостей.

Во всех ссылках поддерживается авторизация HTTP-Basic - укажите её обычным образов в самой ссылке.

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

Готовые страницы сайта

"Готовая страница сайта" это доступная веб-страница любого сайта.

При использовании в выпуске рассылки команды шаблонизатора [% wget("url") %], указанная страница забирается с сайта и подставляется в текст письма. Это происходит в момент формирования текста выпуска рассылки и поэтому адрес страницы может быть задан только как константа.

От страницы используется только содержимое между тегами <body></body>.

На странице можно дополнительно расставить метки фильтрации позволяющие указать только определённyю часть/части которую надо использовать в выпуске.

Такие части отмечаются в своём начале добавлением строки начинающейся с <!-- issueBegin --> и в конце добавлением строки начинающейся с <!-- issueEnd -->

В случае неудачи при получении страницы (не найдена, проблема связи и т.д.) выпуск не выйдет.

Лента Новостей

"Лента новостей" это набор от одного до бесконечности каналов новостей, сообщения из которых собираются и могут быть использованы для формирования индивидуальных писем или общих рассылок.

Один источник новостей может входить в несколько лент - фактически он опрашивается один раз в 1-2 часа и все найденные свежие новости собираются в базу и становятся независимо доступны во всех лента и использование определенной новости в одной из них никак не повлияет на ее состояние в другой ленте.

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

В выпуске может быть использовано несколько лент.

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

После формирования рассылки (или индивидуального письма) отметка времени у всех использованных в рассылке лент устанавливается в текущее время. Таким образом новости одной ленты уже использованные в рассылке повторно использованы не будут.

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

Использование лент новостей в Индивидуальных письмах

Индивидуальные письма подписчикам формируются из новостей накопившихся в их личных лентах которые к подписчикам привязаны владельцем ПРО при их создании (lenta.set).

Письмо формируется по указанному в ленте расписанию с оформлением по указанному шаблону выпуска с использованием новостей накопившихся с прошлой рассылки.

Если свежих новостей нет, то письмо не высылается.

Это позволяет организовать индивидуальное оповещение клиентов персонализированными подборками информации. Например, организовать на сайт подписку на результаты поиска по каталогу товаром - пользователи будут автоматически получать обновления в интересующих их разделах.

Предоставляются простые базовые шаблоны которые можно модифицировать на свой вкус. Они доступны по адресу https://sendsay.ru/resources/help/lenta/drafts.html

Использование лент новостей в рассылках

Для использовании в рассылке новостей одной или нескольких лент надо воспользоваться функциям шаблонизатора news_get() или lenta_get().

Функция news_get() не требует специальной обработки и результатом её работы становится содержимо полей "content" всех свежих новостей. Это упрощает работу с лентами если готового источника у вас ещё нет и вы можете легко формировать его как нравится. Или когда имеющийся источник выдаёт в "content" новости то, что надо для выпуска.

Функция lenta_get() только присваивает переменной lenta специальную структуру данных описывающую все свежие на данный момент новости и их источники. Для их отображение требуется использование в теле письма оператора [% FOREACH %] для обработки каждой новости. Это сложнее, но вы можете оформить вывод новостей самым гибким образом.

Обе функции могут работать как с заранее созданной лентой новостей (указывается её номер [% lenta_get(123) %]), так и напрямую с каналом новостей (указывается его url, не применимо к выпускам personal - [% lenta_get("http://test.ru/index.rss") %]).

При указании url механизм исключения уже использованных новостей не применим и в выпуск всегда попадают все новости канала имеющиеся в нём на момент выпуска.

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

Кроме периодического обновления новостей в всех настроенных лентах, система автоматически проверяет все источники используемых в рассылке лент непосредственно в момент выпуска. Но один источник проверяет не чаще чем раз в 15 минут.

По умолчанию новости отсортированы в порядке убывания и даты публикации. Задать свой порядок и группировку по источникам можно с помощью шаблонизатора - рассмотрите пример "Новости по источникам" по адресу https://sendsay.ru/resources/help/lenta/drafts.html

Базовые шаблоны от индивидуальных писем применимы и тут - как отправная точка для кастомизации под ваши нужды.

Доступные источники новостей для лент новостей

Структура переменной lenta

Структура данных ленты в индивидуальных письмах и после вызовов lenta_get() и news_get() содержит информацию о ленте новостей, всех источниках новостей новости которых попали в ленту и содержимое всех свежих новостей.

Лента новостей
"lenta" : {
       "id" : id ленты в базе

      ,"name" : "название ленты" 

      -- описания всех источников ленты, чьи новости попали в письмо
      ,"sources" : {
                    id-источника : { описание источника }
                   ,id-источника : { описание источника }
                   ......
                   }

      -- id источников упорядоченные так же как упорядочены новости
      ,"sources_order" : [ id-источника, id-источника .........]

      -- описание новостей в заказанном порядке (по умолчанию по убыванию даты публикации новости)
      ,"news" : [
                 { описание новости }
                ,{ описание новости }
                ,{ описание новости }
                ]

}
Описание одного источника
{
 "id" : id источника в базе

 -- индексы в lenta.news[] новостей именно этого источника в порядке совпадающем с порядком lenta.news[]
,"news" : [ индекс, индекс, индекс ....]

 -- параметры источника как получены из описания RSS/Atom-канала при его последнем анализе
 -- названия параметров совпадают с названиями тегов XML

,"title" : "название источника" 

,"descr" : "описание источника" 

,"link" : "ссылка на обычную версию источника" 

,"logo" : "ссылка на логотип источника" 

,"author" : "информация об авторе" 

,"copyright" : "информация об авторских правах" 

}
Описание одной новости
{
 "id" : id новости в базе

,"source" : "id в базе источника из которого получена новость. ключ в lenta.sources{}" 

,"n" : "номер по порядку в lenta.news[]" 

 -- параметры новости как получены из описания RSS/Atom-канала при его последнем анализе
 -- названия параметров в основном совпадают с названиями тегов XML

,"title" : "название источника",

,"content" : "содержимое новости" -- тег <description> или <yandex:full-text>

,"link" : "ссылка на обычную версию новости" -- тег <link>, если пусто и isPermaLink="true" и guid похож на урл, то guid

,"dt" : "YYYY-MM-DD hh:mm:ss" -- дата публикации новости (тег <pubDate> или <dc:creator>)

,"id" : "идентификатор новости" -- тег <guid>

,"author" : "информация об авторе новости" 

,"category" : { -- категории новости
               "домен" : {
                          "категория" : "метка" 
                         ,....
                         }
              ,...
              }

,"enclosure" : [ -- приложения к новости на основании тегов <enclosure> и <media:thumbnail>
                 -- обычно так указывают картинки и звуковые файлы
                {
                 "url" : "ссылка" 
                ,"type" : "mime-type" 
                ,"length" : "длина" 
                }
               ,....
               ]

 -- дополнительные параметры, полученные из dt

,"ymd" : "YYYY-MM-DD" 

,"hm" : "hh:mm" 

}

Черновики выпусков

Черновик выпуска это заготовка выпуска содержащая его содержимое (параметр letter) и ,возможно, разнообразные параметры выпуска влияющие на его формирование (прочие параметры одноимённые с issue.send)

Для использования черновика просто указывает его id или alias в issue.send

Приоритет источника содержимого и параметров выпуска описан в issue.send

Эти вызовы поменяли формат принимаемых и возвращаемых данных на совместимый с issue.send
Поля старого формата ответов будут ещё некоторое время возвращаться для обратной совместимости.
Старый формат изменения данных будет ещё некоторое время приниматься для обратной совместимости.

Список черновиков

Такой же список можно получить с помощью вызова stat.uni для объекта draft c использованием более разнообразной фильтрации и с сортировкой


{

  "action" : "issue.draft.list" 

-- параметры фильтрации, должен быть хотя бы один параметр
--
-- если выбрана последняя порция списка то ответ содержит "last_page" : 1
--
-- доступны поля
--
-- issue_draft.id             -- код черновика
-- issue_draft.name           -- "название" 
-- issue_draft.channel        -- канал отправки email|sms|viber|push|vk|tg|vknotify
-- issue_draft.create.date    -- "дата и время создания" -- Ys, null
-- issue_draft.update.date    -- "дата и время последнего изменения" -- Ys, null
-- issue_draft.alias          -- "альтернативный идентификатор" 
-- issue_draft.reltype        -- число
-- issue_draft.relref         -- число
-- issue_draft.template       -- 0|1 -- признак шаблона (шаблон - заранее предустановленный черновик с оформлением)
-- issue_draft.public_preview -- "ccылка просмотра черновика без пароля" 
--

,"filter" : [ фильтр в синтаксисе stat.uni ]

,"order" : [ сортировка ответа в синтаксисе stat.uni ]

,"skip" : количество пропускаемых записей от начала списка -- по умолчанию 0

,"first" : количество выбираемых записей после skip -- по умолчанию 50, не более 50

}

ответ


{

 <общие поля>

,"list" : [

            {

             "id" : код черновика

            ,"alias" : "альтернативный идентификатор" 

            ,"format" : "viber|sms|html|text|push|vk|tg|vknotify" -- формат черновика (для черновиков с несколькими текстами указывается только один)

            ,"name" : "название" 

            ,"template" : 0|1 -- признак шаблона (шаблон - заранее предустановленный черновик с оформлением)

            ,"create.date" : "дата и время создания" -- Ys, null

            ,"update.date" : "дата и время последнего изменения" -- Ys, null

            ,"public_preview" : "ccылка просмотра черновика без пароля" 

            ,"thumbnail" : [ список ссылок, аналогично issue.draft.get ]

            ,"reltype" : ...

            ,"relref" : ...
            }

            ...

           ]

}

Чтение черновика


{
  "action" : "issue.draft.get" 

 ,"id" : код черновика или "альтернативный идентификатор" 

 ,"novars" : 0|1 -- не возвращать variables
}

ответ


{

  <общие поля>

  "obj" : {

      "id" : код черновика

      ,"alias" : "альтернативный идентификатор" 

      ,"name" : "название черновика" 

      ,"channel" : "email|sms|viber|push|vk|tg|vknotify", -- канал для которого предназначен черновик

      ,"format" : "viber|sms|html|text|push|vk|tg|vknotify" -- формат черновика (для черновиков с несколькими текстами указывается только один)

      ,"create.date" : "дата и время создания" -- Ys, null

      ,"update.date" : "дата и время последнего изменения" -- Ys, null

      ,"public_preview" : "ccылка просмотра черновика без пароля" 

      ,"reltype" : ...

      ,"relref" : ...

      ,"template" : 0|1 -- признак шаблона (шаблон - заранее предустановленный черновик с оформлением)

      -- У предустановленных черновиков (при template = 1)

      ,"template.thumbnail" : "http://.." -- расположение (URL) изображения шаблона

      -- Cодержимое письма и настройки выпуска (аналогично issus.send)

      ,"letter" : {
                   параметры содержимого письма
                  }

      ,"relink" : .....

      ,"relink.param" : .....

      ,"multiple" : .....

      ,"tz_limit" : .....

      ,"tz_observance" : .....

      ,"tz_best" : .....

      ,"contact_rate" : .....

      ,"users.slice" : .....

      ,"only_unique" : .....

      ,"link.qsid" : .....

      ,"campaign.id" : .....

      ,"dkim.id" : .....

      ,"extra" : .....

      ,"group" : .....

      ,"users.url" : .....

      ,"users.url.remove" : ....

      ,"issue_include_filter" : .....

      ,"issue_exclude_filter" : .....

      ,"issue_member_list" : .....

      ,"unsub_list" : .....

      ,"ignore_stoplist" : .....

      ,"basegroup.id" : .......

       -- ссылки на изображения черновика
       -- только для html черновиков
       -- в настоящий момент поддерживаются картинки 800x600
       -- надо учитывать что ссылка может вести на отсутствующее изображение
       -- например, из-за сбоя при её формировании

       ,"thumbnail" : [
                       {
                        "url" : "ссылка на изображение" 
                       ,"width" : ширина в пикселах,
                       ,"height" : высота в пикселах
                       },

                       ...............
                      ]
  }

 -- список используемых в черновике переменных персонализации с указанием где используются
 -- отсутствует при novars : 1 или когда у нет letter

 ,"variables": {
                    "email" {
                             "header": [
                                        "header_var1",
                                        "header_var2",
                                        "header_var3" 
                                         .......
                                       ],
                             "html": [
                                      "html_var1",
                                      "html_var2",
                                      "html_var3" 
                                      .......
                                     ],
                             "amp": [
                                      "html_var1",
                                      "html_var2",
                                      "html_var3" 
                                      .......
                                     ],
                             "text": [
                                      "text_var1",
                                      "text_var2" 
                                      .......
                                     ]
                            }

                  ,"viber" : {
                            "viber": [
                                    "viber_var1",
                                    "viber_var2" 
                                    .......
                                  ]
                           }

                  ,"push" : {
                            "push": [
                                    "push_var1",
                                    "push_var2" 
                                    .......
                                  ]
                           }

                  ,"vk" : {
                            "vk" : [
                                    "vk_var1",
                                    "vk_var2" 
                                    .......
                                  ]
                           }

                  ,"tg" : {
                            "tg" : [
                                    "tg_var1",
                                    "tg_var2" 
                                    .......
                                  ]
                           }

                  ,"vknotify" : {
                            "vknotify" : [
                                    "vknotify_var1",
                                    "vknotify_var2" 
                                    .......
                                  ]
                           }

                  ,"sms" : {
                            "sms": [
                                    "sms_var1",
                                    "sms_var2" 
                                    .......
                                  ]
                           }
              }

}

Создание или изменение черновика

Создает или изменяет параметры и содержимое черновиков. Вызов не может быть применён к шаблонам (предустановленным черновикам) с оформлением.

При изменении уже существующего черновика, изменяются только указанные в obj параметры. Не указанные - остаются как были.


{

  "action" : "issue.draft.set" 

  ,"obj" : {
            "alias" : "альтернативный идентификатор" -- не обязательно. не должен начинаться с цифры и не должен содержать пробелы
                                                     -- может использоваться во всех местах где требуется указать код черновика

            ,"name" : "название черновика" -- обязательно при создании

            ,"reltype" : ...

            ,"relref" : ...

           -- Cодержимое письма и настройки выпуска (аналогично issus.send)

           -- При выпуске приоритет имеют параметры указанные в самом выпуске, а при их отсутствии берутся из черновика если не указано иное (подробнее в issue.send)
           -- Для удаления параметра из черновика используйте значение null - для части параметров играет роль даже просто их наличие

           ,"letter" : { -- обязательно при создании
                        параметры содержимого письма как у issue.send кроме draft.id
                        для email сообщений обязательны не пустые - адрес отправителя, тема и как минимум один текст
                        для sms сообщения обязательны не пустые - имя отправителя и текст
                        для viber сообщения обязательны не пустые - текст
                        для push сообщения обязательны не пустые - тема/текст
                        для vk сообщения обязательны не пустые - тема/текст
                        для tg сообщения обязательны не пустые - тема/текст
                        для vknotify сообщения обязательны не пустые - тема/текст
                       }

           ,"letter.zip" : "содержимое архива закодированное в base64" -- (не обязательно) как и при issue.send
                                                                       -- zip-архив содержащий текст выпуска и сопровождающие картиyки для оформления

                                                      -- В архиве ищется индексный файл который даст текст выпуска -это первый регистронезависимый файл *.htm(l) с наименьшим уровнем вложенности и наименьшим среди одноуровневых именем
                                                      -- Остальное содержимое каталога в котором он находится сохраняется в Хранилище картинок.
                                                      -- Остальные части архива игнорируются
                                                      -- В содержимом индексного файла относительные ссылки в img-src и ссs-url() дополняются веб-базой каталога куда было сохранено содержимое.
                                                      -- Полученный результат записывается в  letter-> message->html (т.е. это будет email черновик)
                                                      -- Остальные параметры по прежнему передаются через явно указаyный в вызове letter (например тема)
                                                      -- Если в вызове уже есть letter и в нём есть есть message, то это ошибка

            ,"class.id" : .....

            ,"relink" : .....

            ,"relink.param" : .....

            ,"multiple" : ..... - игнорируется при issue.send.multi

            ,"tz_limit" : .....

            ,"tz_observance" : .....

            ,"tz_best" : .....

            ,"contact_rate" : .....

            ,"users.slice" : ..... - игнорируется при issue.send.multi

            ,"only_unique" : ..... - игнорируется при issue.send.multi

            ,"link.qsid" : .....

            ,"campaign.id" : "код кампании" 

            ,"dkim.id" : " : .....

            ,"extra" : { ... } -- объединяется с extra выпуска, но имеет меньший приоритет

            ,"group" : "код группы подписчиков" -- игнорируется при выпуске

            ,"users.url" : "адрес реестра получателей" -- игнорируется при выпуске

            ,"users.url.remove" : "удаление реестра" -- игнорируется при выпуске

            ,"issue_member_list" : .....

            ,"unsub_list" : .....

            ,"ignore_stoplist" : .....

            ,"basegroup.id" : .......

            ,'accumulate' : .......

            ,"accumulate_by" : .......
           }

  -- необязательные

  ,"id" : код черновика или "альтернативный идентификатор" -- если не указан, создается новый

 ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ


{

 <общие поля>

 ,obj  { ... } -- объект в формате issue.draft.get если "return_fresh_obj" : 1

 ,"zip_draft" : { -- дополнительная информация если letter.zip был использован

                 "zip_base"  : "подкаталог архива взятый за основу" 
                ,"zip_index" : "имя файла взятого за содержимое" 

                ,"rfs_base" : "путь по rfs до каталога куда загружены файлы в домене image" 
                ,"img_base" : "абсолютный url для rfs_base" 

                ,'attachs' : [
                             "имена файлов загруженных в хранилище" 
                            ]
                }

 }

}

Удаление черновика


{

  "action" : "issue.draft.delete" 

-- одного

  ,"id" : код черновика или "альтернативный идентификатор" 

--- или нескольких

  ,"id" : [код или "альтернативный идентификатор черновика1", код или "альтернативный идентификатор черновика2", .. ]
}

ответ


{

 <общие поля>

}

Предпросмотр черновика

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

Вторая схема использования - получить текстовую версию html-черновика что бы оценить корректность автоматического преобразования.

{

 "action" : "issue.draft.preview" 

 ,"addr_type" : "тип адреса" -- не обязательно

-- одно из или ни одного

 ,"email" : "адрес для данных персонализации" -- не обязательно

-- или

 ,"email" :  {  -- не обязательно
               данные персонализации. даже значения для member.*
             }

 ,"autotext" : "1|ширина"  -- не обязательно
                           -- используется только совместно с html-версией
                           -- как результат возвращается html-версия преобразованная в текст
                           -- с сохранением всех команд персонализации
                           --
                           -- 1 - ширина строки 80 символов
                           -- ширина - ширина строки - как указано

,"extra" : {  -- не обязательно
            дополнительные данные персонализации как в issue.send
           }

-- одно из

 ,"id" : номер черновика

-- или

 ,"obj" : { -- можно передавать объект из issue.draft.get - лишние поля будут проигнорированы

            "letter" : {
                        параметры содержимого письма как у issue.send
                       }
           }

}

ответ

{
 <общие поля>

 ,"format" : "предполагаемый формат выпуска" 

 ,"letter" : {
               -- персонализированные параметры содержимого письма
             }

 ,"smses" : число -- для sms. во сколько смс уложится сообщение

--- для совместимости со старым форматом. будет удалено 01 октября 2017 года
 "text" : "текст выпуска" 

}

Предпросмотр в EmailOnAcid

Специфические ошибки

while_preview/authext_error
while_preview/request_error
while_preview/service_error
while_preview/wrong_args
while_preview/wrong_auth
while_preview/wrong_auth_token
while_preview/wrong_client_name
while_preview/wrong_clients
while_preview/wrong_service_name
while_preview/wrong_tag_name
while_preview/wrong_tags

Заказать генерацию предпросмотра

{
 "action" : "issue.draft.preview" 

 <обычные параметры для предпросмотра определяющие черновик или текст>

 ,"preview" : {
               "service" : "eoa" 

              ,"clients" : ["...","..."] -- список почтовых клиентов для которых требуется генерация
                                         -- не обязательно, по умолчанию используется список настроенный
                                         -- в аккаунте сервиса EmailOnAcid

              ,"tags" : ["...","..."] -- список меток, не обязательно
                                      -- позволяет искать результат в интерфейсе сервиса EmailOnAcid
              }
}

ответ

{
 <обычный ответ апи>

,"preview.id" : "идентификатор генерации для последующего вызова получения результата" 

}

Получить результат генерации

Если сервис EmailOnAcid ещё не успел подготовить результат, то вызов завершится с ошибкой

{
 "action" : "issue.draft.preview" 

 ,"preview" : {
               "service":"eoa" 

              ,"id" : "идентификатор генерации" 

              ,"client" : "почтовый клиент для которого нужен результат" 
              }
}

ответ

{
 <общие поля>

 "list" : {
           "почтовый клиент из запроса" : {
                  "url" : "ссылка на полное изображение" 
                 ,"thumb" : "ссылка на миниатюру" 
                 }
          }
}

Получить список доступных почтовых клиентов

{
 "action" : "issue.draft.preview" 

 ,"preview" : {
               "service":"eoa" 

              ,"get" : "clients" 
              }
}

ответ

{
 <общие поля>

,"list" : [
           "notes85",
           "wde_chr26_win",
           "thunderbird13",
           "gapps_ff21_win",
           "aol_ff21_mac",
           "outlook03",
           "iphonese_12",
           "iphone8_12",
           "gmx_ie11_win",
           "gapps_ie11_win",
           "lb_chr26_win",
           "tsa_ie11_win",
           "toe_chr26_win",
           "o365_ff21_win",
           "iphonexsmax_13",
           ...
          ]
}

Получить список почтовых клиентов установленных по умолчанию

{
 "action" : "issue.draft.preview" 

 ,"preview" : {
               "service":"eoa" 

              ,"get" : "сlients.default" 
              }
}

ответ

{
 <общие поля>

,"list" : [
           ...
          ]
}

Получить подробное описание почтовых клиентов

{
 "action" : "issue.draft.preview" 

,"preview" : {
              "service":"eoa" 

             ,"get" : "clients.config" 
             }
}

ответ

{
 <общие поля>

,"list" : {
      "notes85" : {
         "client" : "Lotus Notes 8.5",
         "default" : true,
         "category" : "Application",
         "id" : "notes85",
         "os" : "Windows 7" 
      },
      "wde_chr26_win" : {
         "client" : "Web.de",
         "browser" : "Chrome",
         "default" : true,
         "category" : "Web",
         "id" : "wde_chr26_win",
         "os" : "Windows 7" 
      },
      "thunderbird13" : {
         "client" : "Thunderbird",
         "default" : true,
         "category" : "Application",
         "id" : "thunderbird13",
         "os" : "Windows 7" 
      },
...
   }
}

вверх

Классы выпусков

Класс выпуска это набор разнообразные параметры выпуска влияющие на его формирование (параметры одноимённые с issue.send)

Подходит для хранения "типичного набора параметров" одинаковых для нескольких черновиков или выпусков.

Так же полезен просто для классификации выпусков.

Для использования класса просто указывает его id или alias в issue.send и/или issur.draft.*

Приоритет определения параметров выпуска в случае нескольких источников описан в issue.send.

Список классов


{

  "action" : "issue.class.list" 

-- параметры фильтрации, должен быть хотя бы один параметр
--
-- если выбрана последняя порция списка то ответ содержит "last_page" : 1
--
-- доступны поля
--
-- issue_class.id             -- код класса
-- issue_class.name           -- "название" 
-- issue_class.create.date    -- "дата и время создания" -- Ys, null
-- issue_class.update.date    -- "дата и время последнего изменения" -- Ys, null
-- issue_class.alias          -- "альтернативный идентификатор" 
-- issue_class.reltype        -- число
-- issue_class.relref         -- число
--

,"filter" : [ фильтр в синтаксисе stat.uni ]

,"order" : [ сортировка ответа в синтаксисе stat.uni ]

,"skip" : количество пропускаемых записей от начала списка -- по умолчанию 0

,"first" : количество выбираемых записей после skip -- по умолчанию 50, не более 50

}

ответ


{

 <общие поля>

,"list" : [

            {

             "id" : код класса

            ,"alias" : "альтернативный идентификатор" 

            ,"name" : "название" 

            ,"create.date" : "дата и время создания" -- Ys, null

            ,"update.date" : "дата и время последнего изменения" -- Ys, null

            ,"reltype" : ...

            ,"relref" : ...
            }

            ...

           ]

}

Чтение класса


{

  "action" : "issue.class.get",

  "id" : код класса или "альтернативный идентификатор" 
}

ответ


{

  <общие поля>

  "obj" : {

      "id" : код класса

      ,"alias" : "альтернативный идентификатор" 

      ,"name" : "название класса" 

      ,"create.date" : "дата и время создания" -- Ys, null

      ,"update.date" : "дата и время последнего изменения" -- Ys, null

      ,"reltype" : ...

      ,"relref" : ...

      -- Настройки выпуска (аналогично issus.send)

      ,"relink" : .....

      ,"relink.param" : .....

      ,"multiple" : .....

      ,"tz_limit" : .....

      ,"tz_observance" : .....

      ,"tz_best" : .....

      ,"contact_rate" : .....

      ,"users.slice" : .....

      ,"only_unique" : .....

      ,"link.qsid" : .....

      ,"campaign.id" : .....

      ,"dkim.id" : .....

      ,"extra" : .....

      ,"group" : .....

      ,"issue_member_list" : .....

      ,"unsub_list" : .....

      ,"ignore_stoplist" : .....
  }

}

Создание или изменение класса

Создает или изменяет параметры и содержимое классов.

При изменении уже существующего класса, изменяются только указанные в obj параметры. Не указанные - остаются как были.


{

  "action" : "issue.class.set" 

  ,"obj" : {
            "alias" : "альтернативный идентификатор" -- не обязательно. не должен начинаться с цифры и не должен содержать пробелы
                                                     -- может использоваться во всех местах где требуется указать код класса

            ,"name" : "название класса" -- обязательно при создании

            ,"reltype" : ...

            ,"relref" : ...

           -- Настройки выпуска (аналогично issus.send)

           -- Для удаления параметра из класса используйте значение null - для части параметров играет роль даже просто их наличие

            ,"relink" : .....

            ,"relink.param" : .....

            ,"multiple" : ..... - игнорируется при issue.send.multi

            ,"tz_limit" : .....

            ,"tz_observance" : .....

            ,"tz_best" : .....

            ,"contact_rate" : .....

            ,"users.slice" : ..... - игнорируется при issue.send.multi

            ,"only_unique" : ..... - игнорируется при issue.send.multi

            ,"link.qsid" : .....

            ,"campaign.id" : "код кампании" 

            ,"dkim.id" : " : .....

            ,"extra" : .....

            ,"issue_member_list" : .....

            ,"unsub_list" : .....

            ,"ignore_stoplist" : .....
           }

  -- необязательные

  ,"id" : код класса или "альтернативный идентификатор" -- если не указан, создается новый

  ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ


{

 <общие поля>

 ,obj  { ... } -- объект в формате issue.class.get если "return_fresh_obj" : 1

}

Удаление класса


{

  "action" : "issue.class.delete" 

-- одного

  ,"id" : код класса или "альтернативный идентификатор" 

--- или нескольких

  ,"id" : [код или "альтернативный идентификатор класса1", код или "альтернативный идентификатор класса2", .. ]
}

ответ


{

 <общие поля>

}

вверх

Архив выпусков

Список выпусков в архиве

В большинстве случаев вызов issue.list + несколько issue.get можно заменить одним более быстрым вызовом stat.uni для объекта issue.


 {

  "action" : "issue.list" 

  ,"from" : "YYYY-MM-DD" -- от даты (не обязательно)

  ,"upto" : "YYYY-MM-DD" -- до даты (не обязательно)

  ,"group" : [ -- фильтр по группам (не обязательно)

               код-группы-1

              ,код-группы-2

               ...

             ]

  ,"format" :  "email|sms|viber|push|vk|tg|vknotify"  -- фильтр по формату (не обязательно)
 }

ответ


{

 <общие поля>

,"list" : [

            {

             "id" : уникальный идентификатор выпуска

            ,"gid" : "код группы" 

            ,"date" : "дата выпуска" -- Ys
            }

            ...

           ]

}

Чтение выпуска в архиве

Если не нужен текст выпуска или фильтр выпуска, то вызов issue.get можно заменить более быстрым вызовом stat.uni для объекта issue.


 {

  "action" : "issue.get" 

  ,"id" : уникальный идентификатор выпуска

  ,'letter' => номерписьма -- только если выпуск personal
                           -- номер письма в выпуске для которого требуется ответ
                           -- без него для выпуска personal будет возвращена только некоторая общая информация так как, в отличии от массовых выпусков,
                           -- каждое письмо personal может быть индивидуально

  ,"source" : 0|1|2 -- вернуть текст как  пошёл в рассылку (0) или как было на входе (1 или 2), а так же исходный текст фильтра выпуска (полностью развёрнутый)
                    -- при source=1 текст выпуска для personal всё равно как при source=0 т.к. для pesonal не сохраняется хранится исходник
                    -- при source=2 как source=1 и attaches в формате с url и со ссылкой на архив
                    -- отсутствие параметра вообще - старый формат ответа. поддерживается до 01 октября 2017

  ,"with_name" : 0|1 -- вернуть group.name

  ,"with_archive" : 0|1 -- attaches в формате с url и со ссылкой на архив

 }

ответ


{

 <общие поля>

 "obj" : {

           "id" : уникальный идентификатор выпуска,

          ,"date" : "дата выпуска",

          ,"name" : "название выпуска" 

          ,"group" : "уникальный идентификатор группы выпуска",

          ,"group.name" : "название группы выпуска", -- если with_name : 1

          ,"channel" : "email|sms|viber|push|vk|tg|vknotify", -- канал выпуска

          ,"draft.id" : "идентификатор черновика, по которому сделан выпуск", (null)

          ,"draft.alias" : "альтернативный идентификатор черновика, по которому сделан выпуск", (null)

          ,"class.id" : "идентификатор класса выпуска использованный при выпуске", (null)

          ,"class.alias" : "альтернативный идентификатор класса выпуска использованный при выпуске", (null)

          ,"sequence.id" : "идентификатор последовательности, событие в которой вызвало выпуск", (null)

          ,"variant.id" : "варианта сплит-тестирования для которого сделан выпуск", (null)

          ,"multi.id" : "идентификатор мультивыпуска для которого сделан выпуск", (null)

          ,"issue" : { -- как элемент issue.send
                     "letter" : {
                                  -- параметры содержимого выпуска (отправитель, получатель, тема,..)
                                  -- при выпуске по черновику - его содержимое, а номер черновика выше в draft.id
                                  -- в тексте уже оформленное содержимое письма как пошло в рассылку если source = 0
                                  -- в тексте исходное содержимое если source = 1 или 2

                                  -- при source 0 или 1 и без with_archive список прикреплённых файлов в формате содержащим только их названия
                                  -- содержимое можно получить через issue.get.attach по названию

                                  ,"attaches" : [

                                                 "aaa.doc" 

                                                ,"bbb.doc" 

                                                 ...
                                                ]

                                  -- при source 2 или with_archive список прикреплённых файлов в формате содержащим их названия, ссылку на архив и, при наличии, url откуда они были получены
                                  -- содержимое можно получить через issue.get.attach по названию

                                  ,"attaches" : [

                                                 {
                                                  "name" : "aaa.doc" 

                                                 ,"url"  : "http://test.ru/aaa.doc" 
                                                 }

                                                ,{
                                                  "name" : "bbb.doc" 
                                                 }

                                                 ...
                                                ]
                                      }
                                 }

                      -- прочие параметры выпуска (accumalate, ttl, class.id, ...)
                     }

  -- source >= 1

          ,"extra" : { дополнительные данные произвольной структуры использованные в выпуске }

          ,"filter" : {
                        -- фильтр группы, каким он был в момент выпуска
                        -- не для personal и masssending
                      }

  -- для всех source

           ,"archive" : "ссылка на выпуск в архиве" -- если personal без letter, то смысла в такой ссылки нет

           -- ссылки на изображения письма выпуска
           -- только для html выпусков, и только для групп отличных от personal
           -- в настоящий момент поддерживаются картинки 800x600
           -- надо учитывать что ссылка может вести на отсутствующее изображение
           -- например, из-за сбоя при её формировании

           ,"thumbnail" : [
                          {
                           "url" : "ссылка на изображение" 
                          ,"width" : ширина в пикселах,
                          ,"height" : высота в пикселах
                          },

                         ...............

                         ]

         }

}

Получение файлов, приложенных к выпуску

Получение приложенных файлов которые задавались при выпуске содержимым, а не ссылкой


 {

  "action" : "issue.get.attach" 

  ,"id" : уникальный идентификатор выпуска

  ,'letter' => номерписьма -- только если выпуск personal
                           -- номер письма в выпуске для которого требуется ответ
                           -- без него для выпуска personal будет возвращена только некоторая общая информация так как, в отличии от массовых выпусков,
                           -- каждое письмо personal может быть индивидуально

  ,"encoding" : "желаемая кодировка данных в ответе" -- пусто или не указана - обычная Unicode/UTF-8
                                                     -- base64 - содержимое будет возвращено в base64

  ,"attach" : [  -- имена файлов вложений, которые необходимо получить (если параметр не указан - все вложения)

                "aaa.doc" 

               ,"bbb.doc" 

                ...

              ]

 }

ответ


{

 <общие поля>

 ,"list" : {

            "aaa.doc" : "содержимое файла|null - если файл не найден или задавался в выпуске ссылкой",

            "bbb.doc" : "содержимое файла|null - если файл не найден или задавался в выпуске ссылкой",

            ...

           }

}

Настройка DKIM-ключей рассылок

DKIM-ключ использутся для двух целей

- Добавление в email-сообщения клиентской dkim-подписи для лучшей доставляемости

- Возможности заменить стандартные системые технические адреса и домены на свои

Минимальное использование - для dkim-подписи - применимо только к выпускам по email

Максимальное - с кастомизацией - позволяет заменить служебные адреса и домены на свои не только в email, но и для других каналов выпуска.

Кастомизация доменов с помощью dkim-ключа

При успешной настройке кастомизации служебные почтовые адрес (например, используемый в MAILFROМ) и домены (например, используемый для учёта переходов link.sendsay.ru) будут использовать ваш домен.

Например, при выделении под кастомизацию домена mail.client.tld домен учёта переходов станет link.mail.client.tld

Перед тем как настраивать dkim-ключ, необходимо правильно настроить DNS-записи для домена link.mail.client.tld

Самый простой и безболезненный пусть - делегировать его нам - мы провердём все настройки и они будут всегда в актуальном состоянии

Если делегирование не возможно, то вы сами вносите необходимые записи в ваш DNS.

Инструкции для обоих способов настройки можно получить в Службе Поддержки.

После настройки DNS указать в dkim-ключе что его домен кастомизированный (issue.dkim.set) и провести проверку (issue.dkim.check).

Ниже приведено описание доступных параметров кастомизации из которых вам, скорее всего, понадобится только domain.custom.

domain.custom : 1 - использовать для кастомизации, все ниже описанные настройки не действуют если domain.custom = 0 или пуст или отсутствует

domain.email : "+" - признак того, что почта настроена клиентом самостоятельно и проверять корректность её настройки при проверки MX DKIM не надо
- остальные значения игнорируются. ОСТАВЛЯТЬ ПУСТЫМ если не "+" !!!

domain.secure : - 0 - использовать http
1 - использовать https - при делегировании домена нам установите свой сертификат через Службу Поддержки или попросите настроить Let's Encrypt

domain.http - домен используемый для кастомизации тематических доменов
"-" - для тематических доменов если они не указаны полность будет использоваться домен dkim. при проверках дким правильность настройки для domain.http проверяться НЕ будет
пусто - для тематических доменов если они не указаны полность будет использоваться домен dkim. при проверках дким правильность настройки для domain.http проверяться БУДЕТ
другой домен - для тематических доменов если они не указаны полность будет указаный домен. при проверках дким правильность настройки для него проверяться БУДУТ

domain.link - префикс к domain/domain.http (если без точек) или полный домен для ссылок. пусто - префикс "link", "-" - отключает кастомизацию данного урла

domain.image - префикс к domain/domain.http (если без точек) или полный домен для картинок. пусто - "image", "-" - отключает кастомизацию данного урла

domain.read - префикс к domain/domain.http (если без точек) или полный домен для учёта чтения. пусто - read.sendsay.ru, "-" - отключает кастомизацию данного урла (это поведение отличается от всех прочих !!!)

domain.arch - префикс к domain/domain.http (если без точек) или полный домен для архива выпусков. пусто - если domain.http непусто то префикс "arch" к domain.http иначе равно просто domain; "-" - отключает кастомизацию данного урла

domain.unsub - префикс к domain/domain.http (если без точек) или полный домен для ссылок отписок от подписки и подтверждения подписки. пусто - если domain.http непусто то префикс "unsub" к domain.http иначе равно просто domain; ; "-" - отключает кастомизацию данного урла

email.mailfrom - префикс к domain (если без @) или полный адрес для MAILFROM. и учитывается long для loggluck. пусто - email@$domain и всегда активен longgluck, '-' - отключает кастомизацию параметра. возможно понадобится domain.email="+"

email.mailfrom.long - если email.mailfrom не пусто, то 1 - он подходит для longgluck

email.fbl - префикс к domain (если без @) или полный адрес для fbl. пусто - *fbl*@$domain, '-' - отключает кастомизацию параметра. возможно понадобится domain.email="+"

Список dkim-ключей

{

  "action" : "issue.dkim.list" 

-- параметры фильтрации, должен быть хотя бы один параметр
--
-- если выбрана последняя порция списка то ответ содержит "last_page" : 1
--
-- доступны поля
--
-- dkim.id -- "идентификатор dkim-ключа" 
-- dkim.domain -- "домен" 
-- dkim.selector -- "селектор" 
-- dkim.onmoderation -- 0|1 -- 0 - dkim-ключ подтверждён, 1 - dkim-ключ ещё не подтверждён
-- dkim.create.date -- дата и время создания Ys
-- dkim.update.date -- дата и время обновления Ys
-- dkim.domain.custom -- 0|1 - домен DKIM используется для кастомизации всех служебных доменов
-- dkim.domain.secure -- 0|1 - при этом используется http:// (0) или https:// (1)
-- dkim.sublogin      -- привязан к конкретному пользователю

,"filter" : [ фильтр в синтаксисе stat.uni ]

,"order" : [ сортировка ответа в синтаксисе stat.uni ]

,"skip" : количество пропускаемых записей от начала списка -- по умолчанию 0

,"first" : количество выбираемых записей после skip -- по умолчанию 50, не более 50

}

ответ

{

 <общие поля>

,"list" : [

            {

             "id" : уникальный идентификатор

            ,"domain" : "домен" 

            ,"selector" : "селектор" 

            ,"onmoderation" : 0|1 -- 0 - dkim-ключ подтверждён, 1 - dkim-ключ ещё не подтверждён

            ,"create.date" : "дата и время создания" -- Ys, null

            ,"update.date" : "дата и время последнего изменения" -- Ys, null

            ,"domain.custom" : 0|1 - домен DKIM используется для кастомизации всех служебных доменов

            ,"domain.secure" : 0|1 - при этом используется http:// (0) или https:// (1)
            }

            ...

           ]

}

Чтение dkim-ключа

{

  "action" : "issue.dkim.get" 

-- или

  ,"id" : идентификатор dkim-ключа

-- или

  ,"id" : {
           "domain" : "домен" -- при использовании интернациональных доменов записывайте их "как есть", без xn-- кодирования

          ,"selector" : "селектор" 

          }

}

ответ

{
  <общие поля>

  "obj" : {

      ,"id" : "идентификатор dkim-ключа" (или {"domain" : "домен", "selector" : "селектор"})

      ,"domain" : "домен" 

      ,"selector" : "селектор" 

      ,"public-key" : "публичный ключ" 

      ,'version' : 'DKIM1'

      ,'key_type' : 'rsa'

      ,'dns_record' : "<select>._domainkey.<domain> IN TXT  \"v=<version>; k=<key_type>; p=<public_key>\"" 

      ,"onmoderation" : 0|1 -- 0 - проверка дкима и если есть,кастомной настройки, прошла не удачно. дким и кастомная настройка не действуют
                            -- 1 - проверки прошли удачно, дким и его настройки действуют

      ,"domain.custom": 0|1|-1 -  1 - домен делегирован нам для кастомной настройки и дкима
                               - -1 - клиент сам в меру сил провёл кастомную настройку и дким

      ,"settings" : { -- настройки которые будут действовать когда проверка пройдёт успешно
         "email" : {
            "fbl" : "адрес для FBL",
            "mailfrom" : "адрес для MAILFROM" 
         },
         "site" : {
            "link" : "cайт для учёта кликов",
            "unsub" : "cайт для страниц отписки ",
            "arch" : "cайт для архива выпусков",
            "read" : "cайт для учёта чтений",
            "image" : "cайт для хранимых изображений " 
         }

     -- настройки кастомизации, если заданы

     ,'domain.secure'
     ,'domain.http'
     ,'domain.link'
     ,'domain.image'
     ,'domain.read'
     ,'domain.arch'
     ,'domain.unsub'
     ,'domain.email'
     ,'email.mailfrom'
     ,'email.mailfrom.long'
     ,'email.fbl' 

   }

}

Создание dkim-ключа

{
  "action" : "issue.dkim.create" 

  ,"obj" : {

            "domain" : "домен" -- при использовании интернациональных доменов записывайте их "как есть", без xn-- кодирования

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

            -- урлы отправки колбеков для событий из выпусков с этим dkim. позволяет переопределить глобальные настройки. все не обязательны
            -- если не пусто заменяет глобальную настройку, но глобальный урл должен быть сконфигурирован чтобы срабатывал этот. не зависит от domain.custom
            ,"url.callback" : "url" -- общий, если не задан один из специфических. если нет не специфического и тут пусто, то как настроено глобально. не обязательно
            ,"url.callback.read" : "url" 
            ,"url.callback.click" : "url" 
            ,"url.callback.target" : "url" 
            ,"url.callback.unsub" : "url" 
            ,"url.callback.deliv" : "url" 
            ,"url.callback.form" : "url" 
            ,"url.callback.tracker" : "url" 
            ,"url.callback.confirm" : "url" 
            ,"url.callback.draft" : "url" 
            ,"url.callback.emailreply" : "url" 
           }

-- необязательные

 ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ

{

 <общие поля>

 ,"obj"  { ... } -- объект в формате issue.dkim.get если "return_fresh_obj" : 1

 ,"id" : идентификатор dkim-ключа

}

Изменение настроек dkim-ключа

{

  "action" : "issue.dkim.set" 

  ,"id" : "идентификатор dkim-ключа" 

  ,"obj" : { -- не указанные параметры остаются неизменными

     ,"protected" : 0 | 1

            -- урлы отправки колбеков для событий из выпусков с этим dkim. позволяет переопределить глобальные настройки. все не обязательны
            -- если не пусто заменяет глобальную настройку, но глобальный урл должен быть сконфигурирован чтобы срабатывал этот. не зависит от domain.custom
     ,"url.callback" : "url" -- общий, если не задан один из специфических. если нет не специфического и тут пусто, то как настроено глобально
     ,"url.callback.read" : "url" 
     ,"url.callback.click" : "url" 
     ,"url.callback.target" : "url" 
     ,"url.callback.unsub" : "url" 
     ,"url.callback.deliv" : "url" 
     ,"url.callback.form" : "url" 
     ,"url.callback.tracker" : "url" 
     ,"url.callback.confirm" : "url" 
     ,"url.callback.draft" : "url" 
     ,"url.callback.emailreply" : "url" 

     -- настройки кастомизации. изменение любой настройки переводит DKIM в состояние "требуется проверка" 

     ,'domain.custom'
     ,'domain.secure'
     ,'domain.http'
     ,'domain.link'
     ,'domain.image'
     ,'domain.read'
     ,'domain.arch'
     ,'domain.unsub'
     ,'domain.email'
     ,'email.mailfrom'
     ,'email.mailfrom.long'
     ,'email.fbl' 

           }

 ,"return_fresh_obj": "0|1" -- вернуть объект в формате issue.dkim.get

}

Удаление dkim-ключа

{

  "action" : "issue.dkim.delete" 

  ,"id" : идентификатор dkim-ключа

}

ответ

{

 <общие поля>

}

Проверка настройки dkim-ключа

Производится проверка DNS-записи необходимой для использования dkim-ключа - запись <selector>._dkim.<domain> типа TXT должна иметь значение соответствующее образцу возвращаемому в issue.dkim.get

Если домен дополнительно настроен как "делегированный", то проверяются все другие записи DNS необходимые для его использования как делегированного.

При успешной проверке onmoderation становится 0 и ключом можно пользоваться для выпуска рассылок. Иначе onmoderation становится 1 и ключ блокируется.

{

  "action" : "issue.dkim.check" 

  ,"id" : идентификатор dkim-ключа

}

ответ

{

 <общие поля>

-- одно из

 "onmoderation" : 0 -- запись успешно проверена

-- одно из

 "onmoderation" : 1 -- запись проверить не удалось, использовать нельзя

,"warnings" : [ -- ошибки встреченные при проверке записей
              {
               "id" : "error/notfound"  -- не найдено ни одно записи вообще

              }
             ,{
               "id" : "error/value"  -- у найденной записи в указанном поле не то значение что ожидается

              ,"explain" : "имя поля" 

              ,"record" : "значение проверяемой DNS-записи" 
              }

              ......

             ]

}

Передача событий из системы клиенту (Callback, WebHook)

Клиент может получать от системы уведомления о события открытии сообщения (если формат выпуска поддерживает такое событие), перехода по ссылке (если формат выпуска поддерживает такое событие), отписке, заполнения формы, завершения работы трекера.

События, на типы которых настроена передача, передаются клиенту по мере появления, но не сразу, а с интервалом накопления порядка минуты.

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

В данный момент, при сборе событий для передачи в один вызов подбирается максимум 2500-2600 событий.

Что даст запрос размером, примерно, 2-5-10 мегабайт в зависимости от событий и настроенной подробности передаваемых данных.

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

Порядок накопления и передачи сохраняющий порядок происхождения событий не гарантирован.
Если для какого-то объекта может быть несколько последовательных событий (например выпуск и изменения его статуса), то близко произошедшие события могут быть переданы как в хронологическом порядке, так и нет.
У событий есть собственная временная отметка когда они произошли, и, в примере с выпуском, полученное событие трекера (имеет status.dt) должно вами проверяться не произошло ли оно в прошлом относительно уже ранее полученных и обработанных событий для данного выпуска.

Так же, формально, массив/поток ни каким образом не упорядочен по дате и времени события, хотя, в целом, более ранние события обычно находятся в начале массива/потока при первой попытке передачи.

События передаются в одном из двух форматов. Используйте вызов sys.settings.get/set с параметром callback.format для выбора наиболее подходящего формата.

Формат "json" - по умолчанию - как элемент events в виде JSON-массива содержащего по одному элементу на событие.

Метод передачи POST application/json, кодировка utf8.

{
 "events" : [
             { событие-1 }
            ,{ событие-2 }
            ,{ событие-3 }
            .....
            ]
}

Формат "json-stream" - JSON-объекты описывающие события следующие друг за другом без json-разделителя (запятой), возможно разделённые пробельными символами (пробелы,переводы строк). Формат хорошо подходит для поточного разбора.

Метод передачи POST application/x-ndjson, кодировка utf8.

{ событие-1 }
{ событие-2 }
{ событие-3 }
.....

Разные типы событий могут быть настроены для передачи по разным адресам. Если на один адрес настроены несколько типов событий, то внутри массива их порядок не определён.

Для настройки адресов передачи используйте sys.settings.set c callback.url.*

Так же, возможна настройка индивидуальных адресов для конкретного выпуска - через настройки в его dkim-ключе - смотрите issue.dkim.*

В адресе можно указать метку SS_EVENT_TYPE и она заменится на тип события как он передаётся в параметре event события.

При наличии у события event.dt так же есть и event.dttz содержащий тоже время, но с тайм-зоной и в формате ISO8601.

Тайм-аут вызова - 15 секунд. Вы должны просто принять данные и сохранить их в свою очередь для дальнейшей обработки, а не пытаться производить её сразу по приходу вызова.

В случае тайм-аута или получения ответа с кодом не 200 и не 204 и не 406 передача событий будет повторяться до 10 раз с возрастающим интервалом между попытками.

В данный момент интервалы следующие

После попытки Минимальная пауза минут до следующей
1 5
2 5
3 5
4 10
5 15
6 25
7 45
8 60
9 60
10 90

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

Cохранение в Отчёты

Для сохранения событий в Отчёты используйте ссылку вида

rfs://report/callback

Формат событий в файле всегда json-stream

Лимит количества событий в файле в 100 раз больше чем при передаче по http

Файл сжимается zip.

Файл всегда попадает в Отчёты даже если хост в ссылке не report.

Если в ссылке указан путь начинающийся с /callback, то используется именно такой путь как название папки куда поместить файл.

Если в ссылке путь не начинается с /callback, то к нему автоматически добавляется в начале /callback и результат используется как название папки куда поместить файл.

Срок хранения файлов - 48 часов.

Рекомендуется самостоятельно удалять забранные файлы.

Передача событий в Google Big Data

Для передачи событий в Google Big Data настройте внешнюю авторизация для схемы gbd:// (описано в вызовах authext.*) и используйте ссылку вида

gbd://authext:AUTHEXT_ID@DATASET_ID/TABLE_ID

где

AUTHEXT_ID - код внешней авторизации

DATASET_ID - код набора данных

TABLE_ID - код (название) таблицы или view в первую колонку (она должна быть типа STRING) который будут вставлены json-закодированные строки с объектами событий. Одно событие - одна строка.

В дальнейшем с этими данными в таблице можно работать через функции работы с json в запросах Google Big Query.

Расширенная информация о выпуске

Включается отдельно через Службу поддержки так как данная информация не нужна большинству получателей.

Добавляется информация:

"issue.dkim.id" : номер дкима (int64)(null)
"issue.name" : "название выпуска" 
"issue.label" : [...] -- метки выпуска (null)
"issue.from_name" : "имя отправителя" 
"issue.from_email" : "адрес отправителя" 
"issue.reply_name" : "имя для ответов" 
"issue.reply_email" : "адрес для ответов" 
"issue.login" : "логин аккаунта запустивший выпуск" (null)

Событие открытия сообщения

{
 "event"     : "read" 
,"event.dt"  : "дата-время события (Ys)" 
,"account"   : "код аккаунта" 
,"gate.name" : "код шлюза выпуска письма" -- api smtp tranz
,"gate.uniq" : "уникальный идентификатор сообщения в шлюзе" 
,"customer.id" : "идентификатор сообщения назначенный клиентом" (null)
,"issue.id"  : "номер выпуска" (int64)
,"issue.draft.id" : "номер черновика выпуска" (int64)(null)
,"issue.label"  : [ ... ] -- метки выпуска (null)
,"letter.id" : "номер сообщения" (int64)
,"letter.custid" : { -- значения пользовательских меток назначенных сообщению при выпуске
                     -- отсутствует если их не было или если не известны одновременно
                     -- issue.id и letter.id (так, например, бывает при отписке)
                    "метка1" : "значение1" 
                   ,"метка2" : "значение2" 
                   ........
                   }
,"email"     : "адрес получателя" 
,"email.id"  : "номер адреса" (int64)

,"ip" : "ip-адрес" 
,"http.user-agent" : "заголовок User-Agent от браузера подписчика" 
,"duration" : "длительность чтения" - целое число секунд. смысл null описан в stat.uni read.duration (int64)(null)

-- информация аналогичная stat.uni
,"gender.gender"    : "тип устройства подписчика" (null)
,"gender.os"        : "операционная система устройства" (null)
,"gender.browser"   : "используемый браузер" (null)
,"gender.browmajor" : "старший номер версии браузера" (int64)(null)
,"geo.id"           : "числовой код географии" (int64)(null)
,"geo.name"         : "название географии" (null)
}

Событие перехода по ссылке

{
 "event"     : "click" 
,"event.dt"  : "дата-время события (Ys)" 
,"account"   : "код аккаунта" 
,"gate.name" : "код шлюза выпуска сообщения" -- api smtp tranz
,"gate.uniq" : "уникальный идентификатор сообщения в шлюзе" 
,"customer.id" : "идентификатор сообщения назначенный клиентом" (null)
,"issue.id"  : "номер выпуска" (int64)
,"issue.draft.id" : "номер черновика выпуска" (int64)(null)
,"issue.label"  : [ ... ] -- метки выпуска (null)
,"letter.id" : "номер сообщения" (int64)
,"letter.custid" : { ... пользовательские метки ... }
,"email"     : "адрес получателя" 
,"email.id"  : "номер адреса" (int64)

,"ip" : "ip-адрес" 
,"http.user-agent" : "заголовок User-Agent от браузера подписчика" 

,"url" : "ссылка по которой был направлен подписчик" 

-- информация аналогичная stat.uni
,"gender.gender"    : "тип устройства подписчика" (null)
,"gender.os"        : "операционная система устройства" (null)
,"gender.browser"   : "используемый браузер" (null)
,"gender.browmajor" : "старший номер версии браузера" (int64)(null)
,"geo.id"           : "числовой код географии" (null)
,"geo.name"         : "название географии" (null)
}

Событие отписки

{
 "event"     : "unsub" 
,"event.dt"  : "дата-время события (Ys)" 
,"account"   : "код аккаунта" 
,"gate.name" : "код шлюза выпуска сообщения" (null) -- api smtp tranz
,"gate.uniq" : "уникальный идентификатор сообщения в шлюзе" (null)
,"customer.id" : "идентификатор сообщения назначеный клиентом" (null)
,"issue.id"  : "номер выпуска" (int64)(null)
,"issue.draft.id" : "номер черновика выпуска" (int64)(null)
,"issue.label"  : [ ... ] -- метки выпуска (null)
,"letter.id" : "номер сообщения" (int64)(null)
,"letter.custid" : { ... пользовательские метки ... }
,"email"     : "адрес получателя" 
,"email.id"  : "номер адреса" (int64)

,"ip" : "ip-адрес" 
,"http.user-agent" : "заголовок User-Agent от браузера подписчика" 

,"event.type" : "причина отписки: unsub - по ссылке отписки, topic - по ссылке тематической отписки, listunsub - нажато "Отписаться" в веб-почте, fbl - нажато "Это спам"  в веб-почте
,"unsub.list" : "код группы для отписки при тематической отписке" (null)
,"sender.email" : "адрес отправителя" -- при отписки при активно стоп-листе по отправителю (null)
,"comment"    : "сообщение от подписчика о причине отписки. если указал" (null)
,"label"      : "метка отписки" - до 64 байт, указывается в параметре label ссылки отписки [% param.url_unsub %]?label=campaign23 (null)
}

Событие подтверждение регистрации

Адрес находившийся в состоянии "Требуется подтверждение регистрации" был подтверждён.

Не важно каким путём - по ссылке подтверждения, апи-вызов, заполнение формы.

{
 "event"     : "member.confirm" 
,"event.dt"  : "дата-время события (Ys)" 
,"account"   : "код аккаунта" 
,"email"     : "адрес получателя" 
,"email.id"  : "номер адреса" (int64)

,"ip" : "ip-адрес" 
,"http.user-agent" : "заголовок User-Agent от браузера подписчика" 
}

Событие доставки

Для email, некоторые почтовые системы присылают письма о недоставке в формате позволяющим понять только про какой аккаунт и почтовый адрес идёт речь, но не определить номер выпуска и номер письма.

В таком случае в событии передаётся номер выпуска и номер письма как 0.

Для email, для одного сообщения может быть отправлено несколько событий доставки. Сначала письмо принимается (положительное событие доставки), а через некоторое время на технический обратный адрес приходит автоматическое письмо что доставка не состоялась - это вызывает ещё одно событие доставки, на этот раз отрицательное.

Например, на принявшем почтовом сервере настроена пересылка на другой адрес и она не получилась.

Для исключённых из выпуска (статус доставки < -100000) могут быть null в зависимости от причины исключения - letter.id, gate.unuq, deliv.str, email.id

{
 "event"     : "deliv" 
,"event.dt"  : "дата-время события (Ys)" 
,"account"   : "код аккаунта" 
,"gate.name" : "код шлюза выпуска сообщения" -- api smtp tranz
,"gate.uniq" : "уникальный идентификатор сообщения шлюзе" (null)
,"customer.id" : "идентификатор сообщения назначенный клиентом" (null)
,"issue.id"  : "номер выпуска" (int64)
,"issue.draft.id" : "номер черновика выпуска" (int64)(null)
,"issue.label"  : [ ... ] -- метки выпуска (null)
,"letter.id" : "номер сообщения" (int64)(null)
,"letter.custid" : { ... пользовательские метки ... }
,"email"     : "адрес получателя" 
,"email.id"  : "номер адреса" (int64)(null)

,"deliv.status" : "число-код результата (описано в stat.uni)" (int64)
,"deliv.str"    : "строка ответа smtp-сервера" (null)
}

Событие формы

Если форма заполнена не из сообщения, то связанные с ним параметры известны не будут.

{
 "event"     : "form" 
,"event.dt"  : "дата-время события (Ys)" 
,"account"   : "код аккаунта" 
,"gate.name" : "код шлюза выпуска сообщения" (null) -- api smtp tranz
,"gate.uniq" : "уникальный идентификатор сообщения в шлюзе" (null)
,"customer.id" : "идентификатор сообщения назначенный клиентом" (null)
,"issue.id"  : "номер выпуска" (int64)(null)
,"issue.draft.id" : "номер использованного черновика" (int64)(null)
,"issue.label"  : [ ... ] -- метки выпуска (null)
,"letter.id" : "номер сообщения" (int64)(null)
,"letter.custid" : { ... пользовательские метки ... }
,"email"     : "адрес указанный при заполнении" 
,"email.id"  : "номер адреса" (int64)

,"event.type" : "fill - заполнена, confirm - подтверждена" 
,"form.id"   : "номер формы" (int64)
,"answer"    : {
                "имя поля в форме" : "выбранное значение" 
               ,"имя поля в форме" : "выбранное значение" 
               ,"имя поля в форме" : "выбранное значение" 
               }
,"origin.id" : "номер источника" (int64)(null)
}

Событие трекера

В данный момент событие отправляется для двух процессов - завершение импорта и для выпусков не транзакционных рассылок.

Для транзакционных рассылок - включается отдельно через Службу Поддержки.

Отправляется при получении трекером окончательного статуса (< 0)

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

В случае выпуска рассылки, так же, при назначении выпуску номера выпуска (статус при этом 2) (для длинных выпусков события связанные с выпуском (например доставки, клики) могут начать приходить ранее завершится формирование выпуска и, следовательно, придёт событие завершения с номером выпуска - получится, что есть события от выпуска про который ещё не известно. а так номер выпуска становится известен сразу)

Таким образом, для одного и того же действия событие может прийти более одного раза. Выпуск попал на модерацию (+событие), Служба поддержки его пропустила, при повторном выпуске он был приостановлен пользователем (+событие), потом им возобновлён и закончился успешно (+событие).

{
 "event"     : "tracker" 
,"event.dt"  : "дата-время события (Ys)" 
,"track.id"  : "id трекера" (int64)
,"api.request.id" : "id api-запроса" (null)
,"account"   : "код аккаунта" 

,"obj"   : {
            ... аналогично obj в ответе track.get
           }

-- для выпусков рассылок

,"issue.id"  : "номер выпуска" (int64)(null)

< расширенная информация о выпуске как описано выше > -- в данном случае включается всегда, вне зависимости от настроек

-- для personal. в случае ошибки выпуска может отсутствовать полностью или частично

,"personal.email"  : "адрес получателя" (null) -- по идее дублирует obj
,"personal.letter" : "номер письма"     (int64)(null) -- по идее дублирует obj
,"personal.custid" : { ... пользовательские метки ... } (null)

}

Событие изменения черновика

{
 "event"      : "draft" 
,"event.type" : "create|change|delete" 
,"event.dt"   : "дата-время события (Ys)" 
,"account"    : "код аккаунта" 

,"obj"        : "как obj вызова issue.draft.get" 
,"variables"  : "как variables вызова issue.draft.get" (null)
}

Ответ на письмо рассылки

Обратитесь в Службу Поддержки для настройки.

Если домен отправителя делегирован Сервису, то возможны приём и передача в виде callback ответа на письмо рассылки.

Данные события передаются по одному за вызов, без агрегации.

{
 "event" : "emailreply" 
,"event.dt" : Ys
,"account": "..." 
,"obj" : {
      "mailfrom" : "xxx@mail.ru", -- MAIL FROM протокола SMTP

      "rcpto" : "yyyy@client.tld", -- RCPTO FROM протокола SMTP

      "header" : [ -- unfolded, но не декодированные заголовки по порядку сверху вниз
         [
            "Return-Path",
            "<xxxx@mail.ru>" 
         ],
         [
            "Received",
            "by e.mail.ru with HTTP; Wed, 03 May 2023 14:44:29 +0300" 
         ],
         .......

        ]

      "date" : "Wed, 03 May 4065 14:44:29 +0300", -- unfolded, но не декодированный заголовок Date: письма

      "message-id" : "1c9d44b12d50955e5dc8649b8c77fa6a@client.tld"  -- unfolded, но не декодированный заголовок Message-Id: письма

      "in-reply-to" : "<20200422101022.0.20200422101022_eux_@863148.1155769260.2867413>", -- unfolded, но не декодированный заголовок In-Reply-To: письма

      "delivered-to" : "yyyy@client.tld"  -- unfolded, но не декодированный заголовок Delivered-To: письма

      --  разобранный заголовок To: письма
      "to_email" : "yyyy@client.tld",
      "to_name" : "Имя получателя",
      "to" : [ [ "yyyy@client.tld", "Имя получателя" ] .... ] -- формально, заголовок может содержать несколько адресов

      --  разобранный заголовок From: письма
      "from_email" : "xxxx@mail.ru",
      "from_name" : "Имя отправителя",
      "from" : [ [ "xxxx@mail.ru", "Имя отправителя" ] .... ] -- формально, заголовок может содержать несколько адресов

      "subject" : "Запрос выписки", -- декодированный заголовок Subject: письма

      "html" : "html-версия письма если есть" 

      "amp" : "amp-версия письма если есть" 

      "text" : "текстовая версия если есть" 
      "stripped-text" : "текстовая версия из которой по возможности вырезаны цитаты и подпись" 

      "att" : [ -- атачи письма
               {
                "name" : "название" -- декодировано
               ,"content-type" : "тип атача как было описано в его заголовке" 
               ,"content" : "содержимое атача в base64" 
               },
               .....
              ]

   }
}

Сплит-тестирование

Описание сплит-тестирования

Сплит-тестирование (A/B-тестирование) позволяет выпустить по некоторой части выбранной аудитории несколько писем различающихся между собой чем-либо и по результатам (если охват не 100%) выбрать в ручную или автоматически "лучший" вариант и послать его оставшейся не охваченной части аудитории.

Традиционный вариант использования - A/B-тестирование - предусматривает только два тестовых варианта писем и выпуск победителя.

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

Параметры тестирования

Параметры одного варианта письма

Правила работы

Канал выпуска и аудитория определяется указанной группой.

Тестирование можно выпустить по любому каналу - email, sms, viber, push, vk, tg, vknotify

Тестирование начинается автоматически не ранее даты указанной для запуска самого раннего варианта и если вариантов к этому моменту не менее двух.

Дата запуска какого-либо тестового выпуска или выпуска-победителя может быть пропущена если тестирование в приостановленном состоянии или не хватает вариантов.

Пропущенные запуски будут выполнены как только тестирование будет переведено в активное состояние или добавлен недостающий вариант.

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

Список участников тестирования определяется как все участники указанной группы и формируется в момент или перед запуском первого варианта. В дальнейшем список неизменен вне зависимости от изменения списка участников группы за исключением адресов удаливших себя или внесённых в стоп-лист.

На время формирования списка тестирование находится в состоянии "подготавливается к запуску" и прогресс формирования может быть отслежен через вызов track.get

Данные персонализации каждого участника берутся на момент выпуска варианта.

Содержимое письма и прочие параметры выпуска берутся из указанного черновика на момент выпуска варианта.

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

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

Если участников меньше количества вариантов (при охвате 100%) или меньше количества вариантов + 1 (при охвате не 100%), то тестирование при запуске сразу перейдёт в состояние "завершено". Другими словами - каждому варианту и победителю должен достаться хотя бы один получатель.

Если процент тестового охвата равен 100, то выпуска-победителя не предусматривается.

Если тестирование НЕ в состоянии "ожидает запуска" то менять процент и группу уже нельзя.

Если тестирование НЕ в состоянии "ожидает запуска" то добавлять или удалять варианты писем уже нельзя.

Если тестирование НЕ в состоянии "ожидает запуска", то его нельзя удалить.

Изменить вариант письма можно только если тестирование в состоянии "ожидает запуска" или дата запуска варианта письма ещё не прошла или и тестирование не активно и изменяемый вариант ещё не выпущен.

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

Если тестирование в состоянии "завершено", то можно изменить только его название.

В выпуске А/B-тестирования проверка на работоспособность ссылок ни когда не проводится (а в обычных рассылках она по умолчанию включена), так как такая проверка типичный источник проблем когда одна часть теста выходит, а другая - нет. Но вы можете явно указать у нужных ссылок что их таки надо проверять.

Список сплит-тестирований

{
 "action" : "issue.split.list" 

-- параметры фильтрации, должен быть хотя бы один параметр
--
-- если выбрана последняя порция списка то ответ содержит "last_page" : 1
--
-- доступны поля
--
-- split.id -- "идентификатор сплит-тестирования" 
-- split.name -- "название" 
-- split.percent -- "процент аудитории для тестирования" 
-- split.active -- "активность" -- 0|1
-- split.state -- "состояние тестирования" 
-- split.group.gid -- символический код группы для получения аудитории. не masssending и не personal
-- split.group.name -- название группы

-- split.winner.by -- способ выбора победителя (null,"manual","click","read","unsub")
-- split.winner.after -- момент выбора победителя

-- split.create.time -- "дата создания" (Ys)
-- split.update.time -- "дата изменения" (Ys)
--

,"filter" : [ фильтр в синтаксисе stat.uni ]

,"order" : [ сортировка ответа в синтаксисе stat.uni ]

,"skip" : количество пропускаемых записей от начала списка -- по умолчанию 0

,"first" : количество выбираемых записей после skip -- по умолчанию 50, не более 50

}

ответ

{
 "list" : [
            {
             "id" : номер тестирования

            ,"name" : "название" 

            ,"group" : код группы

            ,"group.name" : название группы

            ,"active" : "активность" 

            ,"state" : "состояние" 

            ,"percent" : "процент тестирования" 

            ,"winner.by" : "способ выбора победителя" 

            ,"winner.after" : "момент запуска победителя" 

            ,"create.time" : "дата и время создания" -- Ys, null

            ,"update.time" : "дата и время последнего изменения" -- Ys, null

            ,"winner.at" : "дата-время" -- Ys, null
                                        -- прогнозируемое время выпуска победителя если он ещё не вышел
                                        -- реальное время выхода победителя если он вышел

            ,"split.variants.at" - [ дата-время, дата-время,... ] - запланированные времена выпуска вариантов Ys
            }

          ,
             ......
          ]
}

Создать сплит-тестирование

{
 "action" : "issue.split.create" 

 ,"name" : "название" 

 ,"group" : "код группы" -- группа для получения аудитории. только группы c типом адресов email. не masssending и не personal.

 ,"percent" : процент аудитории для тестового охвата. от 1 до 100. целое число.

 ,"winner.by" : manual | read | click | unsub  -- способ выбора победителя
                                               -- manual - в ручную
                                               -- read   - больше чтений
                                               -- click  - больше кликов
                                               -- unsub  - меньше отписок
                                               -- игнорируется, если процент равен 100

 ,"winner.after" : "DD hh" -- момент запуска победителя - через сколько дней (DD) и часов (hh) после выпуска последнего
                           -- теста автоматически выбрать победителя
                           -- игнорируется, если процент равен 100 или способ выбора победителя manual

 ,"active" : 0|1 -- активность. 0 - приостановлено. 1 - активно

  -- не обязательно

 ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 - по умолчанию)" 
}

ответ

{
   <общие поля>

 ,"id" : номер тестирования
}

если "return_fresh_obj" : "1" 

ответ -- как на запрос "issue.split.get" соответствующего тестирования

Прочитать сплит-тестирование

{
 "action" : "issue.split.get" 

 ,"id" : номер тестирования
}

ответ

{
   <общие поля>

 ,"id" : номер тестирования

 ,"name" : "название" 

 ,"group" : код группы для получения аудитории

 ,"group.name" : название группы

 ,"percent" : процент аудитории для тестирования

 ,"winner.after" : "DD hh" -- момент выбора победителя.

 ,"winner.by" : "способ выбора победителя" 

 ,"active" : "активность" 

 ,"state" : "состояние тестирования" -- назначается автоматически
             -- 0 - ожидает запуска
             -- 1 - подготавливается к запуску
             -- 2 - запущено
             -- 3 - ожидает выбора победителя
             -- 5 - победитель выбран (да именно 5, а не 4)
             -- 4 - завершено
             -- -1 - ошибки

 ,"create.time" : "дата и время создания" -- Ys, null

 ,"update.time" : "дата и время последнего изменения" -- Ys, null

 ,"winner.at" : "дата-время" -- Ys, null
                             -- прогнозируемое время выпуска победителя, если он ещё не вышел
                             -- реальное время выхода победителя, если он вышел

 ,"parts" : [ -- список вариантов тестовых писем как в issue.split.list
             {
              "id" : номер варианта

             ,"start.at" : "плановое время запуска" 

             ,"issue.at" : "номер выпуска" -- если вариант уже выпущен для теста

             ,"issue.winner" : "номер выпуска" -- если вариант уже выпущен как победитель

             ,"is_winner" : 0|1 - вариант выбран как победитель
             }

            ,{
              ......
             }

             .......
            ]
}

Изменить сплит-тестирование

{
 "action" : "issue.split.set" 

 ,"id" : номер тестирования

-- не обязательные поля. не указанное поле сохранит прежнее значение

 ,"name" : название

 ,"group" : группа для получения аудитории. не masssending и не personal
            -- изменение игнорируется если состояние тестирования не "ожидает запуска" 

 ,"percent" : процент аудитории для тестового охвата.
            -- изменение игнорируется если состояние тестирования не "ожидает запуска" 
            -- при установке в 100 очищаются поля winner.by и winner.after
            -- при установка в не 100 требуется указать winner.by

 ,"winner.by" : способ выбора победителя
            -- изменение игнорируется, если процент равен 100
            -- изменение игнорируется, если состояние тестирования "победитель выбран" или "завершено".
            -- при установке в manual очищается поле winner.after
            -- при установке не в manual требуется указать winner,after

 ,"winner.after" : момент запуска победителя
            -- изменение игнорируется, если процент равен 100 или способ выбора победителя manual
            -- изменение игнорируется, если состояние тестирования "победитель выбран" или "завершено".

 ,"active" : активность
             -- изменение игнорируется, если состояние тестирования "победитель выбран" или "завершено".

 -- не обязательно

 ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 - по умолчанию)" 
}

ответ

{
   <общие поля>
}

если "return_fresh_obj" : "1" 

ответ -- как на запрос "issue.split.get" соответствующего тестирования

Удалить сплит-тестирование

Нельзя удалять тестирование в состоянии "запущено" и выше, так как при таком состоянии уже был как минимум один выпуск.

{
 "action" : "issue.split.delete" 

 ,"id" : номер тестирования
}

ответ

{
   <общие поля>
}

Выпустить победителя

Указывает какой вариант считать победителем и сразу выпускает его.

Допустимо при ручном способе выбора победителя если уже вышли все варианты, но победитель ещё не выбран.

Допустимо при автоматическом способе выбора победителя если уже вышли все варианты и не прошло время автоматического выбора.

Не допустимо если процент тестирования 100.

{
 "action" : "issue.split.winner" 

 ,"variant.id" : номер варианта
}

ответ

{
   <общие поля>

 ,"track.id" : "номер трекера" 

}

Список вариантов писем сплит-тестирования

{
 "action" : "issue.split.variant.list" 

,"split.id" : номер тестирования
}

ответ

{
   <общие поля>

 ,"list" : [
             {
               "id" : номер варианта

               ,"start.at" : "плановое время запуска Ys" 

               ,"issue.at" : "номер выпуска" 

               ,"state" : "состояние" 

               ,"issue.winner" : "номер выпуска"  -- если уже выпущен как победитель

               ,"is_winner" : 0|1 - вариант выбран как победитель

               ,"start.winner.at" : "время запуска Ys"  -- если уже выпущен как победитель

               ,"draft.id" : "номер используемого черновика" 
             }
            ,........
           ]
}

Создать вариант

Нельзя создать новый вариант если тестирование не в состоянии "ожидает выпуска"

{
 "action" : "issue.split.variant.create" 

 ,"split.id" : номер тестирования

-- параметры варианта

 ,"start.at" : "YYYY-MM-DD hh:mm" -- плановое время запуска. Ys

 ,"draft.id" : "номер используемого черновика" 

  -- не обязательно

 ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 - по умолчанию)" 
}

ответ

{
   <общие поля>

 ,"id" : номер варианта тестирования
}

если "return_fresh_obj" : "1" 

ответ -- как на запрос "issue.split.variant.get" соответствующего варианта

Прочитать вариант

{
 "action" : "issue.split.variant.get" 

 ,"id" : номер варианта

}

ответ

{
   <общие поля>

 ,"id" : номер варианта

 ,"split.id" : номер тестирования куда входит этот вариант

 ,"start.at" : "YYYY-MM-DD hh:mm" -- время запуска. дата и время с точностью до минуты

  ,"state" : 0 - ожидает запуска
            2 - запущено задание на выпуск рассылки. issue.at (winner.at) будет заполнено как только при старте рассылки ей будет присвоен номер
           -1 - выпуск завершился ошибкой. подробности в трекере

 ,"draft.id" : "номер используемого черновика" 

 ,"issue.at" : "номер выпуска" -- если вариант уже выпущен для теста

 ,"issue.winner" : "номер выпуска" -- если вариант уже выпущен как победитель

 ,"start.winner.at" : "время запуска Ys"  -- если уже выпущен как победитель

 ,"is_winner" : 0|1 - вариант выбран как победитель

}

Изменить вариант

Изменить вариант письма можно только если тестирование в состоянии "ожидает запуска" или дата запуска варианта письма ещё не прошла или и тестирование не активно и изменяемый вариант ещё не выпущен.

{
 "action" : "issue.split.variant.set" 

 ,"id" : номер варианта

-- параметры варианта, изменятся только явно указанные

 ,"start.at" : "YYYY-MM-DD hh:mm" -- плановое время запуска. Ys

 ,"draft.id" : "номер используемого черновика" 

  -- не обязательно

 ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 - по умолчанию)" 
}

ответ

{
   <общие поля>

 ,"id" : номер варианта тестирования
}

если "return_fresh_obj" : "1" 

ответ -- как на запрос "issue.split.variant.get" соответствующего варианта

Удалить вариант

Нельзя удалять вариант если тестирование не в состоянии "ожидает выпуска"

{
 "action" : "issue.split.variant.delete" 

 ,"id" : номер варианта
}

ответ

{
   <общие поля>
}

вверх

Подписка на веб-пуш

Использовать "Подписка на веб-пуш" очень просто, но ваш сайт должен работать по https

  1. Получите с помощью вызова sys.settings.get значение настройки push.script - это код необходимый для организации подписки
  2. Разместите полученный код на тех страницах сайта где необходимо предлагать подписку на веб-пуш
  3. Выпускайте рассылки с помощью issue.send по подписчикам тип push

Дополнительно, вы можете оформить включение подписчика на веб-пуш ту или иную группу-список - для этого задайте параметр group.id при получении значения настройки push.script. Это можно использовать для автоматической группировки посетителей сайта.

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

Для этого используйте параметр dk при получении кода для страницы, или используйте дополнительные вызовы addData которые можно добавить в полученный из sys.settings.get код push_script перед вызовом subscribe.

Если дополнительные данные известны на момент вызова init, то проще сразу использовать параметр dk.

Вы так же можете настроить "приветственный push" - сообщение которое будет автоматически высылаться каждому новому push-подписчику. Для этого достаточно создать черновик push-сообщения и установить его как приветственный push через параметр issue.push.welcome вызова sys.settings.set

Например:

......
sendsay_push("init", {
  ..........
});

sendsay_push("addData", {"push.name" : "имя подписчика"});
sendsay_push("addData", {"push.region" : "откуда подписчик"});
sendsay_push("addData", {"push.page.cat"  : 1 }); // подписался из раздела про кошек
sendsay_push("addData", {"push.page.duck" : 1 }); // подписался из раздела про уток

sendsay_push("subscribe");

вверх

Рассылки через ВКонтакте

Достаточно подключить сообщество ВКонтакте через веб-интерфейс сервиса - это автоматически подключит соответствующее приложение ВКонтакте для рассылок и произведёт необходимые настройки системы.

Нашему приложению для рассылок потребуются следующие права:

- Управление сообществом - это необходимо, что бы получать списки участников и регистрировать callback-обработчики для последующего получения в реальном времени изменений в составе участников и их состояний

- Доступ к фотографиям - это необходимо, что бы загружать в сообщество изображения и видео которые вы указываете в выпуске

- Доступ к документам - это необходимо, что бы загружать в сообщество прочие файлы которые вы указываете в выпуске

- Доступ к сообщения - это необходимо, что бы реализовывать чат-боты

После регистрации сообщества в системе автоматически появится группа с кодом vk_xxxx (где ххх - номер сообщества в ВК) и названием "Сообщество YYYYYY", автоматически будет произведёт импорт в неё текущих участников и подписчиков сообщества и автоматически запустится процесс синхронизации её состояния с изменениями сообщества в ВК.

Статистика по этой группе (member.list.count) будет отражать текущее состояние сообщества (участников, подписчиков, отписавшиеся, покинувших и т.д.).

Для сбора подписчиков в сообщество установите наше второе приложение обеспечивающее форму подписки.

Так как новые правила ВК требуют, что бы подписчики явно соглашались на получение рассылок, то именно это и обеспечивает наше приложение-форма.

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

Для отсылки сообщений в сообщество используйте обычный вызов выпуска рассылок issue.send

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

Для предоставления удобной статистики система автоматически ведёт списки участников, подписчиков, отписавшиеся и покинувших сообщество с датой события.

Для кажого подписчика типа vk эта информация так же доступна через вызов member.get по ключу данных member.vk

вверх

Телеграм

Создайте бота и подключите его через веб-интерфейс сервиса или напрямую через создание внешней авторизации.

По умолчанию все callback-вызовы Телеграма связанные с этим ботом будут приходить в Sendsay, а уже настроенный ранее бот будет записан в proxy.url и будет получать события от Sendsay - описано ниже.

Однако, если при создании бота, proxy.url указан, то будет использован именно он, а не ранее настроеный бот из Телеграм.

После подключения в системе автоматически появится группа с кодом tg_xxxx (где ххх - номер бота). Автоматический импорт в неё текущих подписчиков бота, в данный момент не возможен из-за отсутствия необходимых функций в Телеграм.

Статистика по этой группе (member.list.count) будет отражать текущее состояние подписчиков бота.

Рассылки через Телеграм

Для отсылки сообщений от имени бота используйте обычный вызов выпуска рассылок issue.send

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

Для предоставления удобной статистики система автоматически ведёт списки подписчиков и отписавшиеся с датой события.

Для каждого подписчика типа tg эта информация так же доступна через вызов member.get по ключу данных member.tg

Чат-Боты

Для организации чат-ботов можно использовать Триггерные Последовательности с событиями Telegram - "Команда Боту" и "Текст Боту".

Проксирование колбеков из Telegram

Если в запросы из Telegram могут быть проксированы системой далее к вам если настроить proxy.url в параметрах внешней авторизации Telegram.

При создании бота, уже имеющийся в Telegram бот будет автоматически сохранён в proxy.url, если proxy.url не указан явно.

Проксирование запросов не зависит от их обработки системой. При настроенном проксировании пришедшие запросы независимо и обрабатываются системой и передаются далее.

При ошибках проксирования выполняютсяповторы аналогично схеме, как это делает Telegram - попытки достучаться через 1, 2, 4, 8, 16, 32, 60 секунд, далее через 2, 4, 8 минут.

Обработка команд

По умолчанию, наш бот отвечает на любые команды, включая /start и /stop. Вид ответов можно настроить как описано ниже.

Одновременно, все команды передаются к вам если настроено проксирование.

Ответы нашим ботом могут быть неудобны, если вы хотите сами отвечать на такие команды.

Для запрета нашему боту отвечать на что либо, установать reply_disabled:1 в его внешней авторизации.

Наш бот будет принимать команды и обрабатывать их, но не отвечать.

Исполнение команд ботов

Команды могут быть переданы боту разными способами:

Поступающие команды ботам обрабатываются путём отсылки ответа который берётся из подходящего черновика.

Для поиски используется алиас черновика, по формату:

<bot_name>/<command>,<language_code>

и если такого нет, то

<bot_name>/<command>

где <language_code> - двухбуквенный код языка пользователя пришедший из Telegramm

Примеры:

"my_bot/start,ru"

"my_bot/start"

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

Если не найден ни один черновик, ответ не формируется, за исключением команд /start и /stop для которых тогда отправляется стандартный системный текст.

Так же, команды /start и /stop осуществляют подписку на бота и отписку от него.

вверх

Целевые страницы

Сервис "Целевые страницы" предназначен для отслеживания страниц вашего сайта по которым прошёл подписчик после перехода из письма рассылки и определения дошёл ли он до куда вы планировали. При этом вы можете дополнительно добавлять/изменять хранимые в анкетах данные подписчика.

Сервис "Целевые страницы" не имеет своих специальных вызовов API, но используя вызовы issue.send, stat.uni и sys.settings.* вы получите всю интересующую вас информацию.

Использовать "Целевые страницы" очень просто.

  1. Получите с помощью вызова sys.settings.get значение настройки target.script - это код необходимый для отслеживание пути подписчика.
  2. Разместите полученный код на тех страницах сайта, за посещением которых при переходе из писем рассылок вы хотите следить.
  3. Выпускайте рассылки с помощью issue.send обязательно с использование преобразования ссылок.
  4. Все страницы на которых вы разместили код отслеживания и до которых дошёл из выпусков хоть один ваш подписчик будут автоматически созданы в базе с relref=-2.
  5. Альтернативно, при настройке передачи email, в базу будут вноситься данные не только о тех кто посетил вам сайт из рассылки, но и о тех кто просто посетил ваш сайт если вам известны их адреса (описано ниже)
  6. О каждом посещении такой страницы в базе будет автоматически создана запись о "клике" с запоминанием кто, из какой рассылки, когда и с какого адреса это произошло.
  7. Так же будет сгенерировано событие "Целевая страница достигнута"
  8. Используя вызов Универсальной Статистики делайте выборки по кликам (click.*) с типом ссылки -2 (click.link.reltype = -2) вы получите полную информацию о том кто, когда, из какого выпуска и что посетил на вашем сайте.

Если вы хотите отслеживать не только пользователей пришедших из рассылки, но прочих ваших посетителей для которых вы знаете их email, вам необходимо при получении настройки target.script указать дополнительный параметр email = 1, а при генерации страницы сайте заменять в полученном коде метку подстановки адреса на реальный адрес.

Возможно вы захотите дополнительно настроить параметр "Время отслеживания после перехода" (sys.settings.get/set, параметр target.cookie.ttl) - он хранит значение в минутах как долго после перехода из письма система будет отслеживать посещение страниц вашего сайта.

В данный момент значение этого параметра по умолчанию равно 360 - 6 часов.

Дополнительно, вы можете оформить включение пользователя достигшего целевой страницы в ту или иную группу-список - для этого задайте параметр group.id при получении значения настройки target.script. Это можно использовать для автоматической группировки посетителей целевых страниц сайта, скажем, по их принадлежности к тому или иному разделу сайта. (Обратите внимание, что в логе кликов и логе устройств у записи номер выпуска и номер письма в выпуске будут равны 0).

Так же вы можете оформить включение создаваемой ссылки сразу в ту или иную группу ссылок - для этого задайте параметр link.group.id при получении значения настройки target.script. Это можно использовать для автоматической группировки целевых страниц сайта, скажем, по их принадлежности к тому или иному разделу сайта. Настоятельно рекомендуется при создании таких групп указывать им reltype = -2.

Так же вы можете указать в вызове sys.settings.get как дополнить/изменить хранимые в анкетах данные подписчика.

Прочие дополнительные параметры для target.script описаны в вызове sys.settings.get

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

При отслеживании посетителей на основе предоставления адресов Cookie не используются.

вверх

Статистика

География и устройство подписчика

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

Эта информация доступна через объекты вызова Универсальной Статистики: deliv.member.geo.*, deliv.member.gadget.*, click.member.geo.*, click.member.gadget.*, read.member.geo.*, read.member.gadget.*

Устройство подписчика

Для устройства подписчика определяются:

- Тип устройства - Desktop, Mobile, Tablet, Proxy или Robot

- Операционную систему - Linux, Android, Windows, iOS, Mac, Blackberry, Proxy или Other

- Семейство браузера - Firefox, Chrome, Opera, Edge, Yandex, Outlook, Safari, IE, Blackberry, Proxy или Other

- Старшую часть версии браузера

Если устройство определено как Robot, то его операционная система всегда Other, браузер - Other, версия - 0.

Если устройство определено как Proxy, то его операционная система всегда Proxy, браузер - Proxy, версия - 0 - это веб-почта забрала картинку вместо настоящего браузера подписчика

Используйте deliv.member.gadget.*, click.member.gadget.*, read.member.gadget.* вызова Универсальной Статистики.

География подписчика

География определяется с точностью до зарубежной страны и с точностью до субъекта федерации для России.

География хранится как одно число совмещающее в себе номер страны и номер региона в виде СССRR000

Для нумерации стран (CCC) используется ISO 3166-1.

Для нумерации субъектов федерации России (RR) - первый уровень классификатора ОКАТО/ОКТМО.

Таким образом код зарубежной страны это код по ISO * 100000, а код для России это 64300000 + код региона * 1000

Не определившаяся страна имеет код 0. Не определившийся регион России имеет код региона 0.

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

Используйте issue.geo.*, deliv.member.geo.*, click.member.geo.*, read.member.geo.* вызова Универсальной Статистики.

Ссылки с полезной информацией и таблицами кодов:

http://www.iso.org/iso/ru/country_codes
https://www.iso.org/obp/ui/#search
http://ru.wikipedia.org/wiki/ISO_3166-1
http://ru.wikipedia.org/wiki/Субъекты_Российской_Федерации
http://ru.wikipedia.org/wiki/ОКАТО
http://ru.wikipedia.org/wiki/Общероссийский_классификатор_территорий_муниципальных_образований

вверх

Универсальная статистика

Универсальная статистика позволяет получить информацию про переходы, открытия писем, тиражи выпусков и результаты доставки. Примеры использования вынесены в отдельный раздел.

Не смотря на то, что этот вызов позволяет получать информации по конкретному письму и таким образом повторяя запросы следить за его доставкой, чтением и кликами он предназначен не для этого, а для выборок статистики по многим объектам за раз. Для отслеживания событий с письмом в реальном времени предназначены Callback/Webhook. Использование же этого вызова в таких целях может блокировать без предупреждения.

При использовании кэширования запоминаются сами данные, а способ возврата результата задаваемый в "result" и "caption" применяется к ним позже.

При использовании кэширования в режиме "fetch" необходим только параметр "result" и связанные с ним дополнительные параметры так как при отсутствии данных в кэше вычисление результата производиться не будет.

При асинхронном запуске обработку можно прекратить вызовом track.set


{

 "action" : "stat.uni" 

,"cache" : { параметры кэширования }

---- Параметры расчёта

-- или Единичный запрос

,"skip" : число пропускаемых строк данных отчёта от начала. не обязательно. по умолчанию 0

,"first" : число выбираемых строк после пропуска skip. не обязательно. по умолчанию все

,"select" : [ -- обязательно
            --
            --     список полей для выборки
            -- или список полей и функций агрегирования
            -- или список полей и функция unique(*)
            --
            -- указывается или в виде строки
            --
            --  "поле или функция" 
            --
            -- или в виде объекта с картой преобразования для приведения полученных значений к "человеческому виду" 
            -- (например код недоставки можно преобразовать в его словесное описание)
            --
            -- {
            --  "field" : "поле или функция" 
            --
            --  карта преобразования значений одим из двух способов
            --
            -- ,"map" : { -- непосредственно в вызове
            --           "Y" : "Да" 
            --          ,"N" : "Нет" 
            --          ...........
            --          }
            --
            -- или
            --
            -- ,"map" : "Название карты" -- название заранее созданной карты
            --
            -- и к обоим способам управление не найденным значением
            --
            -- ,"map.missing" : "Непонятно" -- не обязательно. чем заменить значение не имеющее соответствия в карте преобразований
            --                              -- если отсутствует, то значение пропускается как есть
            --  }
            --
            --
            -- поля типа дата могут содержать указания на точность в виде двух букв из набора (Y M D h m s)
            -- по умолчанию поля типа дата имеют точность Ys или YD - в зависимости от их назначения
            --
            -- например: dt:YM (дата от года до месяца), dt:Yh (дата от года до часа), dt:YY (год)
            -- не верно: dt:hY  - первый уточнитель (час) младше второго (год)
            --
            -- поля типа дата могут содержать указания на то что требуется получить
            --    номер дня в неделе  - dt:DOW
            --    номер дня в году    - dt:DOY
            --    номер недели в году - dt:WOY
            --    первый день недели  - dt:CW1D
            -- от значения даты
            --
            -- поле с постоянным значением задаётся как
            --
            -- сonst(-123.456)  -- число
            -- const("АБ ВГ Д") -- стока
            -- const.ID         -- идентификатор аккаунта
            -- const.сurrent    -- текущее время. описано ниже
            --
            -- функции преобразования полей - допустимы только в select
            --    md5_hex(поле)
            --    md5_base64(поле)
            --
            -- допустимые функции агрегирования
            --    count(*)
            --    count(unique имяполя)
            --    max(имяполя)
            --    min(имяполя)
            --    avg(имяполя)
            --    sum(имяполя)
            --    range(имяполя)
            --    stdev(имяполя)
            --    variance(имяполя)
            --
            -- в функциях перед именем поля может использоваться префикс unique
            --
            -- поле не может быть и в списке выбора и при этом использоваться
            -- в функциях агрегирования (даже с разным заданием точности)
            --
            -- правильные примеры
            --   a,b,c
            --   a,b,avg(c),max(d)
            --   dt:YM,b,min(c),max(c)
            --   a,b,count(*),min(e)
            --   max(dt:YD),min(y)
            --   max(f),min(f)
            --   a,b,unique(*)
            --   a,b,count(unique c)
            --   const(23)
            --
            -- не правильные примеры
            --
            --   a,b,avg(a)           -- поле а и в выборке и в агрегировании
            --   a,b,unique(*),avg(e) -- функция unique(*) не совместима ни с одной другой функцией
            --   unique(*)            -- unique(*) без списка полей
            --   const(ABC)           -- строка не в кавычках
            --   сonst(123E+10)       -- запись с экспонентой
          ]

,"filter" : [

           -- фильтр результатов, не обязательно
           --
           -- все элементы списка объединяются через "И" 
           --

           -- элементы условий фильтрации

           -- если имя поля содержит указание точности даты, то
           -- значение для сравнения большей точности будут
           -- автоматически сокращены. значения с меньшей точностью
           -- считаются ошибкой
           --
           -- пример: "2010-05-04 12:13:14" станет "2011-05" для точности YM
           -- не верно: "2010-05-04" не хватает точности для Ys
           --
           -- поля типа дата кроме констант могут так же сравниваться с текущим
           -- временем +/- сдвиг. это описано ниже.

           {

             "a"  : имя поля или функция агрегирования

            ,"op" : операции сравнения ( == != < <= > >= )

            ,"v"  : значение
           }

          ,{

             "a"  : имя поля

            ,"op" : операции (не-)вхождения в список (in !in)

            ,"v" : [ значение, значение, ...] список значений для проверки вхождения (in) или не вхождения (!шт)
           }

          ,{

             "a"  : имя поля

            ,"op" : операции (не-)определённости (is_null !is_null)
                    только для полей помеченных (null)
                    параметр "v" должен или отсутствовать или быть равен "" 
           }

           .........

           -- группировка условий ИЛИ

          ,{

            "op" : "OR" 

            "v" : [
                    массив описывающий элементы фильтра. "ИЛИ" между элементами
                  ]

           }

          ,{

            "op" : "!OR" 

            "v" : [
                    массив описывающий элементы фильтра. "ИЛИ" между элементами потом отрицание полученного результата
                  ]

           }

           -- группировка условий И

          ,{

            "op" : "AND" 

            "v" : [
                    массив описывающий элементы фильтра. "И" между элементами
                  ]

           }
          ,{

            "op" : "!AND" 

            "v" : [
                    массив описывающий элементы фильтра. "И" между элементами потом отрицание полученного результата
                  ]

           }

          ]

"have" : [
           -- дополнительный фильтр результатов после выполнения функций агрерирования из select, не обязательно

           -- синтаксис как у filter
           --
           -- использовать можно только такие простые поля как точно указанные в select или функции агрегирования
           --
           -- например, выпуски после 1 декабря у которых более 10 кликов
           --
           --
           -- "select" : [ "click.issue.id" ]
           -- "filter" : [ { "a" : "click.issue.dt:YD", "op" : ">", "v" : "2019-12-01" } ]
           -- "have"   : [ { "a":"count(*)"  ,"op":">"  ,"v": 10 } ]
  ],

,"order" : [

           -- упорядочивание результата, не обязательно
           --
           -- список полей и функций агрегирования
         --
           -- префикc "-" задаёт сортировку по убыванию
           -- префикс "+" или его отсутствие - по возрастанию
           --
           -- a,-b
           -- +b,-avg(z)
           -- -a,+b,-c
           -- +dt:YM,-c

         ]

-- или Запрос с объединением

-- Результаты всех единичных запросов объединяются в одну строку ответа по уникальным ключам по порядку выполнения запроса.
-- Если в каком-либо запросе результатов для уникального ключа нет, то в строку добавляется соответствующее число значений null.
-- Если в результате строки с уникальным ключом встречается больше одного раза, то используется только первый результат, а остальные отбрасываются.
-- Дублирование уникального ключа в объединённой строке не происходит, так как он удаляется из результатов второго и далее запросов перед их добавкой в объединённую строку.
-- Смотрите пример запроса с объединением в разделе "Примеры запросов универсальной статистики" 

,"join" : [ -- объединяемы запросы. обязательно
           { Единичный запрос-1 }
          ,{ Единичный запрос-2 }
          ,{ Единичный запрос-3 }
          ....
          ]

,"joinby" : "число" -- обязательно. больше или равно 0
                     -- размер уникального ключа для объединения результатов из join
                     -- 0 - без ключа. объединяются все колонки. (например вы выбираете min() из нескольких объектов)
                     -- 1 - первая колонка каждого результата единичного запроса
                     -- 2 - первая и вторая
                     -- 3 - первая, вторая и третья
                     -- и так далее

,"order" : [        -- сортировка результата объединённого запроса. не обязательно
                    -- где "правило" это направление сортировки (- или +), номер колонки результата (с единицы),и способ сравнения (s - строчный, n - числовой)
                    -- например  "-1n","+2s","-4s","+5n" 
        "правило1",
        "правило2,
        .....
]

---- Прочие параметры

-- заголовки столбцов
-- предназначены для создания заголовков в csv и xlsx
-- содержимое и количество элементов не анализируется, вы можете задать заголовков и меньше и больше количества столбцов данных

,"caption" : [ -- не обязательно
              "адын" 
             ,"дыва" 
             ,"тры
              ......
             ]

,"track.info" : "дополнительная информация которая будет потом доступна в track.list/get" -- строка 1024 байта. не обязательно

,"result" : [ способ возврата результата. смотрите общее описание ]
}

ответ


{
  <общие поля>

-- для result != response

 ,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

-- для result = response

 ,"list" : [

          [строка1,данные в порядке заданном в select]

          [строка2,данные в порядке заданном в select]

          [строка3,данные в порядке заданном в select]

          ........

        ]

}

День недели, день года и неделя года, первый день недели

У полей типа дата можно указать что требуется не сама дата, а производные от неё величины:

Номер дня в неделе

Запись dt:DOW означает получение номера дня в неделе от значения даты.

Результатом является число от 1 до 7 - Пн -1 .... Сб - 6, Вс -7

В условиях понимает также 0 как воскресенье и английские полные названия дней, а так же их английские трёх и двух буквенные аббревиатуры (регистр не важен)

Номер дня в году

Запись dt:DOY означает получение номера дня в году от значения даты.

Результатом является число от 1 до 366.

Номер недели в году

Запись dt:WOY означает получение номера недели в году на которую приходится значения даты.

Результат возвращается как YYYY-Www, где YYYY - год, ww - номер недели от 01 до 53.

Номер недели определяется по ГОСТ ИСО 8601-2001: первой неделей года считается та, в который есть первый четверг года. (для григорианского календаря - та где 4 января).

Таким образом, начальные дни нового года могут быть в последней неделе прошлого года. И последние дни прошлого года - в первой неделе нового года.

Например:

2000-12-31 даст 2000-W52 - конец года в его последней неделе

2001-12-31 даст 2002-W01 - конец года в первой неделе следующего года

2012-01-01 даст 2001-W52 - первый день года в последней неделе предыдущего года

2012-01-02 даст 2002-W01 - а второй уже в первой неделе своего года

2014-05-04 даст 2014-W18 - просто дата на 18 неделе года

Первый день текущей недели

Запись dt:CW1D означает получение даты первого дня недели на которую приходится значения даты.

Результат возвращается как дата с точностью до дня YYYY-MM-DD

Начало недели определяется по ГОСТ ИСО 8601-2001: понедельник.

Константа текущее время

Синтаксис и поведение аналогично использованию current в сравнении.

const.сurrent:HL +Y year +M month +D day +h hour round +m minute round +s second

Точность по умолчанию всегда Ys если она не задана явно необязательным уточнением :HL

Где H - точность старшего компонента (Y M D h m s)
Где L - точность младшего компонента (Y M D h m s)

Например

const.current - 1 day - 1971-04-05 06:07:08

const.current:YM - 1 day - 1971-04-05

Сравнения с текущим временем

Для полей типа дата возможно сравнение с текущим временем +/- сдвиг.

Для этого значение v записывается в виде

"сurrent +Y year +M month +D day +h hour round +m minute round +s second"

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

Знак сдвига обязателен и может быть "+" (как в примере) или "-".

К названию величины можно добавлять на конце "s"

Указатели округления часов и минут - round - не обязательны.

Результат вычисления current всегда неявно приводится к точности поля заданном в ключе "a"

Например, в условии

issue.dt:YM < current - 1 day

точность результата "current - 1 day" неявно преобразуется к YM.

Вычитание годов и месяцев

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

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

Например, если сегодня 30е марта, то вычитание 1го месяца даст февраль, а 30е февраля было ранее только в 1712 году и следующий раз может быть, предположительно, в 3328 году.

Например, если сегодня 31 декабря, то вычитание 3х месяцев даст 31 сентября.

Например, если сегодня 29 февраля, то вычитание 1 года даст 29 февраля прошлого года, который явно не високосный.

Строго формально всё ещё хуже - бывает 61я секунда в минуте (не так уж и редко, последние разы 31 декабря 2008 23:59:60 и 30 июня 2012 23:59:60) и 23 или 25 часов в сутках (при переходе на и с летнего времени час или выпадает или добавляется, и без указания временной зоны один час может или пропасть или встретиться дважды). Но это системой не учитывается.

Вычитание дней

Вычитание/прибавление 1 дня можно записать как вычитание/прибавление 24 часов, поэтому вычитание дней тоже округляет результат что иметь возможность установить время в 0:0:0.
А вычитание/прибавление часов - не округляет.

Округление часов

Округление часов указываемое словом round необходимо когда временной отрезок должен начинаться точно от начала часа.

Без округления результат сохранит текущие минуты и секунды дав время "на N часов назад/вперёд"

С округлением минуты и секунды будут установлены в 0 что даст время "начало часа N часов назад/вперёд"

Округление минут

Округление минут работает аналогично округления часов - секунды устанавливаются в 0 и вы получаете точно начало минуты.

Правило вычисления current

точность даты понижается до года-месяца

к/из полученной даты прибавляются/вычитаются указанное количество годов и месяцев

результат преобразуется обратно к точности год-секунда установкой первого числа 0 часов 0 минут 0 секунд

точность даты понижается до года

к/из полученной даты прибавляются/вычитаются указанное количество лет

результат преобразуется обратно к точности год-секунда установкой первого месяца первого числа 0 часов 0 минут 0 секунд

точность даты понижается до года-месяца

к/из полученной даты прибавляются/вычитаются указанное количество месяцев

результат преобразуется обратно к точности год-секунда установкой первого числа 0 часов 0 минут 0 секунд

точность даты понижается до года-месяца-дня

к/из полученной даты прибавляются/вычитаются указанное количество дней

результат преобразуется обратно к точности год-секунда установкой 0 часов 0 минут 0 секунд

с округлением

точность даты понижается до года-месяца-дня-часа

к/из полученной даты прибавляются/вычитаются указанное количество часов

результат преобразуется обратно к точности год-секунда установкой 0 минут 0 секунд

без округлением

к/из полученеой даты прибавляются/вычитаются указанное количество часов

с округлением

точность даты понижается до года-месяца-дня-часа-минуты

к/из полученной даты прибавляются/вычитаются указанное количество минут

результат преобразуется обратно к точности год-секунда установкой 0 секунд

без округлением

к/из полученной даты прибавляются/вычитаются указанное количество минут

Примеры

Примеры простые

сейчас

current

cейчас минус 1 час 3 минуты плюс 4 секунды

current -1 hour -3 minutes +4 second

сейчас минус 40 дней назад плюс 78 часов

current -40 days + 78 hours

что-то произошло за два последних часа

dt >= current - 2 hours

что-то произошло за два последних часа и только за них не считая текущий час

dt >= current - 2 hours round and dt < current - 0 hours round

выпуск состоялся в прошлом месяце (ниже есть пример считающийся быстрее)

issue.dt:YM == current - 1 month

выпуск состоялся в прошлом месяце и ранее (ниже есть пример считающийся быстрее)

issue.dt:YM <= current - 1 month

выпуск состоялся вчера

issue.dt:YM == current - 1 day

выпуск состоялся за последние 24 часа

issue.dt:YM > current - 24 hours

выпуск состоялся начиная с 5 числа прошлого месяца и позже
обратите внимание, что вычитается 4 дня - так как вычитание месяца установит день в 1, то для получения 5го числа надо прибавить 4, а не 5

issue.dt >= current - 1 month + 4 days

выпуск состоялся начиная с 3 числа текущего месяца и позже
обратите внимание, что вычитание ноля месяцев использовано для сброса дня в 1 и уже потом коррекции его до 3

issue.dt >= current - 0 month + 2 days

Примеры, если вы хотите что бы считалось быстрее (но записывать зато сложнее):

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

выпуск состоялся в прошлом месяце и ранее
"current - 0 month" даст начало текущего месяца, а вычитание "1 second" даст последнюю секунду прошлого месяца

issue.dt <= current - 0 month - 1 second

выпуск состоялся ровно в прошлом месяце
"current - 1 month" даст начало прошлого месяца
"current - 0 month" даст начало текущего месяца, а вычитание "1 second" даст последнюю секунду прошлого месяца

issue.dt >= current - 1 month И  issue.dt <= current - 0 month - 1 second

Примеры быстрого сравнения для самостоятельного понимания:

выпуск состоялся с 4 мая до 20 августа этого года

issue.dt >= current - 0 year + 4 month + 3 days И  issue.dt < current - 0 years + 7 month + 19 days

выпуск состоялся с 4 мая по 20 августа этого года

issue.dt >= current - 0 year + 4 month + 3 days И  issue.dt <= current - 0 years + 7 month + 20 days - 1 second

Статистика с точностью до каждого email/sms/viber/push/vk/tg/vknotify

Каждый email/sms/viber/push/vk/tg/vknotify имеют уникальный идентификатор состоящий из номера выпуска и номера письма в выпуске. Поэтому вы можете получить статистику с точностью до каждого email/sms/viber/push/vk/tg каждого выпуска для каждого адресата.

Доступные для использования поля

Поля типа дата (помечены dt), временные константы и "текущее время" задаются временем московского часового пояса (с неявным подразумеванием зимнего или летнего времени).

Отметка dt:Ys обозначает, что поле имеет значение времени с точность от года (Y) до секунды (s).

Отметка (null) обозначает, что поле может иметь неопределённое значение.
Например если выпуск рассылки не использовал ни какой черновик, то значение поля issue.draft.id будет не определено.

Все логические операции с полем имеющим неопределённое значение всегда не выполняются.
Например, неопределённое значение номера черновика не попадёт под фильтр issue.draft.id > 10, но так же оно не попадёт и под фильтр issue.draft.id <= 10.

Запись xxx.yyy.* обозначает что доступны все поля объекта yyy.

Например, при наличии у объекта yyy поля zzz, можно его получить через xxx.yyy.zzz

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

Например, так как в объекте Клик доступны все поля объекта Выпуск (cliсk.issue.*), а в объекте Выпуск - объекта Группа (issue.group.*), то можно получить значение поля "Название группы из выпуска которой был этот клик" - click.issue.group.name

Так же доступны, но не продублированы здесь, все поля от фильтроввызовов *.list использующих синтаксис stat.uni

Информация о домене


domain.id - id домена

domain.name - название домена

Информация про адрес

Вне зависимости от наличия в базе подписчика некоторая информация о его адресе доступна для дальнейшего получения

email.id - id адресе

email.type - тип идентификатора адреса
            - 0 - email
            - 1 - msisdn
            - 2 - csid
            - 3 - push
            - 5 - vk
            - 6 - viber
            - 8 - tg
            - 9 - vknotify

email.addr_type - строка типа идентификатора адреса
            - email
            - msisdn
            - csid
            - push
            - vk
            - viber
            - tg
            - vknotify

email.email - значение идентификатора адреса
             - при фильтрации по этому полю понимается не обязательный параметр addr_type
             - если он указан, то сравниваемые значения из v сначала нормализуются в соответствии с указанным типом
             - если нормализация удалась - используется нормализованное значение, если не удалась - оригинальное

email.domain.* - информация о домене адреса

-- информация об ошибках доставки. все поля null если последняя доставка была успешной.

email.error.error - количество ошибок доставки (null)

email.error.dt    - дата последней ошибки доставки (YYYY-MM-DD hh:mm:ss) (null)

email.error.str   - текст последней ошибки доставки (null)

email.error.issue -- выпуск последней ошибки доставки
email.error.letter -- письмо последней ошибки доставки

email.error.lock  - 0|1|null - адрес заблокирован (1) из-за ошибок (в зависимости от количества и типа ошибок) (null)
                              - заблокированный адрес не участвует в рассылке и попадает в статистику как hardbounced

email.error.lock_issue -- выпуск приведший к блокировке
email.error.lock_letter -- письмо приведшее к блокировке

Информация о подписчике

Доступна только при его наличии в базе


member.id - id подписчика

member.type - тип идентификатора подписчика
            - 0 - email
            - 1 - msisdn
            - 2 - csid
            - 3 - push
            - 5 - vk
            - 6 - viber
            - 8 - tg
            - 9 - vknotify

member.addr_type - строка типа идентификатора подписчика
            - email
            - msisdn
            - csid
            - push
            - vk
            - viber
            - tg
            - vknotify

member.email - значение идентификатора подписчика (email,телефон,csid)
             - при фильтрации по этому полю понимается не обязательный, но настоятельно рекомендуемый, параметр addr_type
             -
             - без него происходит нормализация значения параметра в 'v' как адреса email, что подходит для адресов почты и номеров телефонов
             - не не должно применять к другим типам идентификаторов так как они регистрозависимы или имеют другие правила нормализации
             -
             - так же, без addr_type, не происходит ограничений по типу адреса, что может вызвать не нужное совпадение
             - например номер телефона для msisdn и viber и vknotify может быть одинаковый и запрос без указания типа адреса данные будет найдены
             - для обоих типов идентификаторов

             - поэтому рекомендуется всегда указывать тип идентификатора если мы работаете не только с email и msisdn

member.haslock - блокировка подписчика (доступно так же и через обычный вызов member.get). может ли получать сообщения
                  0 - нет            - может
                  1 - отписался      - не может
                  2 - не подтверждён - не может
                  4 - имеет фатальные ошибки доставки - не может
                  прочие значения ( 3,5,6,7 ) - одновременное наличие нескольких указанных блокировок

member.create.time - дата время создания данных подписчика (Ys) (null)
member.create.host - источник создания (null)

member.update.time - дата время последнего изменения данных подписчика (Ys) (null)
member.update.host - источник изменения (null)

member.import.time - дата и время последнего изменения данных подписчика при импорте (Ys) (null)

member.confirm.time - дата время подтверждения регистрации (Ys) (null)
member.confirm.host - источник подтверждения (null)

member.domain.* - информация о домене подписчика

member.last.tz  - информация о временной зоне последнего действия подписчика (как member.get) (null)

-- информация об ошибках доставки. все поля null если последняя доставка была успешной.

member.error.error - количество ошибок доставки (null)

member.error.dt    - дата последней ошибки доставки (YYYY-MM-DD hh:mm:ss) (null)

member.error.str   - текст последней ошибки доставки (null)

member.error.issue -- выпуск последней ошибки доставки
member.error.letter -- письмо последней ошибки доставки

member.error.lock  - 0|1|null - адрес заблокирован (1) из-за ошибок (в зависимости от количества и типа ошибок) (null)
                              - заблокированный адрес не участвует в рассылке и попадает в статистику как hardbounced

member.error.lock_issue -- выпуск приведший к блокировке
member.error.lock_letter -- письмо приведшее к блокировке

-- информация из данных подписчика

member.datakey.DATAKEY - значение ответа как при использовании member.get с таким же ключом данных
                       - *в данный момент допустимо только в select*
                       - *в данный момент не может быть параметром функций агрегации*

-- информация из анкет подписчика

member.anketa.ANKETA.QUEST - значение ответа QUEST из анкеты ANKETA.
                           - при отсутствии ответа на указанный вопрос результат null
                           - *в данный момент допустимо только в select*
                           - *в данный момент не может быть параметром функций агрегации*

-- информация о последнем заполнении форм

member.form.id         -- номер формы

member.form.name       -- название формы

member.form.dt_fill    -- дата последнего заполнения

member.form.dt_confirm -- дата подтверждения (null)

member.form.resp.dk    -- ключ данный ответа в анкетах подписчика

member.form.resp.name  -- название вопроса на который дан ответ

member.form.resp.type  -- тип вопроса на который дан ответ

member.form.resp.value -- значение ответа. для вопросов с выбором это название варианта ответа. для других - сам ответ.

member.form.resp.id    -- код ответа. для вопросов с выборов это код ответа. для других - null.

member.form.issue.id  - номер выпуска из которого заполнена форма (null)

member.form.issue.draft.id - номер черновика использованного в выпуске (null)

member.form.issue.draft.name - название черновика использованного в выпуске (null)

member.form.issue.draft.alias - альтернативный идентификатор черновика использованного в выпуске (null)

member.form.letter.id - номер письма из которого заполнена форма (null)

member.form.letter.dt - дата письма из которого заполнена форма (null)

member.form.letter.custid(element_name) - значение элемента element_name пользовательской метки письма из которого заполнена форма (null)

Существует специальная особенность связанная с выборкой данных подписчика.

Пока вы используете в запросе поля id и email, вы получаете данные о всех подписчиках.

Как только в запросе появляется поле haslock, вы начинаете получать данные только о тех подписчиках, запись о которых до сих пор есть в вашей базе.

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

Пример когда проявится разница - запрос адресов участвовавших в выпуске ("select":["member.email"]) вернёт все адреса попавшие в выпуск, а такой же запрос с дополнительной выборкой текущего состояния блокировки ("select":["member.email", "member.haslock"]) вернёт только тех, кого вы ещё не удалили из своей базы.

Эту особенность можно использовать специально для отсечения тех кто из базы уже удалён. Фильтр member.haslock >= 0 выберет подписчиков с любым состоянием блокировки, но приведёт к исключению из результатов тех кто уже удалён.

История операций с головами

Отражается история действий с голова в операциях которые присоединяют, отсоединяют, передают головы между телами.

niptuck.dt - Ys
niptuck.action - attach,detach,detachsplit,transplant

операция attach

niptuck.email.dataset - получатель. тело
niptuck.email.email - получатель. идентификатор
niptuck.email.type - получатель. тип идентификатора. число
niptuck.email.addr_type - строка

niptuck.head.email - присоединённый идентификатор
niptuck.head.type - тип присоединённого идентификатора. число
niptuck.head.addr_type - строка

операция detach

niptuck.email.dataset - донор тело
niptuck.email.email - отсоединённый идентификатор
niptuck.email.type - тип отсоединённого идентификатора. число
niptuck.email.addr_type - строка

операция detach+split

niptuck.email.dataset - донор тело
niptuck.email.email - отсоединённый идентификатор
niptuck.email.type - тип отсоединённого идентификатора. число
niptuck.email.addr_type - строка

niptuck.head_dataset - новое тело отсоединённого идентификатора. число

операция transplant

niptuck.email.dataset - получатель. тело
niptuck.email.email - получатель. идентификатор
niptuck.email.type - получатель. тип идентификатора. число
niptuck.email.addr_type - строка

niptuck.head.dataset - донор. тело
niptuck.head.email - переданный идентификатор
niptuck.head.type - тип переданного идентификатора. число
niptuck.head.addr_type - строка

операция replace

записывается как detach и attach

События обогащения данных

tde.dt               -- время события  Ys
tde.topic            -- код тематики тематика
tde.price            -- стоимость данного события

tde.member.id,       -- номер идентификатора подписчика
tde.member.addr_type -- строка типа идентификатора подписчика
tde.member.email     -- идентификатор подписчика
tde.member.datase    -- номер набора данных подписчика
tde.domain.name      -- домен идентификатора подписчика

Информация o группе


group.id - id группы

group.gid - символический код группы

group.name - название группы

group.type - тип группы -- list|filter

group.addr_type - тип адресов -- email|msisdn|viber|csid|push|vk|tg|vknotify

-- регулярно обновляемы кэш статистики по всем выпускам вышедшим по группе

-- для email-групп

group.stat.total.email.all.issues -- выпусков рассылок

group.stat.total.email.all.members -- получателей

group.stat.total.email.all.u_members -- уникальный получателей

group.stat.total.email.all.clicked -- кликов

group.stat.total.email.all.u_clicked -- уникальных кликов

group.stat.total.email.all.target -- достижений целевых страниц

group.stat.total.email.all.u_target -- уникальных достижений целевых страниц

group.stat.total.email.all.readed -- чтений

group.stat.total.email.all.u_readed -- уникальный чтений

group.stat.total.email.all.unsub -- отписок из писем выпусков

group.stat.total.email.all.u_unsub -- уникальных отписок из писем выпусков

-- для sms-групп

group.stat.total.sms.all.issues -- выпусков рассылок

group.stat.total.sms.all.members -- получателей

group.stat.total.sms.all.u_members -- уникальный получателей

group.stat.total.sms.all.size -- количество выпущенных смс

group.stat.total.sms.all.cost -- цена выпуск sms в единицах тарификации, в данный момент это копейки

Информация o выпуске

Поля с количеством отсеянных участников содержат актуальное значение для выпусков начиная с 26-07-2013.
Для более ранних выпусков поля содержат 0.

Поле issue.cost содержит актуальное значение для выпусков начиная с 01-11-2013.
Для более ранних выпусков поля содержат 0 так как старая система тарификации не пересчитываема в новую.


issue.id - id выпуска

issue.dt - дата выпуска (dt:Ys)

issue.name - название выпуска (совпадает с issue.subject если не было указано отдельно при выпуске)

issue.label0 -- метки выпуска по порядку их указания в выпуске/черновике
issue.label1
issue.label2
issue.label3
issue.label4
issue.label5
issue.label6
issue.label7
issue.label8
issue.label9

issue.subject - тема выпуска (тема письма или имя отправителя sms)

issue.to_name - имя получателя (с 10.12.2015)

issue.from_name  - имя отправителя (с 10.12.2015)
issue.from_email - адрес отправителя (с 10.12.2015)

issue.reply_name  - имя для ответа (с 10.12.2015)
issue.reply_email - адрес для ответа (с 10.12.2015)

issue.members - число получателей выпуска

issue.format - формат выпуска. e - email, s - sms, v - viber, p - push, k - vk, g - tg, n - vknotify

issue.size  - размер выпуска. Для email, viber, push, vk - размер одного письма/сообщения, для sms - количество выпущенных смс.

issue.cost - цена выпуска. Для email, viber, push, vk, tg - 0, для sms и vknotify - в единицах тарификации, в данный момент это копейки

issue.archive - ссылка на не персонализированную версию выпуска в веб-архиве
              - для Транзакционных Писем (personal) всегда null так не имеет смысла из-за того, что каждое Транзакционное письмо индивидуально

issue.double - количество дублирующихся адресов в списке. имеет смысл только для Экспресс-Выпуска. заполняется с 11-12-2013

issue.onlyunique - режим подавления дублирующихся адресов при выпуске. имеет смысл только для Экспресс-Выпуска. заполняется с 11-12-2013
                 - 0 - высылать письма на адреса встреченные второй и более разы
                 - 1 - НЕ высылать письма на адреса встреченные второй и более разы

issue.wrongline - (этот параметр назван не очень удачно) количество строк в списке (cvs или json) которые система отбросила по разных причинам (не смогла разобрать, адрес в стоп-листе, ошибки доставки и т.д.). имеет смысл только для Экспресс-Выпуска. заполняется с 11-12-2013

issue.hardbounce - количество адресов не допущенных в выпуск из-за большого количества ошибок доставки

issue.stoplist - количество адресов не допущенных в выпуск из-за нахождения их адреса в стоп-листе

issue.lockunsub - количество адресов  не допущенных в выпуск из-за того, что их адрес отписан

issue.lockconfirm - количество адресов не допущенных в выпуск из-за того, что внесение в базу ими ещё не подтверждено

issue.excludefilter -- количество адресов не допущенных в выпуск из-за того, что попали под группу исключения использованную при выпуске

issue.unsublist  -- количество адресов не допущенных в выпуск из-за того, что попали под группу тематической отписки использованную при выпуске

issue.excludecontactrate - количество адресов не допущенных в выпуск из-за того, что не соблюдён contact_rate

issue.group.* - информация о группе по которой был выпуск

issue.unsub_list.* - информация о группе отписки которая использовалась в выпуске (null)

issue.campaign.* - информация о кампании указанной при задании выпуска

issue.draft.id - номер черновика использованного при выпуске (null)
issue.draft.alias - альтернативный идентификатор черновика использованного при выпуске (null)
issue.draft.name - название черновика использованного при выпуске (null)

issue.class.id - номер класса выпуска использованного при выпуске (null)
issue.class.alias - альтернативный идентификатор класса выпуска использованного при выпуске (null)
issue.class.name - название класса выпуска использованного при выпуске (null)

issue.form.id - номер формы использованной при выпуске (null)
issue.form.name - название формы использованной при выпуске (null)

issue.dkim.id - номер DKIM-ключа использованного при выпуске (null) (до 6 октября 2016 года - null)
issue.dkim.by - источник DKIM-ключа использованного при выпуске (до 6 октября 2016 года - null)
                возможные значения: issue, draft, domain, conf, global

issue.sequence.id - номер последовательности вызвавшей выпуск (null)

issue.sequence.campaign.* - информация о кампаниях в которые входит последовательность выпустившая этот выпуск

-- специальные имена для проверки особенностей выпуска
-- правильное использование - сравнение с нулём : == 0 или != 0

issue.feature_none - ничего особенного
issue.feature_pdf  - использовались pdf-атачи
issue.feature_json - использовался json-relay
issue.feature_amp  - выпуск с AMP
issue.feature_csid - выпуск выполнялся по csid
issue.feature_pte  - выпуск использовал pte
issue.feature_fast - выпуск по быстрой схеме

issue.users_slice - выпуск использовал ограничение на размер тиража (0 - нет, > 0 - использованное ограничение в процентах, < 0 - использованное ограничение в штуках)

issue.multiple - выпуск использовал учёт множественных совпадений итератор в фильтре(0|не 0)

issue.contact_rate - выпуск использовал ограничение количества контактов на получателя(0|не 0)

issue.tz_best - выпуск использовал высылку письма в наиболее подходящее для получателя время (0|не 0)

issue.tz_limit - выпуск использовал ограничение на частоту отправки(0|не 0)

issue.tz_observance - выпуск использовал учёт часового пояса получателя (0|не 0)

issue.source - источник выпуска ("api","smtp","tranz")

issue.track.id - номер трекера выпуска (null)

issue.cron.id - номер действия по расписанию вызвавшего выпуск (null)

issue.sublogin - саблогин вызвавший выпуск (null)

issue.istest - это был тестовый выпуск (0|не 0)

issue.unsub_list.gid - символический код группы использованной как список отписки в выпуске

-- сплит-тестирование в рамках которого вышел этот выпуск

issue.variant.id - номер варианта сплит-тестирования для которого вышел выпуск (null)
issue.split.id  - номер АБ-тестирования в рамках которого вышел этот выпуск
issue.split.campaign.*  - информация о кампаниях в которые входит АБ

issue.thumbnail_800x600 - ссылка на изображения письма выпуска 800x600 (подробности в issue.get) (null)

issue.reltype - параметр классификации
issue.relref  - параметр классификации

-- избранные метрики выпуска

issue.delivery_rate     Delivery Rate (decimal x.2) (null)

issue.error_rate     Error Rate (decimal x.2) (null)

issue.open_rate     Open Rate (decimal x.2) (null)

issue.click_rate     Click Rate (decimal x.2) (null)

issue.click_open_rate     Click to Open Rate (decimal x.2) (null)

issue.unsub_rate     Unsub Rate (decimal x.2) (null)

issue.unsub_open_rate     Unsub to Open Rate (decimal x.2) (null)

-- суммарная статистика по выпуску.
-- это кэш. первые 5 дней он обновляется раз в 15 минут. потом - раз в три часа

issue.deliv_ok - количество успешно доставленных писем

issue.deliv_bad - количество писем с ошибкой доставки

issue.clicked - количество кликов

issue.u_clicked - количество уникальных кликов

issue.readed - количество чтений

issue.u_readed - количество уникальных чтений

issue.duraion - средняя длительность чтений (null)

issue.unsubed - количество отписок из выпуска

-- суммарная статистика по выпуску за час
-- это кэш. он обновляется раз в 20 минут

issue.hourly.dt - дата и час (YYYY-MM-DD hh) для которых сгруппирована статистика

issue.daily.deliv    - общее количество писем. в первый день рассылки учитывает ok+bad+unk. в остальные - только ok+bad

issue.daily.deliv_ok - количество успешно доставленных писем

issue.daily.deliv_bad - количество писем с ошибкой доставки

issue.hourly.clicked - количество кликов

issue.hourly.u_clicked - количество уникальных кликов

issue.hourly.readed - количество чтений

issue.hourly.u_readed - количество уникальных чтений

issue.hourly.unsubed   - количество отписок из выпуска

-- суммарная статистика по выпуску/домену за день
-- это кэш. он обновляется раз в 20 минут

issue.daily.domain.id - id домена

issue.daily.domain.name - название домена

issue.daily.dt - дата (YYYY-MM-DD) для которой сгруппирована статистика

issue.daily.deliv    - общее количество писем. в первый день рассылки учитывает ok+bad+unk. в остальные - только ok+bad

issue.daily.deliv_ok - количество успешно доставленных писем

issue.daily.deliv_bad - количество писем с ошибкой доставки

issue.daily.clicked - количество кликов

issue.daily.u_clicked - количество уникальных кликов

issue.daily.readed - количество чтений

issue.daily.u_readed - количество уникальных чтений

issue.daily.unsubed   - количество отписок из выпуска

-- Количество кликнувших или прочитавших по географии - кэш общего числа

issue.stat.geo.n                 - число кликов и чтений для данной географии

issue.stat.geo.id                - числовой код географии CCCRR000 - CCC числовой код страны по ISO 3166-1, RR - для России числовой код региона по ОКАТО/ОКТМО и 00 для прочих стран и не определившегося региона России

issue.stat.geo.name              - название географии на русском языке "Страна. Регион" или "Страна" если регион не определён

issue.stat.geo.country.id        - числовой код страны по ISO 3166-1

issue.stat.geo.country.alpha2    - двухбуквенный код страны по ISO 3166-1

issue.stat.geo.country.name      - название страны на русском языке

issue.stat.geo.region.alpha5     - пятисимвольный код региона по ISO 3166-2 (имеет вид XX-XXX) для России и совпадает с alpha2 для прочих стран

issue.stat.geo.region.name       - название региона для России на русском языке, пусто для прочих стран

-- Вхождение в мультивыпуск

issue.multi.id          - номер мультивыпуска (null)

issue.multi.name        - название мультивыпуска (null)

Информация o кампании


campaign.id - id кампании

campaign.name - название кампании

---- суммарная статистика по входящим в кампанию выпускам и формам по всей кампании. это кэш.

-- количество кликнувших или прочитавших по географии - кэш общего числа

campaign.stat.geo.n                 - число кликнувших или прочитавших для данной географии

campaign.stat.geo.id                - числовой код географии CCCRR000 - CCC числовой код страны по ISO 3166-1, RR - для России числовой код региона по ОКАТО/ОКТМО и 00 для прочих стран и не определившегося региона России

campaign.stat.geo.name              - название географии на русском языке "Страна. Регион" или "Страна" если регион не определён

campaign.stat.geo.country.id        - числовой код страны по ISO 3166-1

campaign.stat.geo.country.alpha2    - двухбуквенный код страны по ISO 3166-1

campaign.stat.geo.country.name      - название страны на русском языке

campaign.stat.geo.region.alpha5     - пятисимвольный код региона по ISO 3166-2 (имеет вид XX-XXX) для России и совпадает с alpha2 для прочих стран

campaign.stat.geo.region.name       - название региона для России на русском языке, пусто для прочих стран

-- по всем email-рассылкам

campaign.stat.total.email.all.issues    - выпусков email-рассылок

campaign.stat.total.email.all.members   - получателей

campaign.stat.total.email.all.u_members - уникальный получателей

campaign.stat.total.email.all.clicked   - кликов

campaign.stat.total.email.all.u_clicked - уникальных кликов

campaign.stat.total.email.all.target    - достижений целевых страниц

campaign.stat.total.email.all.u_target  - уникальных достижений целевых страниц

campaign.stat.total.email.all.readed    - чтений

campaign.stat.total.email.all.u_readed  - уникальный чтений

campaign.stat.total.email.all.unsub     - отписок из писем выпусков

campaign.stat.total.email.all.u_unsub   - уникальных отписок из писем выпусков

-- только по транзакционным email-рассылкам

campaign.stat.total.email.personal.*    - аналогично campaign.stat.total.email.all, но только по выпускам транзакционных email-рассылок (рассылка personal)

-- только по маркетинговым email-рассылкам

campaign.stat.total.email.marketing.*   - аналогично campaign.stat.total.email.all, но только по выпускам маркетинговых email-рассылок (все рассылки кроме personal)

-- по всем sms-рассылкам

campaign.stat.total.sms.all.issues      - выпусков sms-рассылок

campaign.stat.total.sms.all.members     - получателей

campaign.stat.total.sms.all.u_members   - уникальных получателей

campaign.stat.total.sms.all.size        - количество использованных sms

campaign.stat.total.sms.all.cost        - цена выпуска использованных sms в единицах тарификации, в данный момент это копейки

-- только по транзакционным sms-рассылкам

campaign.stat.total.sms.personal.*      - аналогично campaign.stat.total.sms.all, но только по выпускам транзакционных sms-рассылок (рассылка personal)

-- только по маркетинговым sms-рассылкам

campaign.stat.total.sms.marketing.*     - аналогично campaign.stat.total.sms.all, но только по выпускам маркетинговых email-рассылок (все рассылки кроме personal)

-- по формам

campaign.stat.total.form.members        - заполнений форм

campaign.stat.total.form.u_members      - уникальных пользователей заполнивших формы

---- суммарная статистика по кампании по дням. это кэш.

campaign.stat.daily.dt   - день (YD) - YYYY-MM-DD
campaign.stat.daily.*    - аналогично campaign.stat.total

---- суммарная статистика по кампании по неделям это кэш.

campaign.stat.weekly.dt  - первый день недели (т.е. понедельник) (YD) - YYYY-MM-DD
campaign.stat.weekly.*   - аналогично campaign.stat.total

 -- то же самое по месяцам

campaign.stat.monthly.dt  - месяц (YM) - YYYY-MM
campaign.stat.monthly.*   - аналогично campaign.stat.total

Информация o ссылке


link.id - id ссылки

link.url - url ссылки

link.reltype - параметр классификации
                     -- отрицательные значения зарезервированы для системы
                     -- в данный момент:
                     --  -1 - ссылка из выпуска рассылки
                     --  -2 - ссылка - целевая страница достигнутая подписчиком

link.relref - параметр классификации

link.linkgroup.* - информация о группе в которую входит ссылка

Информация o группе ссылок


linkgroup.id - группы ссылок

linkgroup.name - name группы ссылок

linkgroup.reltype - параметр классификации

linkgroup.relref - параметр классификации

linkgroup.campaign.* - информация о кампаниях в которые входит группа ссылок

Информация о переходах

Не забывайте уточнять в запросе click.link.reltype !

Например, при подсчёте всех кликов из выпуска и без указания reltype вы получите информацию как по самим кликам из выпуска (reltype=-1), так по посещения Целевых Страниц (reltype=-2). Уточните в фильтре не только номер выпуска, но и классификацию ссылки.


click.dt - дата и время клика (dt:Ys)

click.ip - ip кликнувшего

click.source - равно 4 если переход осуществлён из amp-версии email-сообщения

click.letter.* - информация о письме как в deliv.letter

click.member.* - информация о кликнувшем подписчике

click.domain.* - информация о домене кликнувшего подписчика

click.issue.*  - информация о выпуске из которого был клик

click.link.id - id ссылки

click.link.url - url ссылки

click.link.reltype - параметр классификации

click.link.relref - параметр классификации

click.linkgroup.* - группы ссылок

click.linkgroup.campaign.* - информация о кампаниях в которые входит группа ссылок ссылки по которой был переход

-- География

click.member.geo.id                - числовой код географии CCCRR000 - CCC числовой код страны по ISO 3166-1, RR - для России числовой код региона по ОКАТО/ОКТМО и 00 для прочих стран и не определившегося региона России

click.member.geo.name              - название географии на русском языке "Страна. Регион" или "Страна" если регион не определён

click.member.geo.country.id        - числовой код страны по ISO 3166-1

click.member.geo.country.alpha2    - двухбуквенный код страны по ISO 3166-1

click.member.geo.country.name      - название страны на русском языке

click.member.geo.region.alpha5     - пятисимвольный код региона по ISO 3166-2 (имеет вид XX-XXX) для России и совпадает с alpha2 для прочих стран

click.member.geo.region.name       - название региона для России на русском языке, пусто для прочих стран

-- Устройство

click.member.gadget.gender     - Desktop, Mobile, Tablet, Proxy, Robot

click.member.gadget.os         - Linux, Android, Windows, iOS, Mac, Blackberry, Proxy, Other

click.member.gadget.browser    - Firefox, Chrome, Opera, Edge, Outlook, Yandex, Safari, IE, Blackberry, Proxy, Other

click.member.gadget.browmajor  - старшая часть версии браузера

Информация об открытии писем


read.dt - дата и время чтения (dt:Ys)

read.ip - ip прочитавшего (null до 01 декабря 2014 года)

read.duration - длительность чтения в секундах (null до 29 августа 2017 года и если не определилось)
              - максимальная отслеживаемая длительность - 30 секунд
              - если null, то это
               * чтение вызванное кликом (если на момент клика ещё не было чтения, то оно добавляется)
               * канал выпуска не поддерживает определение длительности
               * длительность зафиксировать не удалось (мало вероятно)

read.source - равно 4 если прочтение относится к amp-версии email-сообщения

read.letter.* - информация о письме как в deliv.letter

read.member.* - информация о прочитавшем (открывшем письмо) подписчике

read.domain.* - информация о домене прочитавшего подписчика

read.issue.*  - информация о выпуске который был прочтён

-- География

read.member.geo.id                - числовой код географии CCCRR000 - CCC числовой код страны по ISO 3166-1, RR - для России числовой код региона по ОКАТО/ОКТМО и 00 для прочих стран и не определившегося региона России

read.member.geo.name              - название географии на русском языке "Страна. Регион" или "Страна" если регион не определён

read.member.geo.country.id        - числовой код страны по ISO 3166-1

read.member.geo.country.alpha2    - двухбуквенный код страны по ISO 3166-1

read.member.geo.country.name      - название страны на русском языке

read.member.geo.region.alpha5     - пятисимвольный код региона по ISO 3166-2 (имеет вид XX-XXX) для России и совпадает с alpha2 для прочих стран

read.member.geo.region.name       - название региона для России на русском языке, пусто для прочих стран

-- Устройство

read.member.gadget.gender     - Desktop, Mobile, Tablet, Proxy, Robot

read.member.gadget.os         - Linux, Android, Windows, iOS, Mac, Blackberry, Proxy, Other

read.member.gadget.browser    - Firefox, Chrome, Opera, Edge, Outlook, Yandex, Safari, IE, Blackberry, Proxy, Other

read.member.gadget.browmajor  - старшая часть версии браузера

Информация о доставке и прочих свойствах каждого сообщения


deliv.member.* - информация о подписчике-получателе

deliv.member.geo.* - информация о географии подписчика (null)

deliv.domain.* - информация о домене подписчика-получателя

deliv.issue.*  - информация о выпуске который доставлялся

deliv.letter.id - номер письма в выпуске

deliv.letter.dt - дата и время (dt:Ys) последнего изменения статуса доставки. (null)
                - до февраля 2015 года - null

deliv.letter.start_dt -- запланированная дата старта (dt:Ys) при растянутом выпуске (tz_limit/batch)
                      -- или при выпуске с учётом часового пояса (tz_observance)
                      -- до 18 мая 2016 года - null

deliv.letter.custid(element_name) - значение элемента element_name пользовательской метки письма

deliv.status - код статуса доставки
             - больше 0 - доставлено
             - равно  0 - в процессе доставки
             - мешьше 0 - ошибка доставки или отсеян из рассылки

             - Общие кода ошибок
             -  -2 - все попытки доставки успеха не имели, но и не был получен ответ, что точно не примут
             -  -3 - письмо приняли за спам
             -  -4 - письмо отменено (например выпуск растянутый по времени решено вообще не выпускать дальше)

             - -100010 - находится в стоп листе
             - -100011 - блокировка из-за постоянных ошибок доставки
             - -100012 - не подтверждена регистрация
             - -100013 - отписался
             - -100014 - сотовый оператор заблокирован нами
             - -100015 - исключен по фильтру исключения используемого черновика
             - -100016 - исключен из-за нахождения в списке тематической отписки используемого черновика
             - -100017 - превышена установленная частота контакта с пользователем
             - -100018 - указан не верный номер черновика
             - -100019 - не досталось промокода
             - -100020 - адрес получателя синтаксически не верен
             - -100021 - не подошло под фильтр включения в выпуск
             - -100022 - авторизация не верна или не найдена
             - -100023 - адрес в спам-листе

             - Для email кода ошибок обозначают
             -  -5yz - код ошибки SMTP-Enchanced status code 5.Y.Z (RFC 3463 https://tools.ietf.org/html/rfc3463)
             -  -15xx - код ошибки SMTP (5xx) сдвинутый на -1000

             - Для sms, vknotify и viber кода ошибок обозначают
             -     -4 - сообщение отменено
             -  -2000 - прочие ошибки
             -  -2003 - истекло время доставки
             -  -2004 - sms-центр принял сообщение, но потом не стал доставлять и просто удалил
             -  -2005 - не удалось доставить на телефон абонента
             -  -2006 - sms-центр сообщил, что потерял информацию о статусе доставки
             -  -2007 - неизвестная ошибка доставки
             -  -2008 - sms-центр сразу отверг сообщение
             -  -2009 - sms-провайдер не принял сообщение
             -  -2010 - рассылка по данному направлению невозможна (номер не мобильный или данный оператор у вас не подключён)
             -  -2011 - новая ошибка sms-пройвадера описание которой он нам не сообщал
             -  -2012 - неправильно задан номер абонента
             -  -2013 - номер в чёрном списке

             - Для push кода ошибок обозначают
             -  -2100 - InternalServerError
             -  -2101 - Invalid JSON
             -  -2102 - InvalidRegistration
             -  -2103 - InvalidParameters
             -  -2104 - MissingRegistration
             -  -2105 - InvalidPackageName
             -  -2106 - MismatchSenderId
             -  -2107 - DeviceMessageRate Exceeded
             -  -2108 - TopicsMessageRate
             -  -2109 - InvalidApnsCredential
             -  -2110 - Unknown subscription
             -  -2199 - нераспознанная ошибка
             -  -2201 - Missing necessary crypto keys
             -  -2202 - InvalidDataKey / Invalid URL endpoint
             -  -2203 - Expired URL endpoint
             -  -2204 - MessageTooBig / Data payload too large
             -  -2205 - Endpoint became unavailable during request
             -  -2206 - NotRegistered /Invalid subscription
             -  -2208 - Router type is invalid
             -  -2209 - UnauthorizedRegistration / Invalid authentication
             -  -2210 - Invalid crypto keys specified
             -  -2211 - Missing Crypto Headers, TTL
             -  -2213 - Invalid Topic header value
             -  -2212 - InvalidTtl / Invalid TTL header value
             -  -2301 - Unavailable / Use exponential back-off for retries

             - Для vk кода ошибок обозначают
             -  -3000 Ошибка доступа к серверу или нераспознанный ответ сервера
             -  -3001 Произошла неизвестная ошибка.
             -  -3002 Приложение выключено.
             -  -3003 Передан неизвестный метод.
             -  -3004 Неверная подпись.
             -  -3005 Авторизация пользователя не удалась.
             -  -3006 Слишком много запросов в секунду.
             -  -3007 Нет прав для выполнения этого действия.
             -  -3008 Неверный запрос.
             -  -3009 Слишком много однотипных действий.
             -  -3010 Произошла внутренняя ошибка сервера.
             -  -3011 В тестовом режиме приложение должно быть выключено или пользователь должен быть залогинен.
             -  -3014 Требуется ввод кода с картинки (Captcha).
             -  -3015 Доступ запрещён.
             -  -3016 Требуется выполнение запросов по протоколу HTTPS, т.к. пользователь включил настройку, требующую работу через безопасное соединение.
             -  -3017 Требуется валидация пользователя.
             -  -3018 Страница удалена или заблокирована.
             -  -3020 Данное действие запрещено для не Standalone приложений.
             -  -3021 Данное действие разрешено только для Standalone и Open API приложений.
             -  -3023 Метод был выключен.
             -  -3024 Требуется подтверждение со стороны пользователя.
             -  -3027 Ключ доступа сообщества недействителен.
             -  -3028 Ключ доступа приложения недействителен.
             -  -3100 Один из необходимых параметров был не передан или неверен.
             -  -3101 Неверный API ID приложения.
             -  -3113 Неверный идентификатор пользователя.
             -  -3150 Неверный timestamp.
             -  -3200 Доступ к альбому запрещён.
             -  -3201 Доступ к аудио запрещён.
             -  -3203 Доступ к группе запрещён.
             -  -3300 Альбом переполнен.
             -  -3500 Действие запрещено. Вы должны включить переводы голосов в настройках приложения.
             -  -3600 Нет прав на выполнение данных операций с рекламным кабинетом.
             -  -3603 Произошла ошибка при работе с рекламным кабинетом.
             -  -3900 Нельзя отправлять сообщение пользователю из черного списка
             -  -3901 Пользователь запретил отправку сообщений от имени сообщества
             -  -3902 Нельзя отправлять сообщения этому пользователю в связи с настройками приватности
             -  -3913 Слишком много пересланных сообщений
             -  -3914 Сообщение слишком длинное
             -  -3917 You dont have access to this chat
             -  -3921 Невозможно переслать выбранные сообщения
             -  -3260 Доступ к запрошенному списку групп ограничен настройками приватности пользователя.
             -  -3125 Недопустимый идентификатор сообщества.

             - Для tg кода ошибок обозначают
             - -4400 - Не верный запрос. Обычно - не найден чат, означает, что пользователь вышел из чата
             - -4401 - Не авторизован. Например невалидный токен.
             - -4403 - Запрещено. Бот заблокирован пользователем
             - -4404 - Обращение к несуществующему объекту, методу спи телеграмма
             - -4420 - Флуд. Слишком много сообщений пользователю.
             - -4429 - слишком много реквестов (были предприняты повторы, безрезультатно)
             - -4500 - Ошибка сервера Телеграма
             - -4600 - Ошибка при разборе сообщения, например не экранированы символы
             - -4601 - Ошибка в идентификаторе файла или скачивания URL
             - -4602 - Слишком большой текст подписи, например у изображения
             - -4603 - Слишком большой размер сообщения 
             - -4604 - Отписан: бот заблокирован пользователем
             - -4605 - Отписан: пользователь деактивирован
             - -4606 - Отправка запрещена: чат не найден, например не был подписан
             - -4607 - Отправка сообщения боту запрещено
             - -4608 - Бот не может инициировать чат
             - -4609 - Отправка этому пользователю запрещена

deliv.result - результат доставки
             -  1 - доставлено
             -  0 - в процессе доставки
             - -1 - ошибка доставки

-- для sms-рассылок (до 04.12.2013 - null, для прочих - null)

deliv.oper - оператор получателя (null)
           - other    - 0
           - megafon  - 1
           - skylink  - 2
           - mts      - 3
           - beeline  - 4
           - tele2    - 5

deliv.size - количество смс использованных для передачи сообщения (null)

deliv.cost - стоимость сообщения. сотых долях копейки (null)

-- информация о доставки ответов если активна настройка перехвата ответных писем получателей на письма выпуска

deliv.replyed.to - адрес получателя ответа

deliv.replyed.dt - дата и время (Ys) доставки/недоставки ответа

deliv.replyed.status - код статуса доставки/недоставки ответа. аналогичен deliv.status

deliv.replyed.errstr - строка ошибки доставки ответа (null)

-- География

deliv.member.geo.id                - числовой код географии CCCRR000 - CCC числовой код страны по ISO 3166-1, RR - для России числовой код региона по ОКАТО/ОКТМО и 00 для прочих стран и не определившегося региона России

deliv.member.geo.name              - название географии на русском языке "Страна. Регион" или "Страна" если регион не определён

deliv.member.geo.country.id        - числовой код страны по ISO 3166-1

deliv.member.geo.country.alpha2    - двухбуквенный код страны по ISO 3166-1

deliv.member.geo.country.name      - название страны на русском языке

deliv.member.geo.region.alpha5     - пятисимвольный код региона по ISO 3166-2 (имеет вид XX-XXX) для России и совпадает с alpha2 для прочих стран

deliv.member.geo.region.name       - название региона для России на русском языке, пусто для прочих стран

-- Устройство

deliv.member.gadget.gender     - Desktop, Mobile, Tablet, Proxy, Robot

deliv.member.gadget.os         - Linux, Android, Windows, iOS, Mac, Blackberry, Proxy, Other

deliv.member.gadget.browser    - Firefox, Chrome, Opera, Edge, Outlook, Yandex, Safari, IE, Blackberry, Proxy, Other

deliv.member.gadget.browmajor  - старшая часть версии браузера

Вместо префикса deliv можно использовать префиксы

Информация об отписке

unsub.dt     - дата получения информации об отписке (YYYY-MM-DD hh:mm:ss)

unsub.why    - способ отписки
             - 1 - нажав кнопку "Это спам" в своей почтовой системе/программе
             - 2 - по стандартной ссылке отписаться внизу письма
             - 3 - через жалобу в нашу Службу поддержки
             - 4 - нажав кнопку "Отписаться" в своей почтовой системе/программе
             - 5 - по ссылке тематической отписки в письме

unsub.reason - сообщение от подписчика о причине отписки если было указано (null)

unsub.label  - произвольная метка (64 байта) (null)
             - указывается в ссылке отписке в параметрe label
             - например [% param.url_unsub %]?label=campaign23

unsub.letter.* - информация о письме как в deliv.letter

unsub.member.* - информация об отписавшемся подписчике

unsub.domain.* - информация о домене отписавшегося подписчика

unsub.issue.*  - информация о выпуске из которого отписались, если он известен (null)

unsub.sender.id - номер адреса отправителя, 0 - глобальная отписка

unsub.sender.email  - адрес отправителя, null - глобальная запись

Клиентcкие метки писем

custid.name - название клиентской метки

custid.val - значение клиентской метки

custid.member.* - информация о получателе письма

custid.issue.* - информация о выпуске

custid.letter.id - информация о письме

custid.letter.dt

Выданные промо-кода

promocode.val - выданный код

promocode.dt - момент выдачи Ys

promocode.issue.* - выпуск

promocode.letter.* - письмо

promocode.member.* - подписчик

Информация о ряде данных


datarow.id    - идентификатор ряда данных

datarow.name  - название ряда данных

datarow.prec  - точность даты ряда данных

datarow.campaign.* - информация о кампаниях в которые входит ряд данных

-- Одна запись ряда данных

datarow.record.dt     - дата записи
                      - ! дата всегда возвращается с точностью Ys вне зависимости от datarow.prec

datarow.record.value  - значение записи

Cтоп-лист


stoplist.dt       - дата записи (Ys)

stoplist.source   - источник (null)

stoplist.type     - А - внесено владельцем аккаунта, М - владельцем адреса

stoplist.sender.id  - номер адреса отправителя, 0 - глобальная запись

stoplist.sender.email  - адрес отправителя, null - глобальная запись

stoplist.email    - адрес

Последнее заполнение заполнения форм опросов

При повторном заполнения подписчиком формы старые данные заменяются новыми

form.id - идентификатор формы

form.name - название формы

form.state - состояние формы

form.member.id - id заполнившего

form.member.email - адрес заполнившего

form.member.dt_fill - дата заполнения Ys (null)

form.member.dt_confirm - дата подтверждения заполнения Ys (null)

form.member.issue.id - номер выпуска из которого заполнена форма (null)

form.member.issue.draft.id - номер черновика использованного в выпуске (null)

form.member.issue.draft.name - название черновика использованного в выпуске (null)

form.member.issue.draft.alias - альтернативный идентификатор черновика использованного в выпуске (null)

form.member.letter.id - номер письма из которого заполнена форма (null)

form.member.letter.dt - дата письма из которого заполнена форма (null)

form.member.letter.custid(element_name) - значение элемента element_name пользовательской метки письма из которого заполнена форма (null)

form.campaign.* - информация о кампаниях в которые входит форма (null)

form.origin.* - информация об источнике, связанной с формой (null)

Лог заполнения форм

formfilling.dt -- дата действия Ys

formfilling.id -- номер формы

formfilling.action : "showed" | "filled" | "confirmed" -- действие

formfilling.answer : { -- ответы на вопросы для filled и confirmed (null)
"код вопроса" : "ответ"
......
}

formfilling.dt_filled -- для confirmed дата когда было filled Ys (null)

formfilling.origin.id -- номер источника, связанного с формой (null)

formfilling.ip -- ip-адрес заполнявшщего

formfilling.member.id -- номер подписчика

formfilling.member.email -- идентификатор подписчика

formfilling.member.addr_type -- строка типа идентификатора подписчика
- email
- msisdn
- csid
- push
- vk
- viber
- tg
- vknotify

formfilling.member.dataset -- номер набора данных подписчика

formfilling.domain.name -- домен идентификатора подписчика

formfilling.letteer.id -- номер письма из которого произошло действие (null)

formfilling.issue.* -- информация о выпуске из письма которого произошло действие (null)

Общая информация об аккаунте


stat.common.dt     - дата (YYYY-MM-DD)

stat.common.tarif - код тарифа на эту дату

-- данные вызова member.list.count на эту дату. все поля null до 29.01.2014

stat.common.emails        - количество адресов в базе

stat.common.phones        - количество телефонов в базе

stat.common.csids         - количество пользовательских идентификаторов в базе

stat.common.pushs         - количество веб-пуш подписок в базе

stat.common.vks           - количество подписок ВКонтакте в базе

stat.common.vibers        - количество вайбер в базе

stat.common.tgs           - количество подписок Телеграм в базе

stat.common.active_emails - количество адресов способных участвовать в рассылках

stat.common.active_phones - количество телефонов способных участвовать в рассылках

stat.common.active_csids  - количество пользовательских идентификаторов способных участвовать в рассылках

stat.common.active_pushs  - количество веб-пуш подписок способных участвовать в рассылках

stat.common.active_vks    - количество подписок ВКонтакте способных участвовать в рассылках

stat.common.active_vibers - количество вайбер способных участвовать в рассылках

stat.common.active_tgs    - количество подписок Телеграм способных участвовать в рассылках

stat.common.locked        - уникальное количество заблокированных - не могут быть в рассылке.

stat.common.stoplist      -    в том числе заблокированные так как находятся в стоп-листе

stat.common.lockconfirm   -    в том числе заблокированные так как не подтвердили внесение в базу

stat.common.hardbounce    -    в том числе заблокированные так как имеют фатальные ошибки доставки

stat.common.lockunsub     -    в том числе заблокированные так как отписались

Информация о последовательности (триггере)

sequence.id - id последовательности

sequence.name - название последовательности

-- информация о каждом прохождении последовательности каждым участником

sequence.progress.step - шаг в последовательности

sequence.progress.state - cостояние
                            --  1 - активен - происходящие события учитываются
                            --  0 - пауза - происходящие события игнорируются
                            -- -1 - последовательность закончена
                            -- -2 - последовательность закончена из-за sequence.member.stop
                            -- -3 - последовательность закончена из-за parallel = 0 или -1
                            -- -4 - последовательность закончена из-за action = sequence.start, seqeunce.stop, sequence.goto
                            -- -5 - последовательность закончена из-за изменений по указанию
                            -- -6 - последовательность закончена из-за изменений так как участник оказался вне последовательности
                            -- -7 - последовательность закончена так как её прохождение отменено
                            -- -8 - последовательность закончена так как подписчик был удалён
                            -- -9 - последовательность закончена так как подписчик никогда не проёдёт дальше
                                    (например на шаге только member.match и ни каких альтернатив, а member.match не сработал)

sequence.progress.dt - дата-время попадания на последовательность (Ys)

sequence.progress.dt_step - дата-время попадания на текущий шаг или дата окончания последовательности (Ys)

sequence.progress.email - адрес участника

sequence.progress.addr_type - тип адреса участника email|msisdn|viber|csid|push|vk|tg|vknotify

Информация о черновиках

draft.id - номер черновика

draft.name - название черновика

draft.alias - альтернативный идентификатор (null)

draft.format - "viber|sms|html|text|push|vk|tg|vknotify" -- формат черновика

draft.create.time - дата и время создания" -- Ys, null

draft.update.time - дата и время последнего изменения" -- Ys, null

draft.template - 0|1 - признак черновика-шаблона

draft.public_preview - ccылка просмотра черновика без пароля

draft.thumbnail_800x600 - ссылка на изображения черновика 800x600 (подробности в draft.get) null

draft.reltype -- параметр классификации
draft.relref  -- параметр классификации

draft.sender -- Имя отправителя  (null)

draft.from -- Email отправителя  (null)

draft.link.qsid -- Параметр для отслеживания переходов по ссылкам  (null)

draft.class.id -- идентификатор класса выпуска (null)

draft.dkim.id -- идентификатор dkim-ключа  (null)

draft.issue_member_list.gid -- символ. код группы, в которую будут добавляться участники выпусков по черновику (null)

draft.issue_member_list.name -- название группы (null)

draft.issue_member_list.type -- тип группы: list или filter (null)

draft.issue_member_list.addr_type -- тип адресов: email|msisdn|viber|csid|push|vk|tg|vknotify|any (null)

draft.issue_exclude_filter.gid -- симв. код группы, участники которой будут исключены из выпусков по черновику (null)

draft.issue_exclude_filter.name -- название группы (null)

draft.issue_exclude_filter.type -- тип группы: list или filter (null)

draft.issue_exclude_filter.addr_type -- тип адресов: email|msisdn|viber|csid|push|vk|tg|vknotify|any (null)

draft.unsub_list.gid -- симв. код группы списка отписки черновика (null)

draft.unsub_list.name -- название группы (null)

draft.unsub_list.type -- тип группы: list или filter (null)

draft.unsub_list.addr_type -- тип адресов: email|msisdn|viber|csid|push|vk|tg|vknotify|any (null)

Информация о источнике

origin.id            -- идентификатор источника

origin.name          -- название источника

origin.label         -- метка источника

origin.status        -- активность источника

origin.type          -- тип источника: "site" | "internal" | "external" 

origin.create.date   -- дата создания (Ys)

origin.update.date   -- дата изменения (Ys)

origin.reltype       -- параметр классификации
origin.relref        -- параметр классификации

Информация о бухгалтерских документах

Сами документы доступны через Хранилище (вызовы rfs.*) в области Документы (domain:"pase").

Имя файла с документом (path) строится по схеме "/<значение pase.doc.id>.pdf". Например "/123.pdf".

pase.doc.id            -- номер документа в системе
pase.doc.create.time   -- дата загрузки Ys

pase.doc.type          -- тип документа.
                       -- 0 - счёт
                       -- 1 - акт
                       -- 2 - лицензия
                       -- 3 - счет-фактура
pase.doc.dt            -- дата документа YD
pase.doc.name          -- название/номер документа в бухгалтерии
pase.doc.summa         -- сумма документа decimal(16,2) null

Комбинирование в одном запросе

Возможно одновременное использование следующих сочетаний полей в одном запросе

Сводная статистика

Специальные названия полей используются для функций count() и sum() если в запросе участвуют несколько типов статистики.

Область вычисления функции задается аргументом.

В фильтре можно использовать поля из конкретной области статистики.


*.dt - дата события

*.issue.id - id выпуска

*.issue.name - тема выпуска

*.issue.group.id - id группы выпуска

*.issue.group.gid - код группы выпуска

*.issue.group.name - name группы выпуска

*.issue.dt - дата выпуска

*.issue.members - число получателей выпуска

*.issue.format - формат выпуска

*.issue.deliv_ok - количество успешно доставленных писем

*.issue.deliv_bad - количество писем с постоянной ошибкой доставки

*.issue.clicked - количество кликов

*.issue.u_clicked - количество уникальных кликов

*.issue.readed - количество чтений

*.issue.u_readed - количество уникальных чтений

*.issue.duration - cреднее время чтений

*.issue.unsubed - количество отписок из выпуска

*.issue.draft.id - номер черновика использованного при выпуске (null)
*.issue.draft.alias - альтернативный идентификатор черновика использованного при выпуске (null)
*.issue.draft.name - название черновика использованного при выпуске (null)

*.issue.class.id - номер класса выпуска использованного при выпуске (null)
*.issue.class.alias - альтернативный идентификатор класса выпуска использованного при выпуске (null)
*.issue.class.name - название класса выпуска использованного при выпуске (null)

*.issue.form.id - номер формы использованной при выпуске (null)
*.issue.form.name - название формы использованной при выпуске (null)

*.issue.sequence.id - номер последовательности вызвавшей выпуск (null)

*.issue.variant.id - номер варианта сплит-тестирования для которого вышел выпуск (null)

*.member.id - id подписчика

*.member.email - значение email или телефона подписчика

*.member.haslock - блокировка подписчика

вверх

Стоп-лист

Cтоп-лист автоматически применяется при выпуске рассылок для исключения адресов отписавшихся от получения сообщений

Существует типа два записей стоп-листа:

Так же существует две зоны действия записей в Стоп-листе при отписке подписчика:

В стоп-листе для одного адресата может быть одновременно и глобальная запись отписки и записи отписки для разных отправителей для каждого типа (владельца и подписчика).

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

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

Параметр вызовов type поддерживает для совместимости и старые обозначения типов стоп-листов

Чтение записей в стоп-листе владельца и подписчиков

Вместо этого вызова лучше использовать stat.uni - быстрее, гибче фильтры и можно сохранить результат в файл или прислать по почте


{

  "action" : "stoplist.get" 

 ,"type" : "тип стоп-листа" -- all, owner, member. по умолчанию all, но рекомендуется указывать это явно, иначе
                            -- при полном отсутствии параметра type вы получите ответ в старом формате для совместимости

 ,"sender" : "адрес отправителя" -- по умолчанию пусто - возвращаются записи глобального стоп-листа

-- дополнительно, список интересующих адресов

 ,"list" : [ -- отсутсвие параметра - дать всех

             "email-1" 

            ,"email-2" 

            ,"email-3" 

            ,"email-4" 

             .....

           ]

}

ответ

 <общие поля>

 list : {

          "email-1" : {

                        "owner" : 1

                       ,"member" : 1

                       }

         ,"email-3" : {

                        "owner" : 1

                       }

         }

}

Внести в стоп-лист

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

Но вся информация связанная с адресом/телефоном остаётся в базе.

Запрос с указанием списка по умолчанию синхронный.

Запрос с указанием группы по умолчанию асинхронный.

Асинхронные запросы возвращают номер трекера для отслеживания.

Одновременно может выполняться только два таких запроса.

При асинхронном запуске обработку можно прекратить вызовом track.set


{

  "action" : "stoplist.add" 

 ,"type" : "тип стоп-листа" -- all, owner, member. по умолчанию owner

 ,"sender" : "адрес отправителя" -- по умолчанию пусто - вносятся записи в глобальный стоп-лист

-- указание подписчиков одним из способов

 ,"email" : "адрес или телефон" -- телефон в полном формате +7XXXYYYYYYY

или

 ,"list" : [

            "адрес/телефон" 

           ,"адрес/телефон" 

            ........

           ]

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 1 - синхронный

или

 ,"group" : код группы участники которой будут внесены в стоп-лист

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный

или

 ,"group.filter" : [
                    фильтр отбора как у group.filter.set
                   ]

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный

или

 ,"url" : ссылка на файл со списком адресов. по одному на строке. возможно сжатие zip.

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный

или

 ,"stat.uni" : { -- адреса для обработки получаются из запроса Универсальной Cтатистики

                 -- подразумевается unique = 1

                "filter" : [ условие выборки как у запроса в вызове stat.uni ]

               ,"have"   : [ не обязательно. условие отбора поле группировки в фильтре ]

               ,"cache"  : [ настройки кэширования как у запроса в вызове stat.uni ]

               ,"select" : [ ... ] -- используйте только если не подходит значение по умолчанию [ "member.email" ]

               }

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный

 ,"track.info" : "дополнительная информация которая будет потом доступна в track.list/get" -- строка 1024 байта. не обязательно
}

ответ


{

 <общие поля>

-- для асинхронного запроса

,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

-- для синхронного запроса

,"list" : {
           "адрес-1" : 0|1|кодошибки -- результат внесения - 1 - внесён, 0 - нет, кодошибки - синтаксическая ошибка в адресе/телефоне

          ,"адрес-2" : 0|1|кодошибки

           .................
          }

}

Удаление из стоп-листа

Удаление из стоп-лист означает только то, что указанный адрес/телефон будет удалён из стоп-листа. Он перестанет автоматически исключаться из всех рассылок если не будет других причин это делать (стоп-лист пользователя, ошибки доставки и прочее)

Запрос с указанием списка по всегда асинхронный.

Запрос с указанием группы по умолчанию асинхронный.

Асинхронные запросы возвращают номер трекера для отслеживания.

Одновременно может выполняться только два таких запроса.

Удаление из пользовательского стоп-листа работает только в пределах небольшого месячного лимита.

При асинхронном запуске обработку можно прекратить вызовом track.set


{

  "action" : "stoplist.delete" 

 ,"type" : "тип стоп-листа" -- all, owner, member. по умолчанию owner

 ,"sender" : "адрес отправителя" -- по умолчанию пусто - удаляются записи глобального стоп-листа

-- указание подписчиков одним из способов

 ,"email" : "адрес или телефон" -- телефон в полном формате +7XXXYYYYYYY

или

 ,"list" : [ -- запрос всегда асинхронный

            "адрес/телефон" 

           ,"адрес/телефон" 

            ........

           ]

или

 ,"group" : код группы участники которой будут удалены из стоп-листа

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный

или

 ,"group.filter" : [
                    фильтр отбора как у group.filter.set
                   ]

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный

или

 ,"url" : ссылка на файл со списком адресов. по одному на строке. возможно сжатие zip.

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный

или

 ,"stat.uni" : { -- адреса для обработки получаются из запроса Универсальной статистики

                 -- подразумевается unique = 1

                "filter" : [ условие выборки как у запроса в вызове stat.uni ]

               ,"have"   : [ не обязательно. условие отбора поле группировки в фильтре ]

               ,"cache"  : [ настройки кэширования как у запроса в вызове stat.uni ]

               ,"select" : [ ... ] -- используйте только если не подходит значение по умолчанию [ "member.email" ]

               }

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный

 ,"track.info" : "дополнительная информация которая будет потом доступна в track.list/get" -- строка 1024 байта. не обязательно
}

ответ


{

 <общие поля>

-- для асинхронного запроса

,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

-- для синхронного запроса

,"list" : {
           "адрес-1" : 0|1|кодошибки -- результат удаления - 1 - удалён, 0 - нет, кодошибки - синтаксическая ошибка в адресе/телефоне

          ,"адрес-2" : 0|1|кодошибки

           .................
          }

}

Очистить стоп-лист

Удаляются все записи из стоп-листа.

Удаление из пользовательского стоп-листа работает в пределах небольшого месячного лимита не работает.

Вызов асинхронный.


{

  "action" : "stoplist.erase" 

 ,"type" : "тип стоп-листа" -- all, owner, member. по умолчанию owner

 ,"sender" : "адрес отправителя" -- по умолчанию пусто - удаляются записи глобального стоп-листа
                                 -- иначе из стоп-листа данного отправителя
                                 -- специальное значение " ALL " (пробелы вокруг !) для удаления и из глобального стоп-листа
                                 -- и листов всех отправителей

 ,"track.info" : "дополнительная информация которая будет потом доступна в track.list/get" -- строка 1024 байта. не обязательно

}

ответ


{

    <общие поля>

,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

}

вверх

Действия по расписанию

Действия по расписанию позволяют запланировать исполнение api-запроса (и даже нескольких при использовании batch) по гибкому расписанию.

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

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

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

Не активное действие по расписанию можно использовать как хранилище сложного api-запроса и выполнять по мере надобности через cron.runonce

При планировании запросов issue.send параметр sendwhen допустим только в значениях now и test.

Если действие выполняемое по расписанию создаёт трекер, то в параметрах такого трекера запоминается какой номер действия по расписанию этому причина.

Cписок действий по расписанию

{

 "action" : "cron.list" 

-- параметры фильтрации, должен быть хотя бы один параметр
--
-- если выбрана последняя порция списка то ответ содержит "last_page" : 1
--
-- доступны поля
--
-- cron.id           -- "идентификатор действия по расписанию" 
-- cron.name         -- "название действия по расписанию" 
-- cron.active       -- "активность действия по расписанию" 
-- cron.lastran      -- "дата и время последнего запуска действия по расписанию (Ys)" 
-- cron.login        -- "владелец, строковое значение" 
-- cron.dt_from      -- "дата начала действия" 
-- cron.dt_till      -- "дата окончания действия" 
-- cron.reltype      -- число
-- cron.relref       -- число
-- cron.errruns      -- "число ошибочных запусков подряд" 
-- cron.errtracker   -- "номер трекера с последней ошибкой
-- cron.create.date  -- "дата создания" (Ys)
-- cron.update.date  -- "дата изменения" (Ys)
-- cron.action       -- "код api-вызова" 
--

,"filter" : [ фильтр в синтаксисе stat.uni ]

,"order" : [ сортировка ответа в синтаксисе stat.uni ]

,"skip" : количество пропускаемых записей от начала списка -- по умолчанию 0

,"first" : количество выбираемых записей после skip -- по умолчанию 50, не более 50

}

ответ

{

  <общие поля>

 ,"list" : [

             {

                "id"   : идентификатор действия по расписанию

               ,"name" : "название действия по расписанию" 

               ,"active" : "активность действия по расписанию" 

               ,"create.date" : "дата создания" (Ys)

               ,"update.date" : "дата изменения" (Ys)

               ,"action" : "код api-вызова из do" 

               ,"lastran" : "дата и время последнего запуска действия по расписанию" 

               ,"sublogin" : "владелец" 

               ,"dt.from" : "дата начала действия" 

               ,"dt.upto" : "дата окончания действия" 

               ,"error.runs" :  "число ошибочных запусков подряд" 

               ,"error.tracker": "номер трекера с последней ошибкой

               ,"reltype" : ...

               ,"relref" : ...
             }

             ...

           ]

}

Чтение действия по расписанию

{

 "action" : "cron.get" 

 ,"id" : идентификатор действия по расписанию

}

ответ

{

  <общие поля>

 ,"obj" : {
           "id"   : идентификатор действия по расписанию

           ,"sublogin" : "владелец" 

           ,"name" : "название действия по расписанию" 

           ,"active" : "активность действия по расписанию" 

           ,"create.date" : "дата создания" (Ys)

           ,"update.date" : "дата изменения" (Ys)

           ,"action" : "код api-вызова из do" 

           ,"dt.from" : "дата начала действия" 

           ,"dt.upto" : "дата окончания действия" 

           ,"dailyrate" : "максимальное число запусков в день" 

           ,"totalrate" : "максимальное число запусков вcего" 

           ,"lastran" : "дата и время последнего запуска действия по расписанию" -- с точностью Ys. этот параметр доступен только на чтение

           ,"dailyruns" : "количество запусков за день" -- не текущий день, а за тот в который в lastran. этот параметр доступен только на чтение

           ,"totalruns" : "количество запусков всего" 

           ,"do" : {
                     -- выполняемое действие
                   }

           -- все списки возвращаются в полном виде. Если что-то при создании было указано как "ALL", то при чтении это будет список всех возможных значений.

           ,"minute" : [ список минут ]

           ,"hour" : [ список часов ]

           ,"day" : [ список дней месяца ]

           ,"weekday" : [ список дней недели ]

           ,"workday" : [ список признаков рабочего дня ]

           ,"error.runs" :  "число ошибочных запусков подряд" 

           ,"error.tracker": "номер трекера с последней ошибкой

           ,"reltype" : ...

           ,"relref" : ...
           }
}

Создание действия по расписанию

{

 "action" : "cron.create" 

 ,"name" : "название действия по расписанию

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

 ,"active" : "активность действия по расписанию" -- 0 - не выполнять, 1 - выполнять при наступлении времени

 ,"dt.from" : "дата начала действия" -- не обязательно. дата (Ys) начала проверок на возможность запуска действия
                                     -- по умолчания - без ограничения

 ,"dt.upto" : "дата окончания действия" -- не обязательно. дата (Ys) начиная с которой проверки на возможность запуска действия не делаются
                                        -- по умолчания - без ограничения

 ,"dailyrate" : "" | 1+ -- максимальное число запусков в день. "день" это календарные сутки с 00:00 до 23:59. не обязательно
                        -- "" - не ограничено (по умолчанию)
                        -- число от 1 и больше - величина ограничения

 ,"totalrate" : "" | 1+ -- максимальное число запусков вcего. не обязательно
                        -- "" - не ограничено (по умолчанию)
                        -- число от 1 и больше - величина ограничения
 ,"do" : {
           -- выполняемое действие
           --
           -- вызов API без указания параметров авторизации
           --
           -- будет выполняться по указанному расписанию от имени пользователя указанного в sublogin
           --
           -- если такой пользователь на момент выполнения будет удалён или заблокирован, то выполнения не произойдёт
         }

  ,"minute" : [ список минут ] -- по которым срабатывает действие. от 0 до 55 с шагом 5

-- не обязательно. отсутствие трактуется как "ALL" 
-- вместо списка можно указать "ALL" что будет обозначать "все доступные значения".

  ,"hour" : [ список часов ] -- по которым срабатывает действие. от 0 до 23

  ,"day" : [ список дней месяца ] -- по которым срабатывает действие. от 1 до 31

  ,"weekday" : [ список дней недели ] -- по которым срабатывает действие. от 1 (Пн) до 7 (Вс)

  ,"workday" : [ список признаков рабочего дня ] -- по которым срабатывает действие. 0 - срабатывает в не рабочий день, 1 - срабатывает в рабочий день

  ,"reltype" : ...

  ,"relref" : ...
}

ответ

{

  <общие поля>

 ,"id" : "идентификатор созданного действия по расписанию" 

}

Изменение действия по расписанию

Если работа ведётся не основным пользователем, то вы можете менять только свои задания.

Обновляются только указанные в запросе поля.

{

 "action" : "cron.set" 

 ,"id"   : идентификатор действия по расписанию

 ,"obj" : {

           "name" : название действия по расписанию

          ,"sublogin" : "владелец" -- только при работе основным логином
                                   -- возможность сменить владельца

          ,"active" : "активность действия по расписанию" 

          ,"dt.from" : "дата начала действия" 

          ,"dt.upto" : "дата окончания действия" 

          ,"dailyrate" : "максимальное число запусков в день" 

          ,"totalrate" : "максимальное число запусков вcего" 

          ,"do" : {
                   -- выполняемое действие
                  }

          ,"minute" : [ список минут ] или "ALL" 

          ,"hour" : [ список часов ] или "ALL" 

          ,"day" : [ список дней месяца ] или "ALL" 

          ,"weekday" : [ список дней недели ] или "ALL" 

          ,"workday" : [ список признаков рабочего дня ] или "ALL" 

          ,"reltype" : ...

          ,"relref" : ...
          }

  -- не обязательно

 ,"return_fresh_obj" : 0|1
}

ответ

{

  <общие поля>

}

Удаление действия по расписанию

Если работа ведётся не основным пользователем, то вы можете менять удалять только свои задания.

{

 "action" : "cron.delete" 

 ,"id" : идентификатор действия по расписанию
}

ответ

{

  <общие поля>

}

Разовый запуск действия по расписанию

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

Параметр lastran не обновляется.

Запускается от имени текущего логина.

Так же используйте этот вызов при необходимости тестировать настроенный импорт YML-файлов или при необходимости произвести его вне расписания. Указывайте id от настроенного импорта YML.

{

 "action" : "cron.runonce" 

 ,"id" : идентификатор действия по расписанию

}

ответ

{

  <общие поля>

,"result" : {
             результат выполнения
            }

}

вверх

Cобытийные действия / Триггерные рассылки

Описание событыйных действий

Введение

Cобытийные действия предназначены для автоматизации реакции системы в ответ на те или иные события происходящие с пользователем или вызванные его действиями.

Для задания автоматической реакции системы на те или иные действия/бездействия пользователя создайте "Последовательность" описывающую что ("Действия") в ответ на какие события ("Cобытия") в каком порядке ("Шаги", "Варианты") должно происходить.

На каждом Шаге последовательности можно задать несколько сценариев развития событий создав несколько Вариантов описав какие События активируют какой вариант (один вариант могут активировать несколько разных действий) и задать какие Действия (одно или несколько) должны быть выполнены в каждом из вариантов.

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

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

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

Обработка событий

Каждое событие произошедшее в системе асинхронно проверяется не подходит ли оно в какой-либо последовательности к её первому шагу или к текущему шагу уже находящегося на последовательности пользователя.

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

Событие может подойти к нескольким последовательностям и сработает во всех сразу куда подошло.

Подходящее событие может быть проигнорировано если пользователь или последовательно находятся в состоянии паузы.

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

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

Если у последовательности установлен тип адресов отличный от any, то она будет принимать события только для адресов подходящего типа.

Прохождение последовательности

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

Обязательно наличие шага номер 1 - с него начинается прохождение последовательности.

Когда событие вызывает срабатывание одного из вариантов на том шаге последовательности на котором находится подписчик, то выполняются соответствующие этому варианту действия и подписчик переходит на шаг последовательности заданный в параметре next сработавашего варианта. (если, конечно, в действиях не было действия "Уйти на другую последовательность", "Завершить прохождение последовательности")

Это позволяет организовать самую сложную логику ветвлений при прохождении последовательности.

Ограничения:

Свойства последовательности

Тип адресов

Принимаются только для указанного типа адресов если только не установлено any

Однократность

Можно ли ещё раз начать прохождение последовательности, если она уже была пройдена или проходится в данный момент.

Параллельность

В данный момент не реализовано !

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

Список исключений

Группа-список участники которой не попадут на последовательность даже если произошло нужное событие.

Закрытость

Новые участники не могут начать последовательность при наступлении подходящих событий.

Однако новый участник может быть добавлен вызовом sequence.member.start

Это полезно для тестирования. Отметьте последовательность закрытой на время её создания и тестирования, что бы ни кто из обычных пользователей не мог случайно начать её прохождение. А тестовых пользователей вы можете добавить через sequence.member.start

Ни как не влияет на прохождение последовательности уже имеющимися участниками.

Пауза

Cобытия не учитываются и значит ни кто не продвигается по последовательности и не начинает её. Пропущенные события теряются.

Возобновляемость

Возобновление прохождения при увеличении количества шагов.

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

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

Не возобновляется участник и так проходящий последовательность в момент её удлинения не смотря на параметр parallel.

Статистика работы последовательности

Статистика работы последовательности доступна через объект sequence.* вызова Универсальной Статистики stat.uni.

Например, можно узнать сколько подписчиков на каких шагах последовательности находятся или как они её завершили. Получить список находящихся на конкретном шаге.

вверх

События

Никогда

Событие которое никогда не произойдёт.

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

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

{

 "type" : "never" 

}

Иначе

Событие-затычка для member.match

Не может быть на первом шаге

Если на шаге ни у одного варианта нет событий кроме member.match и never, то должен быть вариант с единственным событием - else - который сработает если не сработает ни один member.match

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

{

 "type" : "else" 

}

Вероятностный выбор

Не может быть на первом шаге

На шаге может быть от 1 до нужного числа Вероятностных событий.

Сумма вероятностей всех событий во всех вариантах на одном шаге должна равняться 100 и на шаге не должно быть ожидаемых событий других типов.

При переходе на шаг вычисляется случайное число от 1 до 100 которое указывает какое из событий random будет считаться произошедшим с учётом указанной у них вероятности.

Процент равный 0 - способ временно отключить вариант.

{
 "type" : "random"  -- вероятностное событие

,"probability" : "процент вероятности срабатывания" - цело число от 0 до 100
}

Новая регистрация без подтверждения

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

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

{

 "type" : "member.new.no-confirm" 

}

Новая регистрация c подтверждением

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

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

{

 "type" : "member.new.confirm" 

}

Подтверждение регистрации

Происходит когда пользователь подтверждает регистрацию.

Не важно каким путём - по ссылке подтверждения, апи-вызов, заполнение формы.

{

 "type" : "member.confirm" 

}

Самостоятельное удаление регистрации

Заменено на unsub.all. Используйте новое событие.

Отписка от всего

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

{

 "type" : "unsub.all" 

}

Отписка по отправителю

Происходит когда пользователь отписывается при активной настройке "Отписка по отправителю" с занесением в стоп-лист по отправителю.

{

 "type" : "unsub.sender" 

,"sender" : "адрес отправителя" -- при указании адреса - только при отписке от отправителя с данным адресом
                                -- пусто или параметр не указан - то срабатывает при любой отписке по отправителю

,"addr_type" : "тип адреса"     -- не обязательно, если отсутствует или пусто, то автоопределение между email и msisdn

}

Отписка по тематике

Происходит когда пользователь отписывается от конкретной тематики с занесение в группу-список отписавшихся для этой тематики

{

 "type" : "unsub.topic" 

,"unsub_list" : "код группы-списка тематической отписки" -- не обязательно, если отсутствует или пусто, то срабатывает на любую тематическую отписку

,"unsub_topic.name" : "название группы" -- при вызове get
}

Прошло времени

Не может быть в списке у вариантов первого действия последовательности.

Происходит когда с момента попадания на данных шаг прошёл указанный интервал времени равный указанному количеству дней, часов и минут.

{

 "type" : "time.elapsed" 

,"interval" : "DDDDD hh:mm" или "hh:mm" или "mm" 
             -- DDDDD - до пяти цифр количества дней
             -- hh    - количество часов
             -- mm    - количество минут

}

Точное время

Не может быть в списке у вариантов первого действия последовательности.

Происходит когда текущее время совпало с заданными ожидаемыми.

Зачем ? Например удобно накопить желающих начать какой либо курс и разом начать его в удобный день высылкой первого письма.

Можно указать:

  1. месяц, день и время - сработает раз в год
  2. день и время - сработает раз в месяц если в нём есть такой день
  3. только время - сработает раз в день
  4. день недели - сработает раз в неделю в 0 часов 0 минут
  5. день недели и время - сработает раз в неделю в указанный момент
{

 "type" : "time.happened" 

,"weekday" : "номер дня недели" -- не обязательно при наличии time
                     -- 1-6 - Пн-Сб, 7 - Вс
                     -- вызов sequence.steps.set дополнительно принимает 0 как Вс, но при записи преобразует в 7

,"time" : "дата и время" -- не обязательно при наличии weekday
                     -- YYYY - год
                     -- ММ - месяц
                     -- DD - день
                     -- hh - час
                     -- mm - минута

-- Допустимые сочетания дня недели, даты и времени:
-- "YYYY-MM-DD hh:mm" 
--         "DD hh:mm" 
--            "hh:mm" 
--  weekday
--  weekday + "hh:mm" 
}

Время сейчас

Не может быть в списке у вариантов первого действия серии.

Проверяется сразу при переходе на шаг. Срабатывает если при переходе на шаг текущее время (день недели/год/месяц/день/час) совпали с заданными ожидаемыми.

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

Зачем ? Например, недельный цикл писем. Присоеденившийся в Пн получает по одному письму в день. Присоеденившийся по вторник - сразу письма за понедельник и вторник и далее по одному в день. Присоеденившийся в среду - три письма и далее по одному в день. Присоеденившийся в чертверг и позже - ждут понедельника (событие time.happened) и начинают получать дождавшись.

{

 "type" : "time.now" 

,"weekday" : "номер дня недели" 
                     -- 1-6 - Пн-Сб, 7 - Вс
                     -- вызов sequence.steps.set дополнительно принимает 0 как Вс, но при записи преобразует в 7

,"time" : "дата и время" -- не обязательно при наличии wekday
                     -- YYYY - год -- текущий, если не задан
                     -- ММ - месяц
                     -- DD - день
                     -- hh - час
                     -- mm - минута

,"op": "<=" | "!=" | "==" | ">=" -- операция сравнения текущего времени с предполагаемым в событии

-- Допустимые сочетания дня недели, даты и времени, операции:
--
--"YYYY-MM-DD hh:mm" - "<=" | ">=" 
--        "DD hh:mm" - "<=" | ">=" 
--           "hh:mm" - "<=" | ">=" 
-- weekday           - "<=" | "!=" | "==" | ">=" 
-- weekday + "hh:mm" - "<=" | ">=" 

}

Клик по ссылке

Происходит когда пользователь нажимает на ссылку в высылаемых ему письмах.

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

Клик по определённой ссылке в письме обычной рассылки ("свободный клик") приведёт к наступления события "Клик" во всех последовательностях где такое событие ожидается на первом шаге для этой ссылки и где состояние настроек последовательности позволяет ему начать её прохождение.

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

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

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

Другим словами - событие "Клик по ссылке" НА первом шаге сработает только, если оно произошло в письме высланном обычной рассылкой.

А событие "Клик по ссылке" НЕ на первом шаге сработает только, если оно произошло в письме ранее высланном с этой же последовательности.

Если от события "Клик по ссылке" вы хотите поведения "и когда-то ранее переходил по такой-то ссылке", то это реализуется с помощью события "Данные совпадают" с фильтром отбора по Универсальной Статистике по условию перехода по интересующей ссылке.

{

 "type" : "click.link" 

,"url" : "ссылка" 

}

Чтения письма

Происходит когда фиксируется чтение письма высланного ранее в данной последовательности или в любой другой (параметр "any").

Чтение обычных рассылок событие не генерируют.

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

{

 "type" : "read" 

,"draft" : "номер черновика" -- или пусто если не требуется

,"any"   : 0|1 -- 0 - срабатывает только в своей последовательности, 1 - в любой

,"draft.name"    : "название черновика" -- при вызове get
,"draft.channel" : "канал черновика" -- при вызове get

}

Доставка письма

Только для personal и при наличии в выпуске параметра sequence_event : 1

Только окончательный статус когда точно или доставлено или нет.

{

 "type" : "deliv" 

,"draft" : "номер черновика или null если не проверять" 

,"any"   : 0|1 -- 0 - срабатывает только в своей последовательности, 1 - в любой

,"status" : null - любой
               1 - успещно
              -1 - не успешно
            -ххх - конкретный неуспешный статус

}

Изменение данных

Первый вариант.

Происходит когда при изменении данных пользователя удовлетворяются (или не удовлетворяются - в зависимости от параметра *.not) условия обоих фильтров если заданы оба и единственного фильтра, если задан один.

Фильтр БЫЛО проверяет данные до изменения. Фильтр СТАЛО - после изменений. Должен быть указан одни любой или оба фильтра сразу.

Если указан только фильтр БЫЛО, то пользователь удовлетворяет условия события, если его старые данные (до изменения) прошли проверку успешно, а новые (после изменения) - НЕ успешно. Т.е. данные перестали попадать под условие.

Если указан только фильтр СТАЛО, то пользователь удовлетворяет условия события, если его старые данные (до изменения) прошли проверку НЕ успешно, а новые (после изменения) - успешно. Т.е. данные стали попадать под условие.

Если указаны ОБА фильтра, то пользователь удовлетворяет условия события, если его старые данные (до изменения) успешно прошли проверку по фильтру БЫЛО, а новые (после изменения) успешно прошли проверку по фильтру СТАЛО. Т.е. старые данные перестали попадать под условие БЫЛО и новые стали попадать под условие СТАЛО.

{

 "type" : "member.change" 

---------
-- фильтр "БЫЛО. может отсутствовать при наличии фильтра "СТАЛО" 

,"was.not" : 0|1 -- 0 - фильтр соблюдён если пользователь удовлетворяет условию
                 -- 1 - фильтр соблюдён если пользователь НЕ удовлетворяет условию

-- и одно из:

,"was.group" : "код группы" 

,"was.group.name" : "название группы" -- только при вызове get

,"was.group.addr_type" : "тип адресов группы" -- только в get

,"was.group.type" : "тип группы" -- только при вызове get

-- или

,"was.cond" : { единичное условие фильтра как в group.filter.set. при вызове get дополнительно aid.name и qid.name }

-- или

,"was.cond" :  [ фильтр из несколько условии как в group.filter.set ]

---------
-- фильтр "СТАЛО". может отсутствовать при наличии фильтра "БЫЛО" 

,"new.not" : 0|1 -- 0 - фильтр соблюдён если пользователь удовлетворяет условию
                 -- 1 - фильтр соблюдён если пользователь НЕ удовлетворяет условию

-- и одно из:

,"new.group" : "код группы" 

,"new.group.name" : "название группы" -- только при вызове get

,"new.group.addr_type" : "тип адресов группы" -- только в get

,"new.group.type" : "тип группы" -- только при вызове get

-- или

,"new.cond" : { единичное условие фильтра как в group.filter.set. при вызове get дополнительно aid.name и qid.name }

-- или

,"new.cond" :  [ фильтр из несколько условии как в group.filter.set ]

}

Второй вариант.

Происходит когда данные изменились, но не важно как - просто предыдущее значение не равно новому.

По умолчанию производится строчное сравнение значений.

{

 "type" : "member.change" 

,"as_number" : 0|1 -- не обязательно. сравнивать значения как числа.

-- и одно из:

,"field" : {
            "aid" : "код анкеты" 
           ,"qid" : "код вопроса" 

            -- при вызове get дополнительно aid.name и qid.name
           }
--- или

,"field" : {
            "datakey" : "ключи данных" 
           }

}

Расширенная информация об изменении данных

Данная возможность подключается через Службу поддержки

При использовании второго варианта разница между старым и новым состоянием данных при использовании второго варианта "Данные просто изменились" может быть использовано в выпуске письма, если среди действий на данном шаге триггера есть "выслать письмо" - описание найденной разницы передаётся в extra в параметре trigger.

trigger содержит массив описывающий каждое найденное изменение

Возможные варианты записей

Данные добавлены

В версии до изменения ключа данных не было, в новой - есть

{ "change" : "add", "dk" => "ключ данных", "value" => новоезначение }
Данные удалены

В версии до изменения ключ данных был, в новой - удалён

{ "change" : "remove", "dk" => "ключ данных", "value" => прежнеезначение }
Данные изменены

Значение по ключу данных изменилось

{ "change" : "change", "dk" => "ключ данных", "vwas" => прежнеезначение, "vnow" => новоезначение }

Расширенное отслеживание изменений

Данная возможность подключается через Службу Поддержки

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

Если используется расширенное сравнение, а тип данных до и после не сопоставим (например был массив, а стал объект), то изменение не будет засчитано так как не нет способа сопоставления элементов (какой ключ объекта выбрать для сравнения с элементом N массива ?)

Тестовый пример


{
 "a" : { --
        "b" : [ -- a.b
                {  -- a.b[0]
                 "c" : { -- a.b[0].с
                        "d" : "123" -- a.b.[0].c.d
                       ,"e" : "456" -- a.b.[0].c.e
                       }
                }
               ,{  -- a.b[1]
                 "c" : { -- a.b[1].с
                        "f" : "789" -- a.b.[0].c.f
                       ,"g" : "xyz" -- a.b.[0].c.g
                       }
                }
              ]
        }
}

Просто сравнение ключей данных

Формат записи

a.b

Сравниваются значения до и после по ключу a.b

В тестовом примере будут сравнены состояния до и после для массива a.b и замечено изменение его содержимого

Сравнение компонентов ключей данных

Формат записи

a.b.*

Сравниваются эквивалентные компоненты до и после из ключа a.b

Для массива - с одинаковым индексом

Для объекта - с одинаковыми ключами

Для скаляра - изменение замечено не будет, так как скаляр не составной тип

В тестовом примере будут сравнены состояния до и после для a.b[0] и a.b[1]

Сравнение пути в компонентах ключей данных

Формат записи

a.b.*.с

Сравниваются значения по ключу данных с применённому к эквивалентным компонентам до и после из ключа a.b

Если компонент скаляр - изменение замечено не будет, так как скаляр не может иметь внутри себя дополнительный уровень данных

В тестовом примере будут сравнены состояния до и после для a.b[0]c и a.b[1].c

Если вам понадобился именно этот вариант сравнения, то возможно данные можно организовать как-то проще.

Сравнение субкомпонентов пути в компонентах ключей данных

Формат записи

a.b.*.с.*

Сравниваются между собой компоненты значения по ключу данных с применённому к эквивалентным компонентам до и после из ключа a.b

Если компонент скаляр - изменение замечено не будет, так как скаляр не может иметь внутри себя дополнительный уровень данных

В тестовом примере будут сравнены состояния до и после для a.b[0]c.d, a.b[0]c.e, a.b[1].c.f и a.b[1].c.g

Если вам понадобился именно этот вариант сравнения, то скорее почти наверняка переусложнили структуру данных и стоит переделать

Совпадение данных

Происходит когда пользователь переходит на данных шаг и его текущие данные попадают (или при match.not = 1 - не попадают) под описанное условие.

Применение - условные ветвления.

На шаге должны быть ОБЯЗАТЕЛЬНО предусмотрены другие альтернативные события (хотя бы else) которые могут продвинуть пользователя дальше. Иначе, если совпадения не будет, пользователь застрянет на этом шаге навечно.

{

 "type" : "member.match" 

,"match.not" : 0|1 -- 0 - совпадение если пользователь попал под условие
                   -- 1 - совпадение если пользователь НЕ попал под условие
-- одно из:

,"match.group" : "код группы" 

,"match.group.name" : "название группы" -- только при вызове get

,"match.group.addr_type" : "тип адресов группы" -- только в get

,"match.group.type" : "тип группы" -- только при вызове get

-- или

,"match.cond" : { единичное условие фильтра как в group.filter.set. при вызове get дополнительно aid.name и qid.name }

-- или

,"match.cond" :  [ фильтр из несколько условии как в group.filter.set ]

}

Форма заполнена

Происходит когда заполняется форма опросов.

В ситуациях когда заполнение формы не требует подтверждения происходят последовательно сразу два события: сначала form.filled и сразу за ним form_completed

Должны быть указаны form.id или origin.id или оба.


 "type" : "form.filled" 

,"form.id" : "код формы" -- null - любой, число - конкретно эта

,"origin.id" : "код источника" -- null - любой, число - конкретно этот

,"form.name" : название формы -- только при вызове get

,"origin.name" : название источника -- только при вызове get

Форма подтверждена

Происходит когда пользователь подтверждает заполнение формы опросов.

В ситуациях когда заполнение формы не требует подтверждения происходят последовательно сразу два события: сначала form.filled и сразу за ним form_completed

Должны быть указаны form.id или origin.id или оба.


 "type" : "form.completed" 

,"form.id" : "код формы" -- null - любой, число - конкретно эта

,"origin.id" : "код источника" -- null - любой, число - конкретно этот

,"form.name" : название формы -- только при вызове get

,"origin.name" : название источника -- только при вызове get

Целевая страница достигнута

Происходят когда пользователь достиг указанно целевой страницы


 "type" : "target.achieved" 

,"url" : "ссылка или маска ссылки" -- "*" заменяет любое количество символов, "?" - один символ

Команда Telegram-боту

Получена команда боту


 "type" : "tg.command" 

,"tg" : номер бота (т.е. номер authext)

,"tg.name" : "название бота" -- только в get

,"command" : "команда" -- null - любая команда, строка - конкретно эта

Текст Telegram-боту

Получена команда боту


 "type" : "tg.text" 

,"tg" : номер бота (т.е. номер authext)

,"tg.name" : "название бота" -- только в get

,"text" : "текст" -- null - любой, строка - конкретно эта номализованная и токенизированная строка совпадает с получено

вверх

Действия

Ничего не делать

{

 "type" : "noop" 

}

Выслать письмо

Пользователю высылается персонализированное письмо на основе черновика выпуска рассылки.

Если указан черновик для sms или viber или vknotify, то будет выслано sms или отправлено сообщение по номеру телефону связанному с email.

Если связанного номера телефона нет, то не произойдёт ничего.

Для привязки номера телефона заранее используйте member.head.attach

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

{

 "type" : "send.letter" 

,"draft" : "код черновика рассылки" -- обязательно
                                    -- черновик должен иметь заполненный адрес отправителя

,"draft.name" : "название черновика" -- только при вызове get

,"draft.channel" : "канал черновика" -- только при вызове get
}

Выслать письмо подтверждения

Используется вызов member.sendconfirm

Назначение параметров - аналогочное

{

 "type" : "send.confirm" 

,"letter" : "код черновика письма" 

-- одно из

 ,'confirm' : 1 -- высылка писем для подтверждения внесения в базу

 ,'unsubcancel' : 1 -- высылка писем для удаления из глобального стоп-листа

 ,'unsubsendercancel' : 1 -- высылка писем для удаления из стоп-листа по отправителю. отправитель - тот что указан в черновике letter

,"letter.name" : "название черновика" -- только при вызове get

,"letter.channel" : "канал черновика" -- только при вызове get
}

Вызвать внешнюю ссылку

Указанная ссылка вызывается методом GET. Результат вызова игнорируется.

Вхождение символов EMAIL заменяется на адрес пользователя.

Так же можно подставлять данные пользователя используя команду подстановки как шаблонах выпусков @[% anketa.XXX %]@

{

 "type" : "http.get" 

,"url"  : "http/https ссылка" 

}

Изменить данные

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

В целом аналогично вызову member.update

{

 "type" : "member.update" 

 ,"fire_event" : 0 | 1 -- при выполнении записать событие Изменение Данных (member.change)
                       -- по умолчанию 0 - не записывать

-- по ключам данных DK

 ,"datakey" => [ массив команд аналогичный member.set ]

-- устаревшее по анкетам АВО

 ,"if_has" : что делать, если изменяемый пункт анкеты уже заполнен

             -- "overwrite" - заменить старое значение новым из формата

             -- "merge"     - объединить старое и новое значения

             -- "skip"      - оставить старое значение

 ,"if_hasnt" : что делать, если изменяемый пункт анкеты ещё не заполнен

             -- "set"  - заполнить указанным в формате значением

             -- "skip" - оставить пункт незаполненным

-- одно из

,"format" : "код формата" 

,"format.name" : "название формата" -- только при вызове get

или

,"field" : {

             "aid" : "код анкеты" 

            ,"qid" : "код ответа" 

            ,"answer" : "значение ответа используемое при заполнении" 

            ,"aid.name" : "название анкеты" -- только при вызове get

            ,"qid.name" : "название анкеты" -- только при вызове get
            }

,"track.info" : "дополнительная информация которая будет потом доступна в track.list/get" -- строка 1024 байта. не обязательно
}

Внести в группу-список

Вносит пользователя в указанную группу-список если его там ещё нет.

{

 "type" : "group.in" 

,"group" : "код группы" 

,"group.name" : "название группы" -- только при вызове get

,"group.addr_type" : "тип адресов группы" -- только в get

,"group.type" : "тип группы" -- только при вызове get

}

Удалить из группы списка

Удаляет пользователя из указанной группы-списка если он там есть.

{

 "type" : "group.out" 

,"group" : "код группы" 

,"group.name" : "название группы" -- только при вызове get

,"group.addr_type" : "тип адресов группы" -- только в get

,"group.type" : "тип группы" -- только при вызове get

}

Запустить параллельно ещё последовательность

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

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

Выполнение указанной последовательности начинается с действий её шага 1. Условия её шага 1 не проверяются из-за того что участник начинает прохождение насильно. Последовательность должны иметь только одни вариант на шаге 1 иначе не понятно какой из нескольких вариантов выполнить.

{

 "type" : "sequence.start" 

,"sequence : "код последовательности" -- при отсутствии запускает саму себя ещё раз

,"sequence.name" : "название последовательности" -- только при вызове get
}

Остановить последовательность

Все участия пользователя в указанной последовательности (если имеются) принудительно завершаются.

{

 "type" : "sequence.stop" 

,"sequence : "код последовательности" -- при отсутствии останавливает саму себя

,"sequence.name" : "название последовательности" -- только при вызове get
}

Уйти в другую последовательность

Текущая последовательность принудительно завершается и запускается указанная новая последовательность с участием этого пользователя.

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

Выполнение указанной последовательности начинается с действий её шага 1. Условия её шага 1 не проверяются из-за того что участник переводится туда насильно. Последовательность должны иметь только одни вариант на шаге 1 иначе не понятно какой из нескольких вариантов выполнить.

{

 "type" : "sequence.goto" 

,"sequence : "код последовательности" -- при отсутствии перезапускает саму себя

,"sequence.name" : "название последовательности" -- только при вызове get
}

вверх

Список последовательноcтей

{

 "action" : "sequence.list" 

-- параметры фильтрации, должен быть хотя бы один параметр
--
-- если выбрана последняя порция списка то ответ содержит "last_page" : 1
--
-- доступны поля
--
-- sequence.id                   -- идентификатор последовательности
-- sequece.addr_type             -- тип адресов
-- sequence.name                 -- название последовательности
-- sequence.parallel             -- параллельность последовательности
-- sequence.onlyonce             -- последовательность однократна
-- sequence.closed               -- закрытость для новых участников
-- sequence.pause                -- остановка последовательности
-- sequence.create.date          -- дата-время создания -- (Ys)
-- sequence.update.date          -- дата-время последнего изменения -- (Ys)
-- sequence.dictnode             -- номер узла словаря
-- sequence.reltype              -- число
-- sequence.relref               -- число

,"filter" : [ фильтр в синтаксисе stat.uni ]

,"order" : [ сортировка ответа в синтаксисе stat.uni ]

,"skip" : количество пропускаемых записей от начала списка -- по умолчанию 0

,"first" : количество выбираемых записей после skip -- по умолчанию 50, не более 50

}

ответ

{

 <общие поля>

,"list" : [

            {

             "id" : код последовательности

            ,"name" : "название" 

            }

            ...

           ]

}

вверх

Cоздать последовательность

{

  "action" : "sequence.create" 

 ,"name" : "название последовательности" -- обязательно

 ,"addr_type" : "any|email|sms|viber|push|vk|tg|vknotify" -- тип адресов которые принимает последовательность
                                                 -- any - любые

 ,"onlyonce" : 0 | 1 -- последовательность однократна. по умолчанию 0
                     -- 0 - нет
                     -- 1 - да

 ,"parallel" : -1 | 0 | +1 -- параллельность последовательности. по умолчанию 0.
                           -- *В данный момент не реализовано и всегда 0 !*
                           -- 0 - нет
                           -- +1 - да
                           -- -1 - да, с прерыванием текущих

 ,"closed" : 0 | 1 -- закрытость для новых участников. по умолчанию 0
                   -- 0 - нет
                   -- 1 - да

 ,"pause" :  0 | 1 -- остановка последовательности. по умолчанию 0
                   -- 0 - нет
                   -- 1 - да

 ,'exclude_list" : "код группы-списка или группы-филтра исключения из последовательности" - не обязательно
                    -- участник подходящий под группу молча не начинает прохождение последовательности
                    -- из-за наступления события или из-за перехода с другой последовательности
                    -- при ручном загоне на последовательность через sequence.member.start можно указать игнорирование
                    -- исключает независимо от exclude_filter

 ,'exclude_filter" : [ код фильтра как group.filter.set ] или null - не обязательно
                    -- участник подходящий под фильтр молча не начинает прохождение последовательности
                    -- из-за наступления события или из-за перехода с другой последовательности
                    -- при ручном загоне на последовательность через sequence.member.start можно указать игнорирование
                    -- исключает независимо от exclude_list

 ,"info" : "строка произвольных данных. 1024 байта" -- необязательный
}

ответ

{

 <общие поля>

 , "id" : код созданной последовательности

}

вверх

Прочитать последовательность

{

  "action" : "sequence.get" 

 ,"id" : код последовательности
}

ответ

{

 <общие поля>

 ,"obj" : {

            ,"id" : код последовательности

            ,"name" : "название последовательности" 

            ,"addr_type" : "тип адресов" 

            ,"onlyonce" : "однократность последовательности" 

            ,"parallel" : "параллельность последовательности" 

            ,"closed" : "недоступность для новых участников" 

            ,"pause" : "остановка последовательности" 

            ,"info" : "строка произвольных данных" 

            ,"create.date" : "дата-время создания" -- (Ys)

            ,"update.date" : "дата-время последнего изменения" -- (Ys)

            ,"exclude_list" : "код группы исключения из последовательности" (null)

            ,"exclude_list.name" : "....." (null)

            ,'exclude_filter" : [ код фильтра как group.filter.set ] или null
           }
}

вверх

Изменить последовательность

Изменяются только явно заданные в запросе поля

Не заданные - остаются как были

{

  "action" : "sequence.set" 

 , "id" : код последовательности

 ,"name" : "название последовательности" -  необязательный

 ,"addr_type" : "any" -- не обязательный. поменять тип адресов можно только на any

 ,"onlyonce" : "однократность последовательности -  необязательный

 ,"parallel" : "параллельность последовательности" -  необязательный

 ,"closed" : "недоступность для новых участников" -  необязательный

 ,"pause" : "остановка последовательности" -  необязательный

 ,"info" : "строка произвольных данных" -  необязательный

 ,'exclude_list" : "код группы-списка или группы-филтра исключения из последовательности" - не обязательно

 ,'exclude_filter" : [ код фильтра как group.filter.set ] -- не обязательно

  -- не обязательно

 ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ

{

 <общие поля>

}

если "return_fresh_obj" : "1", то ответ -- как на запрос чтения sequence.get

вверх

Удалить последовательность

{

  "action" : "sequence.delete" 

 , "id" : код последовательности

}

ответ

{

 <общие поля>

}

вверх

Получить список шагов

{

  "action" : "sequence.steps.get" 

 , "id" : код последовательности

}

ответ

{

 <общие поля>

 ,"list" : { -- описание шагов последовательности

             "номер шага" : [ -- список вариантов шага

                               { -- вариант

                                "next" : "cледующий шаг" 

                               ,"event" : [ -- список ожидаемых событий
                                            { описание события }
                                           ,{ описание события }
                                           .....
                                          ]

                                ,"action" : [ -- список действий выполняемых при наступлении любого из событий
                                             { описание действия }
                                            ,{ описание действия }
                                            .....
                                            ]
                               }

                             ,{ вариант }

                             ,{ вариант }

                             .....

                             ]

            ,"номер шага" : [
                              ....
                            ]

            ......

           ]

}

вверх

Установить список шагов

Изменение списка шагов полностью заменяет текущий список на новый

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

Остальные пользователи сохраняют номер шага на котором находятся, но "смысл шага" (события, действия, варианты) может стать полностью новым в соответствии с устанавливаемым списком.

Так же должны соблюдаться условия:

{

  "action" : "sequence.steps.set" 

 , "id" : код последовательности

 , "list" : {

              описание шагов последовательности
              как в sequence.steps.get

            }

}

ответ

{

 <общие поля>

}

вверх

Начать прохождение последовательности

Указанные пользователи начинают прохождение указанной последовательности как-будто произошло одно из событий её первого шага.

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

Настройки последовательности могут влиять на возможность того или иного участника начать её прохождение.

Вызов асинхронный для list c двумя и более элементами и для group.


{

  "action" : "sequence.member.start" 

 ,"id" : код последовательности

 ,"ignore_exclude_list" : 0 | 1 -- не обязательно, по умолчанию 0
                                -- игнорировать группу исключения

-- обязателен один из параметров:

 ,"email" : "адрес подписчика" 

или

 ,"list" : [

            "адрес подписчика" 

           ,"адрес подписчика" 

            ........

           ]

или

 ,"group" : код группы для получения списка

 ,"track.info" : "дополнительная информация которая будет потом доступна в track.list/get" -- строка 1024 байта. не обязательно

}

ответ


 <общие поля>

,"track.id" : "номер трекера" 

вверх

Приостановить прохождение последовательности

Указанные пользователи приостанавливаю прохождение указанной последовательности и возобновят его при вызове sequence.member.resume

Вызов асинхронный для list c двумя и более элементами и для group.


{

  "action" : "sequence.member.pause" 

 , "id" : код последовательности

-- обязателен один из параметров:

 ,"email" : "адрес подписчика" 

или

 ,"list" : [

            "адрес подписчика" 

           ,"адрес подписчика" 

            ........

           ]

или

 ,"group" : код группы для получения списка

 ,"track.info" : "дополнительная информация которая будет потом доступна в track.list/get" -- строка 1024 байта. не обязательно
}

ответ


 <общие поля>

,"track.id" : "номер трекера" 

вверх

Возобновить прохождение последовательности

Указанные пользователи возобновляют прохождение указанной последовательности

Вызов асинхронный для list c двумя и более элементами и для group.


{

  "action" : "sequence.member.resume" 

 , "id" : код последовательности

-- обязателен один из параметров:

 ,"email" : "адрес подписчика" 

или

 ,"list" : [

            "адрес подписчика" 

           ,"адрес подписчика" 

            ........

           ]

или

 ,"group" : код группы для получения списка

 ,"track.info" : "дополнительная информация которая будет потом доступна в track.list/get" -- строка 1024 байта. не обязательно

}

ответ


 <общие поля>

,"track.id" : "номер трекера" 

вверх

Прервать прохождение последовательности

Указанные пользователи завершают прохождение указанной последовательности на каком бы её шаге не находились

Вызов асинхронный для list c двумя и более элементами и для group.

{

  "action" : "sequence.member.stop" 

 , "id" : код последовательности

-- обязателен один из параметров:

 ,"email" : "адрес подписчика" 

или

 ,"list" : [

            "адрес подписчика" 

           ,"адрес подписчика" 

            ........

           ]

или

 ,"group" : код группы для получения списка

 ,"track.info" : "дополнительная информация которая будет потом доступна в track.list/get" -- строка 1024 байта. не обязательно

}

ответ


 <общие поля>

,"track.id" : "номер трекера" 

вверх

Участие пользователя в последовательностях

{

  "action" : "sequence.member.membership" 

 , "id" : код последовательности -- не обязательно

 , "email" : "адрес пользователя" 

}

ответ

{

 <общие поля>

 ,"list" : {
            код последовательности : [ -- список описаний шагов
                                       -- структура одно элемента описана в вызове sequence.member.list
                                       { ... }
                                      ,{ ... }
                                      ....
                                     ]

           ,код последовательности : [ -- список описаний шагов
                                       { ... }
                                      ,{ ... }
                                      ....
                                     ]
            .....
           }

вверх

Формы опросов

Формы опросов предназначены для получения информации от пользователей c сохранением её в анкеты.

Предусмотрен механизм верификации заполнения формы при котором данные попадают сначала в первичную анкету (анкету-формы) и только после подтверждения в основные анкеты (анкеты-хранилища).

Данные анкеты-формы и анкет-хранилищ всегда доступны для просмотра и использования в рассылках. В зависимости от того, насколько вам важна верификация заполнения вы можете пользоваться в работе как сразу первичными данными из анкеты-формы, так и только подтверждёнными данными из анкет-хранилищ.

Форма размещается клиентом у себя на сайте в виде статической формы или в виде всплывающей с помощью java-script или можно использовать создаваемую на сервисе входную страницу landing-page.

После заполнения пользователем формы введённые данные сохраняются в указанную начальную анкету-формы, ему высылается fill-letter и он попадает на welcome-page (или welcome-redirect).

После подтверждения заполнения формы (как описано в высланном ранее fill-letter), данные переносятся (если запланировано) из анкеты-формы по описанным в ней правилам в анкеты-хранилища, пользователю высылается welcome-letter и он попадает на fill-page (или fill-reedirect).

Заполнение и подтверждение формы так же порождают триггерные события для расширения логики обработки. При переносе данных в анкеты-хранилища так же порождается событие "Данные пользователя изменились".

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

Через вызов stat.uni можно получить статистику заполнения форм и подробный лог действий с ней.

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

Не обязательно использовать код формы получаемый от нас. Заполнять формы адаптировав их под свои нужны и свой сайт можно получив первоначальный код формы.

Список форм


{

  "action" : `"form.list" 

}

ответ


{

 <общие поля>

,"list" : [

            {

             "id" : уникальный идентификатор

            ,"name" : "название" 

            ,"state : "состояние" 

            ,"origin" : "источник" 

            ,"create.date" : "дата и время создания" -- Ys

            ,"update.date" : "дата и время последнего изменения" -- Ys

            ,'reltype' => ....
            ,'relref'  => ....
            }

            ...

           ]

}

Чтение формы


{

  "action" : "form.get" 

 ,"id" : идентификатор формы

}

ответ


{

  <общие поля>

  "obj" : {

      "id" : идентификатор формы

     ,"name" : "название" 

     ,"create.date" : "дата и время создания" -- Ys

     ,"update.date" : "дата и время последнего изменения" -- Ys

     ,"state" : 0|1 -- состояние: 0 - форма отключена, 1 - активна

     ,"preview" : 0|1 -- при возврате подписчика к форме из-за ошибки заполнения заполнять её ранее введёнными данными

     ,"only_once" : 0|1 -- форма заполняется подписчиком только один раз

     ,"anketa" : анкета-форма в которую собираются данные

     ,"group" : группа-список в которую попадают заполнившие форму. не обязательно

     ,"origin" : "источник" -- источник, к которому относится форма. не обязательно.

     ,"landing" : {

                   "webpage" : id веб-страницы служащей шаблоном для отрисовки landing-page. не обязательно

                  }

     ,"fill" : {
                "draft" :id черновика высылаемого как fill-letter. не обязательно

                -- или одно из или вообще ничего:

               ,"webpage" : id веб-страницы служащей шаблоном для отрисовки fill-page

               ,"link" : id ссылки для fill-redirect

               }

     ,"welcome" : {
                "draft" :id черновика высылаемого как welcome-letter. не обязательно

                -- или одно из или вообще ничего:

                "webpage" : id веб-страницы служащая шаблоном для отрисовки welcome-page

               ,"link" : id ссылки для welcome-redirect

               }

          }

    ,"notify" : {
                  "email" : [ список адресов ]

                 ,"draft" : id черновика
                 }

     ,"reltype" : ...

     ,"relref" : ...
    }
}

Создание или изменение формы

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

Для удаление не обязательного параметра используйте пустое значение.


{

  "action" : "form.set" 

  ,"obj" : {

     ,"name" : "название" 

     ,"state" : 0|1 -- состояние: 0 - форма отключена, 1 - активна

     ,"anketa" : анкета-форма в которую собираются данные

     ,"preview" : 0|1 -- при возврате подписчика к форме заполнять её ранее введёнными данными
                      -- 0 - нет (по умолчанию)
                      -- 1 - да

     ,"only_once" : 0|1 -- форма заполняется подписчиком только один раз (0 - нет, 1 - да)

     ,"group" : группа-список в которую попадают заполнившие форму

     ,"origin" : "источник" -- источник, к которому относится форма

     ,"landing" : {

                   "webpage" : id веб-страницы служащей шаблоном для отрисовки landing-page

                  }

     ,"fill" : {
                "draft" :id черновика высылаемого как fill-letter. не обязательно

                -- ответ пользователю после заполнения. одно из:

                  -- или отрисовка страницы (параметр link стирается)

               ,"webpage" : id веб-страницы служащей шаблоном для отрисовки fill-page

                  -- или перенаправление (параметр webpage стирается)

               ,"link" : id ссылки для fill-redirect. в ссылке поддерживается такая же персонализация к как и на веб-странице

                  -- или станица с текстом по умолчанию (оба параметра стираются)

               ,"webpage" : "" 

               ,"link" : "" 

               }

     ,"welcome" : {

                "draft" :id черновика высылаемого как welcome-letter. не обязательно

                -- ответ пользователю после подтверждения заполнения формы. одно из:

                  -- или отрисовка страницы (параметр link стирается)

               ,"webpage" : id веб-страницы служащей шаблоном для отрисовки fill-page

                  -- или перенаправление (параметр webpage стирается)

               ,"link" : id ссылки для fill-redirect. в ссылке поддерживается такая же персонализация к как и на веб-странице

                  -- или станица с текстом по умолчанию (оба параметра стираются)

               ,"webpage" : "" 

               ,"link" : "" 

               }

           }

     -- извещать о заполнении/подтверждении формы указанные адреса высылкой письма
     -- или полностью отсутствует или указан как минимум один адрес и номер черновика
     -- для удаления ранее настроенного извещения укажите "notify" : {}

     ,"notify" : {
                  "email" : [ список адресов ]

                 ,"draft" : id черновика
                 }

     ,"reltype" : ...

     ,"relref" : ...
    }

 -- необязательные

  ,"id" : идентификатор формы -- если не указан, создается новая

  ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ


{

 <общие поля>

 ,obj  { ... } -- объект в формате form.get если "return_fresh_obj" : 1

}

Удаление формы

Вместе с формой будет удалена привязанна к ней анкета (не собранные данные, а именно сама анкета) если код анкеты начинается на form_ и protected у анкеты не установлен.


{

  "action" : "form.delete" 

  ,"id" : идентификатор формы

  ,"keep_anketa" : 0 | 1 -- оставть анкету (1), по умолчанию 0 - удалить

}

ответ


{

 <общие поля>

}

Получение кода формы для установки на сайте


{

  "action" : "form.source" 

  ,"id" : идентификатор формы

  ,"js" : 0|1 -- генерировать код с учётом использования java-script
              -- не всегда возможно разместить код с java-script на не своём сайте
              -- в большестве случаев java-script не будет работать в письмах
              --
              -- при использовании java-script код будет пытаться максимально оставлять
              -- пользователя на странице где он заполняет форму выводя все сообщения
              -- по возможности во всплывающих окнах

 ,"channel" : "site|email|amp" -- где будет размещена форма, сайт или письмо. по умолчанию - site
}

ответ


{

 "html" : "код формы" 

,"url" : "полный адрес входной страницы формы" -- #6025 [BASE]/
,"url.fill" : "полный адрес страницы, отображаемой при окончании заполнения" 
,"url.welcome" : "полный адрес страницы, отображаемой при подтверждении заполнения" 

-- для amp

,"amp" : "код формы" - разместить в тексте AMP-письма
,"amp.head" : "код скриптов" - разместить в письме между @<head></head>@ для подключения необходимых скриптов.

}

Перенос значений из анкеты-формы в анкеты-хранилища

Этот вызов переносит ответ пользователя из анкеты-формы в анкеты-хранилища не дожидаясь, пока он сам подтвердит заполнение,

Этот вызов делает ровно то что написано, он не подтвердит регистрацию нового пользователя за него и не породит событие "Форма заполнена" или "форма подтверждена".


{

  "action" : "form.transfer" 

  ,"id" : идентификатор формы

  ,"email" : "адрес, данные которого перенести" 

}

ответ


{

 <общие поля>

}

вверх

Хранилище файлов

Хранилище имеющееся в вашем распоряжении позволяет:
- хранить в нём изображения и файлы на которые вы ссылаетесь из писем или прикрепляете к ним (хранилище картинок image)
- получать от системы отчёты которые вы заказывали с параметром "сохранить на сервере" (хранилище отчётов report)
- загружать файлы данных для импорта и Экспресс-Выпуска в недоступное публично место (хранилище загрузок upload)
- получать бухгалтерские документы по аккаунту (хранилище документов pase)

Доступ к Хранилищу осуществляется по данному api, по обычному ftp, по ftps, по sftp, по ссылкам со схемой rfs://

Для доступа по api используйте описанные ниже методы.

Доступ по ftps/sftp осуществляется через ftps.sendsay.ru. Данные для доступа необходимо получить в Службе поддержки.

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

К хранилищу отчётов, документов и хранилищу загрузок публичного доступа по http/ftp/ftps/sftp нет.

К хранилищу загрузок есть дополнительно доступ по rfs://upload/путь-до-файла, что бы на загруженные файлы можно было использовать при импорте списков и рассылке Экспресс-выпуска

Параметр domain указывает на используемую область - хранилище картинок (image), хранилище отчётов (report), хранилище загрузок (upload), хранилище бухгалтерских документов (pase)

Параметр path используемый в вызовах это абсолютный путь по хранилищу начинающийся со слэша.

Доступ к каждой области ограничен правами которые изначально есть все у основного логина аккаунта. А дополнительным логинам вы их можете назначить сами в зависимости от их назначения. Это удобнее делать через интерфейс, но возможно и через api.

Название права строится по схеме "api.rfs/<domain>". Например "api.rfs/report". Право доступа регулирует все действия. При его наличии возможно совершать любые действия в соответствующей области. При отсутствии - никаких.

Права доступа не учитываются при работе через доступ ftps/sftp получаемый в Службе Поддержки.

Cписок файлов

Если path указывает на файл, то возвращается информация только о нём - можно использовать для проверки существования этого файла.

Если path указывает на каталог, то возвращается список его файлов и подкаталогов.


{

 "action" : "rfs.list" 

-- или список файлов

 ,"domain" : "image|report|upload|pase" 

 ,"path" : "полный путь - каталог или файл" 

-- или список доступных хранилищ

 ,"domain" : "" 

 ,"path" : "/" 

}

ответ


{

 <общие поля>

 ,"type" : null | "dir" | "file" -- тип того, на что показывал path
                                 -- null   - нет такого каталога или файла
                                 -- "dir"  - это каталог
                                 -- "file" - это файл

 -- для type = null - отсутствует
 -- для type = dir  - cписок файлов в каталоге
 -- для type = file - список из одной записи описывающий запрошенный файл

 ,"file" : [

            {
              "name" : название

             ,"path" : полный путь c названием

             ,"size" : размер

             ,"date" : дата изменения (в формате yyyy-mm-dd hh:mm:ss)

             ,"url" : публичная веб-ссылка для доступа. только если domain=image
            }

            .........

           ]

 -- только для type = dir -- список подкаталогов

 ,"dir" : [

           {
             "name" : название

            ,"path" : полный путь с названием

            ,"date" : дата изменения (в формате yyyy-mm-dd hh:mm:ss)

            ,"url" : публичная веб-ссылка для доступа. только если domain=image
           }

           .........

          ]

}

вверх

Переименовать файл или каталог

Вызов позволяет переименовать файл или каталог.

Путь path.to должен отсутствовать.

{

 "action" : "rfs.rename" 

 ,"domain" : "image|upload" 

-- единичное переименование

 ,"path" : "полный путь который переименовывается" 

 ,"path.to" : "полный путь в который переименовывается" 

-- несколько переименований за один вызов. действия независимые.

 ,"list" : [ -- до 100 элементов
            { "path" : "..." ,"path.to" : "..." }
           ,{ "path" : "..." ,"path.to" : "..." }
           ,{ "path" : "..." ,"path.to" : "..." }
           ...
           ]

}

ответ
{

-- при единичном переименовании

 <общие поля>

 ,"name" : новое название файла

 ,"path" : новый полный путь до файла

 ,"url" : новая публичная веб-ссылка для доступа. только если domain=image

-- при использовании list

 <общие поля>

 ,"list" : [
            { ответ для первого элемента списка }
           ,{ ответ для второго элемента списка }
           ,{ ответ для третьего элемента списка }
           ]

}

Получить файл


{

 "action" : "rfs.file.get" 

 ,"domain" : "image|report|upload|pase" 

 ,"path" : "полный путь с названием файла" 

 ,"encoding" : "желаемая кодировка данных в ответе" -- пусто или не указана - обычная Unicode/UTF-8
                                                    -- base64 - содержимое будет возвращено в base64
}

ответ


{

 <общие поля>

 ,"data" : "содержимое файла" -- предполагается что файл двоичный и :
                              -- без заказа кодировки значение октетов 0x00 - 0xFF отображено как U+0000-U+00FF
                              -- при заказе base64 данные будет возвращены в base64

 ,"name" : название файла

 ,"path" : полный путь до файла

 ,"url" : публичная веб-ссылка для доступа. только если domain=image

 ,"size" : размер

 ,"date" : дата изменения (yyyy-mm-dd hh:mm:ss)
}

вверх

Записать файл

Только для domain = image и upload


{

 "action" : "rfs.file.put" 

 ,"domain" : "image|upload" 

 ,"path" : "полный путь с названием файла (отсутствующие попутные подкаталоги НЕ создаются автоматически)" 

 ,"data" : "содержимое файла" -- предполагается что файл двоичный и:
                              -- без указания кодировки значение октетов 0x00 - 0xFF заданы как U+0000-U+00FF
                              -- при указании base64 данные считаются закодированы в base64

 ,"encoding" : "кодировка данных" -- пусто или не указана - обычная Unicode/UTF-8
                                  -- base64 - содержимое data закодировано в base64

,"append" : 0|1 -- дописать данные в файл а не заменить его содержимое. только для upload

,"error_if_exists" : 0|1 -- не перезаписывать/не дописывать если файл существует
}

ответ


{

 <общие поля>

 ,"url" : публичная веб-ссылка для доступа. только если domain=image

 ,"name" : название файла

 ,"path" : полный путь до файла

 ,"size" : размер

 ,"date" : дата изменения (yyyy-mm-dd hh:mm:ss)
}

вверх

Удалить файл


{

 "action" : "rfs.file.delete" 

 ,"domain" : "image|report|upload" 

-- единичное удаление

 ,"path" : "полный путь с названием файла" 

-- несколько удалений за один вызов. действия независимые.

 ,"list" : [ -- до 100 элементов
            "path" 
           ,"path" 
           ,"path" 
           ...
           ]
}

ответ


{

-- при единичном удалении

 <общие поля>

-- при использовании list

 <общие поля>

 ,"list" : [
            { ответ для первого элемента списка }
           ,{ ответ для второго элемента списка }
           ,{ ответ для третьго элемента списка }
           ]

}

вверх

Cоздать каталог

Только для domain = image и upload


{

 "action" : "rfs.dir.make" 

 ,"domain" : "image|upload" 

 ,"name" : название каталога

 ,"path" : полный путь с названием каталога (отсутствующие попутные подкаталоги создаются автоматически)" 

 ,"url" : публичная веб-ссылка для доступа. только если domain=image
}

ответ


{

 <общие поля>

 ,"url" : публичная веб-ссылка для доступа. только если domain=image

}

вверх

Удалить каталог

Только пустой каталог.


{

 "action" : "rfs.dir.delete" 

 ,"domain" : "image|report|upload" 

-- единичное удаление

 ,"path" : "полный путь с названием каталога" 

-- несколько удалений за один вызов. действия независимые.

 ,"list" : [ -- до 100 элементов
            "path" 
           ,"path" 
           ,"path" 
           ...
           ]

}

ответ


{

-- при единичном удалении

 <общие поля>

-- при использовании list

 <общие поля>

 ,"list" : [
            { ответ для первого элемента списка }
           ,{ ответ для второго элемента списка }
           ,{ ответ для третьего элемента списка }
           ]

}

вверх

Кампании

Кампании позволяют объединять разные объекты для удобства и для упрощения получения сводной статистики.

Cписок кампаний

{

 "action" : "campaign.list" 

}

ответ

{

  <общие поля>

 ,"list" : [

             {

                "id"   : идентификатор кампании

               ,"name" : "название кампании" 

             }

             ...

           ]

}

Чтение кампании

{

 "action" : "campaign.get" 

 ,"id" : идентификатор кампании

}

ответ

{

  <общие поля>

 ,"obj" : {
           "id"   : идентификатор кампании

           ,"name" : "название кампании" 

           ,"descr" : "описание кампании" 

           }
}

Создание/Изменение кампании

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

{

 "action" : "campaign.set" 

 ,"id"   : идентификатор кампании -- при отсутствии создаётся новая кампания

 ,"obj" : {

           "name" : "название кампании" -- при создании обязательно

          ,"descr" : "описание кампании" 

          }

  -- не обязательно

 ,"return_fresh_obj" : 0|1
}

ответ

{

  <общие поля>

 ,"id"   : идентификатор кампании -- при cоздании

}

Удаление кампании

{

 "action" : "campaign.delete" 

 ,"id" : идентификатор кампании
}

ответ

{

  <общие поля>

}

Cписок объектов кампании

{

 "action" : "campaign.member.list" 

,"id" : идентификатор кампании

}

ответ

{

  <общие поля>

,"list" : [

           {

            "type" : "тип объекта" 

           ,"id"   : "идентификатор объекта" 

           }

          ..............
          ]
}

Добавить объект в кампанию

Объект может входить в состав нескольких кампаний.

{

 "action" : "campaign.member.add" 

,"id" : идентификатор кампании

,"obj" : {
          "type" : "issue|cron|split|form|sequence|linkgroup|datarow" -- тип объекта

         ,"id"   : "идентификатор объекта" 
         }
}

ответ

{

  <общие поля>

}

Удалить объект из кампании

{

 "action" : "campaign.member.delete" 

,"id" : идентификатор кампании

,"obj" : {
          "type" : "тип объекта" 

         ,"id"   : "идентификатор объекта" 
         }
}

ответ

{

  <общие поля>

}

вверх

Ряды данных

Ряд Данных предназначен для хранения статистики из внешних источников.

Ряд Данных позволяет хранит наборы пар (дата, число)

Дата - от YY до Yh, уникальна в Ряде Данных.

Число - целое

Пополнять Ряд Данных можно задавать непосредственно из данных вызова, из csv- или xlsx-файлов с указание по какой ссылке их забирать, из статистических данных Google Analitics.

Использование Рядов Данных в вызове Универсальной Статистики в сочетании с выборкой данных по выпуску рассылки (клики, чтения, доставки) позволит оценить, например, изменение посещаемости после рассылки.

Список рядов данных

{

 "action" : "datarow.list" 

}

ответ

{

  <общие поля>

 ,"list" : [

             {

                "id"   : идентификатор рядя данных

               ,"name" : "название ряда данных" 

               ,"prec" : "точность даты ряда данных" 

               ,"ga.bind" : "привязка к GA" 

               ,"reltype" : ...

               ,"relref" : ...
             }

             ...

           ]

}

Чтение ряда данных

{

 "action" : "datarow.get" 

 ,"id" : идентификатор ряда данных

}

ответ

ответ

{

  <общие поля>

 ,"obj" : {
           "id" : идентификатор рядя данных

           ,"name" : "название ряда данных" 

           ,"prec" : "точность даты ряда данных" 

           ,"ga.bind" : "привязка к GA" 

           ,"ga.request" :  {
                             параметры запроса к Google Analictics
                            }

            ,"reltype" : ...

            ,"relref" : ...
           }
}

Создание ряда данных

{

 "action" : "datarow.create" 

 ,"name" : "название ряда данных

 ,"prec" : "точность даты ряда данных" -- "YY" - год, "YM" - год-месяц, "YD" - год-день, "Yh" - год-час
                                       -- точность даты указывается при создании и в дальнейшем изменена быть не может

 -- не обязательная привязка к Google Analitics

 ,"ga.bind" : "id авторизации" -- ссылка на объект authext хранящий авторизацию

 ,"ga.request" :  { -- игнорируется при пустом или отсутствующем ga.bind

                    -- параметры запроса к Google Analictics для пополнения ряда данных через Сore Reporting API
                    -- https://developers.google.com/analytics/devguides/reporting/core/v3/reference
                    --
                    -- запрос должен возвращать в первой колонке дату, во второй - значение
                    -- обратите внимание, что GA возвращает дату с точностью YD и, следовательно, может использовать
                    -- только если точность ряда данных YD и меньше
                    --
                    -- автоматические запросы к GA вы можете настроить на удобное вам время с удобной частотой через Действия по расписанию cron.*

                   "ids" : "...." 
                  ,"metrics" : "...." 
                  ,"dimensions" : "...." 
                  ,"segment" : "...." 
                  ,"filters" : "...." 
                  }

 ,"reltype" : ...

 ,"relref" : ...
}

ответ

{

  <общие поля>

 ,"id" : идентификатор созданного ряда данных

}

Изменение ряда данных

Обновляются только указанные в запросе поля

Точность ряда данных изменить нельзя

{

 "action" : "datarow.set" 

 ,"id" : идентификатор ряда данных

 ,"name" : "новое название ряда данных

 ,"ga.bind" : "новая авторизация к GA авторизации" -- укажите пусто что бы удалить привязку к GA

 ,"ga.request" : { новый запрос к GA } -- игнорируется при пустом или отсутствующем ga.bind

 ,"reltype" : ...

 ,"relref" : ...
}

ответ

{

  <общие поля>

}

Удаление ряда данных

{

 "action" : "datarow.delete" 

 ,"id" : идентификатор ряда данных
}

ответ

{

  <общие поля>

}

Внесение записей в ряд данных

Вызов позволяет внести, изменить или удалить записи.

Одна запись состоит из пары "дата" (идентифицирует запись в данном ряду данных) и соответствующего дате "числа".

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

Формат даты - YYYY-MM-DD hh:mm:ss или YYYYMMDDhhmmss

Лишние компоненты даты отбрасываются.

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

Число - положительное или отрицательное число или ноль или пусто. Дробная часть, при наличии, отбрасывается.

Запись для отсутствующей ещё даты создаётся автоматически.

Запись для уже существующей даты изменяет значение на новое, если число не пусто, или удаляется, если задано пустое значение.

При указании файла или ссылки на файл поддерживается содержимое сжатое zip.

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

Иначе файл считается текстовым (csv) с попыткой автоматически определить разделитель из возможных (запятая, точка с запятой, вертикальная черта, табуляция). Одна строка - одна запись. Первая колонка - дата, вторая число.

Если Ряд Данных связан с Google Analitics, то он пополняется автоматически. Но можно пополнить его и по своему желанию использовав данный вызов в варианте с ga.

Положение колонки с датой и числом для любого способа задания данных можно указать явно через dt.index и value.index, если они отличаются от 0 и 1. Нумерация с нуля.

{

 "action" : "datarow.load" 

,"id" : идентификатор ряда данных

,"dt.index" : "индекс колонки с датой" --  по умолчанию 0

,"value.index" : "индекс колонки с числом" -- по умолчанию 1

-- или получение данных из Google Analitics (требуется заранее заполнить ga.bind и ga.request

  "ga" : { -- указанные тут параметры сильнее параметров сохранённых в ga.request
          "start-date" : "YYYY-MM-DD | today | yesterday | NdaysAgo | minimum" 

         ,"end-date" : "YYYY-MM-DD | today | yesterday | NdaysAgo" 

         ,прочие параметры которые может понять Google Analitics
         }

-- или получения данных из внешнего файла

  "url" : "ссылка на файл" 

-- или явное задание структуры данных

  "list" : [
            [ "дата-1", "число-1"]
           ,[ "дата-2", "число-2"]
           ,[ "дата-3", "число-3"]
            ......
           ]

-- или задание данных содержимым файла

  "list" : "содержимое файла" 

}

ответ

{

  <общие поля>

,"count" : число добавленных, изменённых или удалённых записей

,"warnings" : [ описание ошибочных строк проигнорированных при обработке - причина и номер строки ]
}

Массовое удаление записей ряда данных

Альтернатива удалению записей по одной через datarow.load.

Удаляются все записи попадающие в указанный диапазон дат и значений.

В целях безопасности пустой фильтр игнорируется (формально он означал бы удаление всего).

Для удаления всех записей ряда данных используйте filter со специальным значением "ALL".

{

 "action" : "datarow.clean" 

,"id" : идентификатор ряда данных

-- одно из

,"filter" : [
             фильтр как для stat.uni по полям datarow.record.dt и datarow.record.value
            ]

-- или

,"filter" : "ALL" 

}

ответ

{

  <общие поля>

,"count" : "количество удалённых записей" 
}

вверх

Ленты Новостей

Список лент

{
 "action" : "lenta.list" 
}

ответ

{
 "list" : [
            {
             "id" : номер ленты

            ,"name" : название ленты

            ,"issued" : "дата-время последнего использования в рассылках" -- Ys

  -- если лента привязана к подписчику

            ,"member" : "получатель" 
            }

           ...........

          ]
}

Прочитать ленту

{
 "action" : "lenta.get" 

."id" : номер ленты
}

ответ

{
 "obj" : {
          "id" : номер ленты

         ,"name" : название ленты

         ,"issued" : "дата-время последнего использования в рассылках" -- Ys

         ,"source" : [ -- информация об RSS/Atom каналах входящих в ленту

                       {
                        "url" : "адрес канала" 
                       ,"dt.fetched" : "YYYY-MM-DD hh:mm:ss" -- дата последнего чтения канала
                       ,"dt.updated" : "YYYY-MM-DD hh:mm:ss" -- дата последнего пополнения новостей из канала
                       ,"error" : {
                                   "count": количество ошибок работы с каналом
                                  ,"dt"   : "YYYY-MM-DD hh:mm:ss" - дата последней ошибки
                                  ,"str"  : описание последней ошибки
                                  }

                       }
                       ...
                     ]

    -- если лента привязана к подписчику

         ,"member" : "адрес получателя" 

         ,"decor"  : "идентификатор" -- идентификатор информационного письма или черновика выпуска который
                                     -- будет использоваться при выпуске накопившихся новостей

         -- расписание выпусков
         ,"schedule" : {
                        -- часы по которым высылать письма
                        "hour" : [ час, час, ... ]

                        -- номера дней недели по которым высылать письма
                        -- от 1 (Пн) до 7 (Вс)
                       ,"weekday" : [ номер, номер, ... ]
                       }
         }
}

Создать/изменить ленту

Создает ленту (при отсутствии id) или изменяет некоторые её параметры.

{
 "action" : "lenta.set" 

 ,"obj" : {

       "name" : "название ленты" 

      ,"member" : "адрес получателя" -- только при создании. изменить нельзя

-- для лент привязанных к получателю

      ,"decor"  : "идентификатор информационного письма или черновика выпуска" 

      -- расписание выпусков
      -- если параметр полностью отсутствует, то с 8 по 18 с понедельника по пятницу
      ,"schedule" : {
                     -- часы по которым высылать письма. если не указано, то с 8 по 18
                     "hour" : [ час, час, ... ]

                     -- номера дней недели по которым высылать письма, если не указано, то с Пн по Пт
                     -- от 1 (Пн) до 7 (Вс)
                    ,"weekday" : [ номер, номер, ... ]
                    }

   }

-- необязательные

 ,"id" : номер ленты

 ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ

{

 <общие поля>

 ,"id" : номер ленты

 ,obj  { ... } -- объект в формате lenta.get если "return_fresh_obj" : 1

}

Удаление ленты

{

  "action" : "lenta.delete" 

  ,"id" : номер ленты

}

ответ

{

 <общие поля>

}

Добавление источника в ленту

{

  "action" : "lenta.source.add" 

  ,"lenta.id" : номер ленты

  ,"url" : "адрес источника добавляемого в ленту" 

}

ответ

{

 <общие поля>

}

Удаление источника из ленты

{

  "action" : "lenta.source.delete" 

  ,"lenta.id" : номер ленты

  ,"url" : "адрес источника удаляемого из ленты" 

}

ответ

{

 <общие поля>

}

Обновление источника

Указаный источник опрашивается вне расписания в поиске свежих новостей.

Если с момента прошлой провеки (плановой и этим вызовом) прошло меньше 15 минут, то опрос произведён не будет и количество свежих новостей будет возвращено как -1.

{

  "action" : "lenta.source.refresh" 

 ,"url" : "адрес обновляемого источника" 

}

ответ

{

 <общие поля>

,"news" : "количество найденех свежих новостей" 

}

Тестовый выпуск ленты

Вызов отправляет тестовое письмо с указанным количеством последних новостей ленты на указанный адрес (а не на адрес для которого создана лента).

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

Если количество последних новостей указано, то отметка их использования в предыдущих выпусках игнорируется.

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

Если в итоге новостей нет, то письмо не отправляется

Для тестирования выпуска рассылки с лентой есть альтернативные вариант - тестовый выпуск через issue.send

{
 "action" : "lenta.send" 

."id" : номер ленты

,"email" : адрес для отсылки тестового письма

,"n" : количество последних новостей для теста
       -- если не указано, то используются все ещё не отосланные новости (их может не быть)

,"decor" : "черновик" -- если лента привязана к подписчику, то можно не указывать - будет взят черновик указанный в ленте
                      -- если лента не привязана к подписчику, то черновик указать необходимо
}

ответ

{
 "news" : "количество новостей попавших в письмо" 

 < параметры ответа issue.send если письмо было выслано >
}

Шаблоны информационных писем

Эти вызовы поменяли формат принимаемых и возвращаемых данных на совместимый с issue.send

Поля старого формата ответов будут ещё некоторое время возвращаться для обратной совместимости.

Старый формат изменения данных будет ещё некоторое время приниматься для обратной совместимости.

Список шаблонов информационных писем


{

  "action" : "infolett.list" 

-- параметры фильтрации, должен быть хотя бы один параметр
--
-- если выбрана последняя порция списка то ответ содержит "last_page" : 1
--
-- доступны поля
--
-- issue_infolett.id             -- "код информационного письма" 
-- issue_infolett.name           -- "название" 
-- issue_infolett.channel        -- канал отправки email|sms|viber|push|vk|tg|vknotify
-- issue_infolett.create.time    -- "дата и время создания" -- Ys, null
-- issue_infolett.update.time    -- "дата и время последнего изменения" -- Ys, null
-- issue_infolett.alias          -- "альтернативный идентификатор" 
-- issue_infolett.reltype        -- число
-- issue_infolett.relref         -- число
-- issue_infolett.public_preview -- "ccылка просмотра черновика без пароля" 
-- issue_infolett.onmoderation : -- 0|1  0 - шаблон одобрен для использования, 1 - шаблон на модерации
--

,"filter" : [ фильтр в синтаксисе stat.uni ]

,"order" : [ сортировка ответа в синтаксисе stat.uni ]

,"skip" : количество пропускаемых записей от начала списка -- по умолчанию 0

,"first" : количество выбираемых записей после skip -- по умолчанию 50, не более 50

}

ответ


{

 <общие поля>

,"list" : [

            {

             "id" : уникальный идентификатор

            ,"format" : "viber|sms|html|text|push|vk|tg|vknotify" -- формат

            ,"name" : "название" 

            ,"onmoderation" : 0|1 -- 0 - шаблон одобрен для использования, 1 - шаблон на модерации

            }

            ...

           ]

}

Чтение шаблона информационного письма


{

  "action" : "infolett.get" 

 ,"id" : идентификатор шаблона

}

ответ


{

  <общие поля>

  "obj" : {

           "id" : идентификатор шаблона

          ,"name" : "название" 

          ,"onmoderation" : 0|1 -- 0 - шаблон одобрен для использования, 1 - шаблон на модерации

          ,"letter" : {
                       параметры содержимого письма как у issue.send

                       -- и дополнительно

                      ,"link.qsid" : "..." 
                      ,"campaign.id" : "код кампании" 
                      }
          }

}

Создание или изменение шаблона информационного письма

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

По результатам модерации вам придёт уведомление.


{

  "action" : "infolett.set" 

  ,"obj" : {

            "name" : "название" 

            ,"letter" : {
                         параметры содержимого письма как у issue.send
                         для текстовых писем обязательны не пустые - адрес отправителя, тема и как минимум один текст
                         для sms сообщения обязательны не пустые - имя отправителя и текст

                         -- дополнительно для email. не обязательно

                        ,"link.qsid" : "..." 
                        }

           }

 -- необязательные

  ,"id" : идентификатор шаблона -- если не указан, создается новый

 ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ


{

 <общие поля>

 ,obj  { ... } -- объект в формате infolett.get если "return_fresh_obj" : 1

}

Удаление шаблона информационного письма


{

  "action" : "infolett.delete" 

-- одного

  ,"id" : код шаблона

--- или нескольких

  ,"id" : [код шаблона1, код шаблона2, .. ]

}

ответ


{

 <общие поля>

}

Предпросмотр информационного письма


{

  "action" : "infolett.preview" 

 ,"email" : "адрес для параметризации подстановок" -- не обязательно

-- одно из

 ,"id" : номер письма

-- или

,"obj" : { -- можно передавать объект из infolett.get - лишние поля будут проигнорированы

           "letter" : {
                        параметры содержимого письма как у issue.send
                      }
        }
}

ответ


{

 <общие поля>,

 "text" : "текст письма" 

}

вверх

Форматы просмотра и шаблоны заполнения

Список форматов/шаблонов


{

  "action" : "format.list" 

}

ответ


{

 <общие поля>

,"list" : [

            {

             "id" : "уникальный идентификатор" 

            ,"name" : "название" 

            ,"type" : "uni|fill|view" -- соответственно тип: универсальный | шаблон заполнения | формат просмотра

            }

            ...

          ]

}

Чтение формата/шаблона


{

  "action" : "format.get" 

  "id" : "уникальный идентификатор" 

}

ответ


{

 <общие поля>

,"obj" : {

             "id" : "уникальный идентификатор" 

            ,"name" : "название" 

            ,"type" : "uni|fill|view" -- соответственно тип: универсальный | шаблон заполнения | формат просмотра

            ,"fields" : [ -- список полей ответов анкет в том порядке, в котором они будут использоваться при отображении

                              {

                                 "aid" : "код анкеты" 

                                ,"qid" : "код ответа" 

                                -- поля для шаблона заполнения данных

                                ,"unused" : "1|0" - использовать это поле или нет при заполнении

                                ,"answer" : "значение ответа используемое при заполнении" 

                              }

                         ...

                        ]

            }

}

Создание или изменение формата/шаблона


{

  "action" : "format.set" 

  ,"obj" : {

            ,"name" : "название" 

            ,"type" : "uni|fill|view" -- соответственно тип: универсальный | шаблон заполнения | формат просмотра

            ,"fields" : [ -- список полей ответов анкет в том порядке, в котором они будут использоваться при отображении
                          -- специальная пара aid=member с qid=head.list позволяют получить в ответе список всех голов как в результате member.head.list

                          {

                            "aid" : "код анкеты" 

                           ,"qid" : "код ответа" 

                            -- поля для шаблона заполнения данных

                            "unused" : "1|0" -- использовать это поле или нет при заполнении

                            "answer" : "значение ответа используемое при заполнении. для вопросов с выбором - это объект !" 

                           }

                         ...

                        ]

            }

  -- необязательные

  "id" : "уникальный идентификатор" -- если не указан, создается новый

 ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ


{

 <общие поля>

 ,obj : { ... } -- объект в формате format.get если "return_fresh_obj" : "1" 

}

Удаление формата/шаблона


{

 "action" : "format.delete" 

,"id" : "уникальный идентификатор" 

}

ответ


{

 <общие поля>

}

вверх

Ссылки

Новые ссылки и группы ссылок появляются с системе автоматически c каждым новым выпуском issue.send в зависимости от настроек relink и relink.param.

Если в выпуске есть ссылки для отслеживания переходов, то, при необходимости, создаются соответствующие им объекты "ссылка".

Поддерживаются пользовательские метки reltype/relref (смотрите описание в Общих Замечаниях)

Список ссылок


{

 ,"action" : "link.list" 

 ,"filter" : { -- не обязательно

              "group"  : "идентификатор группы" -- не обязательно
                                                -- при наличии ограничивает список только ссылками указанной группы

             ,"group.reltype"  : число          -- не обязательно
                                                -- при наличии ограничивает список только ссылками групп с указанным reltype

             ,"group.relref"   : число          -- не обязательно
                                                -- при наличии ограничивает список только ссылками групп с указанным relref

             ,"reltype"  : число  -- не обязательно
                                  -- при наличии ограничивает список только ccылками с указанным reltype

             ,"relref"   : число  -- не обязательно
                                  -- при наличии ограничивает список только ссылками с указанным relref
             }
}

ответ

{

  <общие поля>

 ,"list" : [

             {

                "id"       : идентификатор ссылки

               ,"url"      : "url ссылки" 

               ,"group"    : [

                               {

                                 "id"   : идентификатор группы ссылок

                                ,"name" : "название группы ссылок" 

                               }

                               ...

                             ]

             }

             ...

           ]

  ,"group" : { -- присутствует в ответе, только если в запросе был указан идентификатор группы

               "id"   : "идентификатор группы" 

               ,"name" : "название группы" 

               ,"reltype" : ....

               ,"relref" : ....
             }

}

Прочитать ссылку


{

  ,"action" : "link.get" 

  ,"id"     : идентификатор ссылки
}

ответ

{

   <общие поля>

  "obj" : {

     "id" : идентификатор ссылки

    ,"url"   : "url ссылки" 

    ,"reltype" : ......

    ,"relref" : ......
  }

}

Создание ссылки

Ссылки не уникальны. С одним и тем же url может быть несколько ссылок с разным id.

{
   "action" : "link.create" 

  ,"url"    : "url ссылки" 

  ,"test"   : 0|1  -- проверять запросом доступность ссылки

  ,"reltype" : число -- не обязательно, по умолчанию 0
                     -- отрицательные значения зарезервированы для системы
                     -- в данный момент:
                     --  -1 - ссылка из выпуска рассылки
                     --  -2 - ссылка - целевая страница достигнутая подписчиком

  ,"relref" : число  -- не обязательно, по умолчанию 0
}

ответ

{

   <общие поля>

 "id" : идентификатор ссылки

}

Изменение ссылок

При изменении нескольких ссылок все изменения будут проигнорированы если проверка (GET) хоть одной ссылки для которой указано 1 вернёт HTTP код ответа не 2xxx и не 301, 302, 303, 307, 308, 401

При положительных ответах 3хх дальнейшая проверка по пути редиректа не производится.


{

   "action" : "link.set" 

  -- не указанные поля остаются не изменёнными. test учитывается только при наличии url

или одна ссылка

  ,"id"     : идентификатор ссылки

  ,"url"    : "новое значение" 

  ,"test"   : "1/0 - проверять доступность ссылки" 

  ,"name"   : "новое название группы" 

  ,"reltype" : "новое значение" 

  ,"relref" : "новое значение" 

или несколько ссылок

  ,"list"   : [

               {

                  "id"     : ....

                 ,"url"    : ....

                 ,"test"   : ....

                 ,"reltype"  : ....

                 ,"relref"   : ....

                }

                ..

              ]

}

ответ

{

   <общие поля>

}

Удаление ссылки

{
   "action" : "link.delete" 

  ,"id"     : идентификатор ссылки
}

ответ

{

   <общие поля>

}

Изменить участие ссылки в группах ссылок


{

   "action" : "link.set.group" 

  ,"id"     : идентификатор ссылки

  ,"group"  : { -- участие в не указанных группах не изменяется

                 id1 : 0|1 -- 0 - удалить из группы. 1 -добавить в группу

                ,id2 : 0|1

                ,id3 : 0|1

                ...

               }

}

ответ

{

   <общие поля>

}

Группы ссылок

Группы ссылок позволяют объединять несколько ссылок в одно целое. Например для более простого получения по ним суммарной статистики.

Поддерживаются пользовательские метки reltype/relref (смотрите описание в Общих Замечаниях)

Получить список групп ссылок


{

 "action" : "link.group.list" 

 ,"filter" : { -- не обязательно

              "reltype"  : число  -- не обязательно
                                  -- при наличии ограничивает список только группами с указанным reltype

             ,"relref"   : число  -- не обязательно
                                  -- при наличии ограничивает список только группами с указанным relref
             }
}

ответ

{

  <общие поля>

 ,"list" : [

             {

                "id"   : идентификатор группы ссылок

               ,"name" : "название группы ссылок" 

               ,"reltype" : ....

               ,"relref" : ....

             }

             ...

           ]

}

Прочитать группу ссылок


{

  ,"action" : "link.group.get" 

  ,"id"     : идентификатор группы
}

ответ

{

   <общие поля>

  "obj" : {

     "id" : идентификатор группы

    ,"name"   : "название группы" 

    ,"reltype" : ......

    ,"relref" : ......
  }

}

Создать группу ссылок / Изменить группу ссылок


{

  ,"action" : "link.group.set" 

-- для создания

  ,"name"   : "название группы" -- обязательно

  ,"reltype" : число - не обязательно, по умолчанию 0

  ,"reltype" : число -- не обязательно, по умолчанию 0
                     -- отрицательные значения зарезервированы для системы
                     -- в данный момент:
                     --  -2 - ссылка - целевая страниц

  ,"relref" : число  - не обязательно, по умолчанию 0

-- для изменения

  ,"id"     : идентификатор группы ссылок

  -- не указанные поля остаются не изменёнными

  ,"name"   : "новое название группы" 

  ,"reltype" : "новое значение" 

  ,"relref" : "новое значение" 
}

ответ

{

   <общие поля>

  ,"id" : идентификатор группы

}

Удалить группу ссылок

Удаляется именно группа ссылок как объект обеспечивающий группировку ссылок.

Входящие в неё ссылки не удаляются.


{

  ,"action" : "link.group.delete" 

  ,"id"     : идентификатор группы

}

ответ

{

   <общие поля>

}

Шаблоны веб-страниц

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

Список шаблонов веб-страниц


{

  "action" : "webpage.list" 

}

ответ


{

 <общие поля>

,"list" : [

            {

             "id" : уникальный идентификатор

            ,"name" : "название" 

            ,"create.date" : "дата и время создания" -- Ys, null

            ,"update.date" : "дата и время последнего изменения" -- Ys, null
            }

            ...

           ]

}

Чтение шаблона веб-страницы


{

  "action" : "webpage.get" 

 ,"id" : идентификатор шаблона

}

ответ


{

  <общие поля>

  "obj" : {

      "id" : идентификатор шаблона

     ,"name" : "название" 

     ,"page" : "html текст страницы" 

     ,"create.date" : "дата и время создания" -- Ys, null

     ,"update.date" : "дата и время последнего изменения" -- Ys, null

          }

}

Создание или изменение шаблона веб-страницы


{

  "action" : "webpage.set" 

  ,"obj" : {

      ,"name" : "название" 

      ,"page" : "html текст страницы" 

           }

 -- необязательные

  ,"id" : идентификатор шаблона

  ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ


{

 <общие поля>

 ,"id" : номер шаблона

 ,"obj" :  { ... } -- объект в формате webpage.get если "return_fresh_obj" : 1

}

Удаление шаблона веб-страницы


{

  "action" : "webpage.delete" 

  ,"id" : идентификатор шаблона

--- или несколько

  ,"id" : [ id1, id2, .. ]

}

ответ


{

 <общие поля>

}

вверх

Источники

Список источников

{
  "action" : "origin.list" 

-- параметры фильтрации, все параметры необязательны
--
-- если выбрана последняя порция списка, то ответ содержит "last_page" : 1
--
-- доступны поля
--
-- origin.id            -- "идентификатор источника" 
-- origin.name          -- "название источника" 
-- origin.label         -- "метка источника" 
-- origin.status        -- "активность источника" 
-- origin.type          -- "тип источника": "site" | "internal" | "external" 
-- origin.create.date   -- "дата создания" (Ys)
-- origin.update.date   -- "дата изменения" (Ys)
-- origin.reltype       -- "число, ограничивает список только источниками с указанным reltype" 
-- origin.relref        -- "число, ограничивает список только источниками с указанным relref" 
--

 ,"filter" : [ фильтр в синтаксисе stat.uni ]
 ,"order" : [ сортировка ответа в синтаксисе stat.uni ]
 ,"skip" : количество пропускаемых записей от начала списка -- по умолчанию 0
 ,"first" : количество выбираемых записей после skip -- по умолчанию 50, не более 50

}

ответ

{
  <общие поля>

 ,"list" : [
    {
      "id" : "идентификатор источника" 
     ,"name" : "имя источника" 
     ,"status" : "активность источника" 
     ,"label" : "метка источника" 
     ,"type" : "тип источника" 
     ,"relref" : "пользовательская метка" 
     ,"reltype" : "пользовательская метка" 
     ,"create.date" : "дата создания" (Ys)
     ,"update.date" : "дата изменения" (Ys)
    }

    ...
  ]
}

Чтение источника

{
  "action" : "origin.get" 

 ,"id" : "идентификатор источника" 
}

ответ

{
  <общие поля>

  "obj" : {
    "id" : "идентификатор источника" 
   ,"name" : "имя источника" 
   ,"label" : "метка источника" 
   ,"type" : "тип источника" 
   ,"status" : "активность источника" 
   ,"create.date" : "дата создания" (Ys)
   ,"update.date" : "дата изменения" (Ys)
   ,"reltype" : "пользовательская метка" 
   ,"relref" : "пользовательская метка" 
   }
}

Создание источника

label должно быть уникально для всех источников.

Для создания доступен только тип site.

{
  "action" : "origin.create" 

 ,"name" : "название источника"   -- обязательно (string)

 ,"label" : "метка"               -- обязательно (string)
                                  --
                                  -- если type == site, то или просто имя сайта или урл из которого
                                  -- будет взят и нормализовано имя хоста
                                  --
                                  -- либо label не пусто, если type == internal || type == external

 ,"type" : "тип источника"  -- site - сайт
                            -- internal - внутренний
                            -- external - внешний
                            -- не изменяем после создания

 ,"status" : "статус источника" -- не обязательно
                                -- 1 - активирован (по умолчанию)
                                -- 0 - заблокирован

 ,"reltype" : параметр классификации -- не обязательно (int)
                                     -- пользовательская метка

 ,"relref" : параметр классификации -- не обязательно (int)
                                    -- пользовательская метка
}

ответ

{
  <общие поля>

  ,"id" : "идентификатор источника" 
}

Изменение источника

Обновляются только указанные в запросе поля.

Обновить можно только источник с типом site.

Параметр type не изменяем.

{
  "action" : "origin.set" 

 ,"id" : "идентификатор источника" 
 ,"obj" : {
   ,"name" : "имя источника" 
   ,"label" : "метка источника" 
   ,"type" : "тип источника" 
   ,"status" : "активность источника" 
   ,"reltype" : "общая группа источников" 
   ,"relref" : "пользовательская метка" 
  }

  -- не обязательно
 ,"return_fresh_obj" : 0|1
}

ответ

{
  <общие поля>
}

Удаление источника

{
  "action" : "origin.delete" 

 ,"id" : "идентификатор источника" 
}

ответ

{
  <общие поля>
}

вверх

Внешние авторизации

Хранение параметров внешних авторизаций в данный момент поддерживает

Список внешних авторизаций

{

 "action" : "authext.list" 

-- параметры фильтрации, должен быть хотя бы один параметр
--
-- если выбрана последняя порция списка то ответ содержит "last_page" : 1
--
-- доступны поля
--
-- authext.id           -- "идентификатор внешней авторизации" 
-- authext.type         -- "тип внешней авторизации, число" 
-- authext.login        -- "логин внешней авторизации" 
-- authext.status       -- "статус внешней авторизации" 0 - ошибочная, 1 - активная
-- authext.reltype      -- число
-- authext.relref       -- число
--

,"filter" : [ фильтр в синтаксисе stat.uni ]

,"order" : [ сортировка ответа в синтаксисе stat.uni ]

,"skip" : количество пропускаемых записей от начала списка -- по умолчанию 0

,"first" : количество выбираемых записей после skip -- по умолчанию 50, не более 50

}

ответ

{

  <общие поля>

 ,"list" : [

             {

                "id"   : идентификатор внешней авторизации

               ,"type" : "тип внешней авторизации" 

               ,"login" : "логин внешней авторизации" 

               ,"reltype" : ...

               ,"relref" : ...
             }

             ...

           ]

}

Чтение внешней авторизации

Поля хранящие собственно данные для авторизации прочесть нельзя.

{

 "action" : "authext.get" 

 ,"id" : идентификатор внешней авторизации
}

ответ

{

  <общие поля>

 ,"obj" : {
           "id"   : идентификатор внешней авторизации

           ,"type" : "тип внешней авторизации" 

           ,"login" : "логин внешней авторизации" 

           ,"reltype" : ...

           ,"relref" : ...

-- для telegram

           ,"status" : "1",

           ,"param" : {
                     "id" : номер бота в telegram
                    ,"username" : "username бота" -- системное имя бота
                    ,"first_name" : "название бота" -- Отображаемое название бота

                    ,"supports_inline_queries" : 0|1 -- бот поддерживает встроенные запросы
                    ,"can_read_all_group_messages" : 0|1 -- для бота отключен приватный режим
                    ,"can_join_groups" : 0|1 -- можно ли бота приглашать в группы

                    ,"proxy.url" : "url для просирования запросов от Telegram" 

                    ,"reply_disabled" :  0|1 -- включен ли запрет ответов нашего бота

                    ,"telegram.webhook" : 0|1 -- включен ли приём колбэков от телеграма.

                    ,"webhook.url" : "url приёма колбэк данных сервером sendsay" -- генерируется автоматически

                    ,"webhook.error" : "сообщение об ошибке" -- ошибка АПИ телеграма при запросах проверки, установки или удаления webhook. 
                                                             -- webhook.error удаляется при последующих успешных ответах. 

                    ,"webhook.url.previous" : "прежний url" -- если на момент установки webhook от sendsay
                                                            -- в настройках телеграма уже стоял другой webhook, то его url сохраняется 

                   }
           }

           }

-- для TDE

  "api": { "user" :"логин" , "password" :"пароль" }

}

Создание внешней авторизации

{

 "action" : "authext.create" 

 ,"type" : "тип внешней авторизации" 
            -- в данный момент поддерживается:
            --  0 - пара логин-пароль
            --  8 - Google Analitics
            --  9 - Bitrix24
            -- 10 - Google Big Data / Google Big Query
            -- 11 - amoCRM
            -- 14 - webpush
            -- 15 - emailonacid
            -- 17 - telegram
            -- 21 - TDE

 ,"login" : "название внешней авторизации" -- просто некая уникальная метка (должна быть пустой или отсутствовать для telegram)

 ,"reltype" : ...
 ,"relref" : ...

-- для пары логин-пароль

 ,"token" : "логин:пароль" 

-- для Google Big Data / Google Big Query

  -- Зарегистрируйтесь в Google, создайте проект в Google Cloud Platform и создайте сервисный аккаунт со ограниченными правами специально для sendsay
  -- Сервисный аккауyт - специальный гугловый аккаунт для работы с конкретным проектом в рамках заданных прав. Подробнее о нём читайте в https://cloud.google.com/iam/docs/service-accounts?hl=en_US

  -- После этого нужно выгрузить приватный ключ сервисного аккаунта в формате json. Все данные будут в одноименных полях - client_email, private_key, project_id - для использования в данном вызове.

 ,"client_email": "XXXXXXXXX@developer.gserviceaccount.com" -- почтовый адрес сервисного аккаунта

 ,"project_id": "XXXX" -- код проекта

 ,"private_key": "-----BEGIN PRIVATE KEY-----XXXXXXX-----END PRIVATE KEY-----\n" -- приватный ключ сервисного аккаунта

 ,"token": "..." - что угодно, скажем, ещё раз адрес сервисного аккаунта

-- для Google Analitics

 ,"token" : "refresh token" 

-- для amoCRM

 ,"user_login": ".....", -- логин (email) пользователя amoCRM

 ,"api_key": "......", -- несменяемый api-ключ пользователя (из профиля amoCRM)

 ,"subdomain": "new5991bf813bceb" -- субдомен действующей amoCRM

 ,"token": "....", - что угодно, скажем, ещё раз subdomain

-- для Bitrix24

 -- получение необходимых данных требует веб-диалога с пользователем Bitrix24 и поэтому не может быть тут описано
 -- воспользуйтесь веб-интерфейсом Sendsay для создания данной внешней авторизации

-- для EmailOnAcid

 ."login":"eoa" 

 ,"token":"Basic значение-авторизации" -- значение авторизации это закодированная в base64 строка "<api_key_emailonacid>:<account_password_for_emailonacid>" 

-- для Telegram

 ,"token":"....." --  строка, полученный клиентом токен при регистрации бота

 ,"telegram.webhook": 0|1 -- необходимость регистрации веб-хука системы в Telegram
                          -- при указании в authext.set регистрирует или убирает регистрация веб-хука системы в Telegram

 ,"reply_disabled" : 0|1 -- включен ли запрет ответов нашего бота. не обязательно

 ,"proxy.url" : "url для просирования запросов от Telegram" -- не обязательно
                                                            -- для удаления укажите пустое значение в authext.set
                                                            -- проверяется доступность ссылки методом POST
                                                            -- наличие ошибки - прерывается создание внешней авторизации
-- для TDE

  "api": { "user" :"логин" , "password" :"пароль" }

}

ответ

{
  <общие поля>

 ,"id" : идентификатор созданной внешней авторизации

-- для Telegram

 ,"login"    : "username бота",
 ,"bot_name" : "имя бота" 

}

Изменение внешней авторизации

Обновляются только указанные в запросе поля

{
 "action" : "authext.set" 

 ,"id" : идентификатор внешней авторизации

 ,"type" : "тип внешней авторизации" 

 ,"login" : "логин внешней авторизации" 

 ... прочие поля специфические для используемого типа авторизации ...

 ,"reltype" : ...

 ,"relref" : ...
}

ответ

{

  <общие поля>

}

Удаление внешней авторизации

{
 "action" : "authext.delete" 

 ,"id" : идентификатор внешней авторизации
}

ответ

{

  <общие поля>

}

Информация об авторизации в Google Analitics

{

 "action" : "authext.ga.props" 

,"id" : идентификатор внешней авторизации

}

ответ

{

  <общие поля>

 "list" : {
           "логин-1" : [
                         {
                          "id" : "id сайта" 
                         ,"name" : "название сайта" 
                         }

                        ,....
                       ]

          ,....
          }
}

вверх

Система

Получить настройки


{

 "action" : "sys.settings.get",

 "list" : [ ], -- коды настроек. Если пусто - все настройки кроме отмеченных (НД)

 "params" : { -- не обязательно

             "код настройки" : {
                                "параметр" : "значение",

                                "параметр" : "значение",

                                ....
                               },

             "код настройки" : [
                                 ...
                               ]

            }

}

ответ


{

 <общие поля>,

 "list" : {

           "код настройки" : "значение в формате зависящим от настройки" 

           ..........

          }

}

Поменять настройки


{

 "action" : "sys.settings.set",

 "list" : { -- только те что надо изменить

            "код настройки" : "значение в формате зависящим от настройки" 

            ...........

          }

}

ответ


{

 <общие поля>

}

Список настроек которые можно только прочесть


 "about.id"    : "код клиента" 

,"about.name"  : "Название" 

,"about.tarif" : "код тарифа" 

,"about.user"  : "текущий пользователь (sublogin)" 

,"about.user.email"  : "email текущего пользователя" 

,"about.user.name"  : "имя текущего пользователя" 

,"about.manager.account" : "аккаунт-менеджер клиента" 

,"about.manager.sale"    : "менеджер по продажам клиента" 

,"trial"       : 0|1 - тестовый режим. действуют некоторые ограничения

,"trial.issue.limit" : лимит на тираж сообщений. null - ограничения нет. (НД)

,"trial.issue.rest"  : доступный остаток тиража сообщений. null - ограничения нет. (НД)

,"allow.email" : 0|1 - доступна работа с email

,"allow.sms"   : 0|1 - доступна работа с sms

,"sms.unlimited" : 0|1 - тираж sms не ограничен купленным количеством

,"sms.used" : "использовано единиц тарификации sms начиная с 01-11-2013" - в настоящий момент это копейки. (НД)

,"sms.byed" : "куплено единиц тарификации sms начиная с 01-11-2013" - в настоящий момент это копейки. (НД)

,"allow.viber"   : 0|1 - доступна работа с viber

,"viber.unlimited" : 0|1 - тираж viber не ограничен купленным количеством

,"viber.used" : "использовано единиц тарификации viber" - в настоящий момент это копейки. (НД)

,"viber.byed" : "куплено единиц тарификации viber" - в настоящий момент это копейки. (НД)

,"allow.push"   : 0|1 - доступна работа с push

,"allow.vk"   : 0|1 - доступна работа с vk

,"allow.tg"   : 0|1 - доступна работа с tg

,"allow.vknotify"   : 0|1 - доступна работа с vknotify

,"vknotify.unlimited" : 0|1 - тираж vknotify не ограничен купленным количеством

,"vknotify.used" : "использовано единиц тарификации vknotify" - в настоящий момент это копейки. (НД)

,"vknotify.byed" : "куплено единиц тарификации vknotify" - в настоящий момент это копейки. (НД)

,"member.tarif.limit" : лимит на количество адресов в базе используемый при расчёте оплаты. null - ограничения нет. (НД)

,"member.hard.limit" : лимит на количество адресов в базе который не превысить. null - ограничения нет. (НД)

,"member.hard.rest" : доступный остаток на внесение email-адреcов. null - ограничения нет. (НД)

,"member.noconfirm.limit" : лимит на количество email-адрес которые можно внести без подтверждения. null - ограничения нет. (НД)

,"member.noconfirm.rest" : доступный остаток на внесение без подтверждения. null - ограничения нет. (НД)

,"issue.email.moderation" : включена (1) или нет (0) модерация выпусков email-рассылок (НД)

,"issue.email.sender.moderation" : включено (1) или нет (0) подтверждение адресов отправителей писем

,"issue.sms.moderation" : включена (1) или нет (0) модерация выпусков sms-рассылок (НД)

,"issue.sms.sender.moderation" : включена (1) или нет (0) модерация названий отправителей sms

,"issue.stoplist.bysender" : включена (1) или нет (0) отписка по адресу отправителя

,"issue.month.limit" : лимит месячного тиража сообщений. null - ограничения нет. (НД)

,"issue.month.rest" : доступный остаток месячного тиража сообщений. null - ограничения нет. (НД)

,"issue.month.limit.excess" : превышение лимита месячного тиража сообщений. (НД)
                               -- allowed - разрешено
                               -- partial - разрешено частично - первый превышающий выпуск выйдет в не превышающей части, а дальнейшие - полностью не выйдут
                               -- denied  - запрещено - даже частично превышающий выпуск полностью не выйдет

,"issue.month.personal.limit" : лимит месячного тиража транзакционных сообщений. null - ограничения нет. (НД)

,"issue.month.personal.rest" : доступный остаток месячного тиража транзакционных сообщений. null - ограничения нет. (НД)

,"issue.limit.joint" : совместный учёт тиража. Отдельный лимит транзакционных писем игнорируется. Лимит массовых начинает действовать на всё - и на массовые и на транзакционные.

,"invite.month.limit" : месячный лимит приглашений. null - ограничения нет. (НД)

,"invite.month.rest" : доступный остаток месячного лимита приглашений. null - ограничения нет. (НД)

,"limit.ratio" :  Коэффициенты учёта адресов и писем в лимитах по тиражу и количеству подписчиков (НД)

                  Количество адресов или писем соответствующего типа умножается на указанный коэффициент и делится на 100.
                  Сумма полученных значение по всем типам адресов (см. limit.usage.raw) и есть текущее использование лимита адресов в базе.
                  Сумма полученных значение по всем типам сообщение (см. limit.usage.raw) и есть текущее использование лимита сообщений.

         Формат данных в ответе

         "channel" : {
            "email" : 100,
            "push" : 20,
            "sms" : 0,
            "tg" : 0,
            "viber" : 0,
            "vk" : 0,
            "tg" : 100,
            "vknotify" : 0
         },
         "addr_type" : {
            "email" : 100,
            "push" : 20,
            "msisdn" : 0,
            "tg" : 0,
            "viber" : 0,
            "csid" : 0,
            "vk" : 0
            "tg" : 100,
            "vknotify" : 0
         }

,"limit.usage.raw" : Тиражи сообщений по каналам и количество адресов по типам. (НД)

                     Эти величины совместно с коэффициентами limit.ratio и дают величину использование лимитов
                     Не забываем, что есть раздельный и совместный учёт тиража транзакционных email-сообщений - issue.limit.joint.
                     При совместном учёте тиража в "personal" будет 0 - тираж войдёт в "mass" 
                     У остальных каналов (не еmail) транзакционный тираж не имеет специального ограничения и засчитывается как mass

          Формат данных в ответе

         "channel" : {
            "mass" : {
               "email" : 23,
               "tg" : 12,
               "vk" : 5
            },
            "personal" : {
               "email" : 1
            }
         },
         "addr_type" : {
            "email" : 1213551,
            "push" : 225,
            "msisdn" : 5077,
            "viber" : 2,
            "tg" : 6,
            "csid" : 199,
            "vk" : 167424
         }

,"stoplist.remove.month.limit" : месячный лимит удаления из пользовательского стоп-листа. null - ограничения нет. (НД)

,"stoplist.remove.month.rest" : доступный остаток месячного лимита удаления из пользовательского стоп-листа. null - ограничения нет. (НД)

,"sec.allowip" : [ -- список ip-адресов или сidr-адресов только с которых разрешена работа. доступно только основному логину
                   -- пустой список - нет ограничений
                  "адрес" 
                 ,"адрес" 
                  ...
                 ]

,"sec.login.denysuper" : 0|1 -- вход основным логином запрещён. доступно только основному логину

,"sec.login.lock.error" : лимит числа неудачных попыток входа после которого логин блокируется, доступно только основному логину

,"sec.login.lock.inactive" : количество дней неактивности после которых логин блокируется. доступно только основному логину

,"sec.password.history"  глубина истории паролей каждого логина. нельзя сменить пароль на имеющийся в истории. доступно только основному логину

,"sec.password.check" : 0|1 -- проверять что новый пароль соответствует каждому из требований. доступно только основному логину
                            -- * 8 символов и длиннее
                            -- * содержит буквы
                            -- * содержит цифры

,"sec.password.expire.day" : количество дней после смены пароля после которых заставлять менять пароль опять. доступно только основному логину

,"target.script" :  "код для сервиса "Целевые страницы", предназначенный для размещения на сайте"  (НД)

                    -- поддерживаются дополнительные параметры через params (все не обязательны)

                     "group.id : номер -- автоматически включать посетившего целевую страницу в указанную группу-список

                    ,"link.group.id" : номер -- автоматически включать новые целевые страницы в указанную группу ссылок

                    ,"noqs" : 1 -- удалить из адреса целевой страницы все параметры. сильнее keeputm.
                                -- например, они всегда разные для разных посетителей, а для отслеживания этого не требуется

                    ,"keeputm" : 1 -- оставить в адресе целевой страницы utm-параметры

                    ,"url" : "ccылка" -- вместо адреса целевой страницы использовать указанный

                    ,"noevent" : 1 -- не создавать событие "Цель достигнута" 

                    ,"email" : 1 -- включить в код метку для подстановки email посетителя
                                 -- при выдаче страницы каждый раз заменяйте метку на реальный адрес в кодировке url-encoded

                    ,"js" : 1 -- получить код в формате java-script. это полезно в случае сложных, частично обновляемых по мере действий
                              -- пользователя страниц сайта. например при использовании подхода single-page-application

                    ,"datakey": [
                                 -- правила изменения пользовательских данных
                                 -- формат совпадает с аналогичным параметром вызова member.set

                                 -- данная возможность позволяет создавать сложные сценарии учёта достижения страниц вашего сайта
                                 -- например "Забытая корзина" или "Запоминание просмотренных товаров" 
                                 -- Служба Поддержки может проконсультировать вас подробнее по данному вопросу.
                                ]

,"push.script" :  { -- код для организации подписки на пуши, предназначенный для размещения на сайте  (НД)
                    -- сайт должен быть https !!!
                   "push_script" : "...." - устанавливается на сайте на каждую страницу с которой хочется предлагать подписку на push
                  ,"sw_script"   : "...." - устанавливается на сайте в корень что бы был доступен по url /sendsay_push_sw.js

                  -- компоненты использованные при генерации push_script
                  ,"app_server_key" : "ключ приложения" 
                  ,"backend" : "адрес бэкенда;
                  ,"sw" : "ожидаемый урл sw_script";
                  ,"sw_script_src" : "адрес основного скрипта сервис-воркера" 
                }

                  -- поддерживаются дополнительные параметры через params (все не обязательны)

                     "group.id : номер -- автоматически включать посетившего целевую страницу в указанную группу-список

                     "dk" : {
                             -- дополнительные данные для передачи при подписке если они известны сразу
                             -- или потом их можно добавлять вызовами addData()
                            }

,"spec_attach.pdf" : 0|1 -- доступность для использования в рассылке индивидуальных pdf-файлов
,"spec_attach.xlsx" : 0|1 -- доступность для использования в рассылке индивидуальных excel-файлов
,"spec_attach.url" : 0|1 -- доступность для использования в рассылке персональных прикрепляемых файлов

,"dt.now" : "YYYY-MM-DDThh:mm:ss+HH:MM -- текущие дата, время и временная зона систему. В формате ISO-8601.
                                          например "2014-11-11T15:36:09+03:00" 
                                          именно в этой временной зоне считаются все даты и время используемые в API

,"tz.delta" -- проверка знания системой часового пояса в вашем способе записи. воспользуйтесь перед первыми выпусками рассылок с учётом часовго пояса
            -- Если не распознаётся - обратитесь в Службу Поддержки за расширением нашего списка городов и стран.
            --
            -- список проверяемых строк берётся из одноимённого ключа в params
            --
            -- как результат возвращается объект где ключами являются проверяемые строки, а их значениями - null если строка не распознана,
            -- или её часовой пояс в виде смещения относительно UTC в виде +hhmm или -hhmm

,"integration.flocktory" -- получить ссылку для интеграции с Flocktory
            --
            -- передаваемый от Flocktory адрес будет внесён в базу (если его ещё нет), добавлен в группу-список (если его там нет)
            -- и на "новый" адрес будет выслано указанное письмо
            --
            -- поддерживаются дополнительные параметры через params (все не обязательны)
            --
            -- "mode": "B|G" - режим внесения. B - по умолчанию - новым считается адрес которого нет в базе
            --                                 G - новым считается адрес которого нет в указанной группе-списке
            --
            -- "group.id": "код группы-списка" - группа-список, что бы понимать что даёт flocktory.com
            --
            -- "draft.id": "код черновика" - письмо высылаемое "новым" адресам. (кто "новый" зависит от параметра mode)

,"integration.tilda" -- получить ссылку для интеграции с Tilda
            -- аналогично Flocktory

,"integration.amocrm" -- получить ссылку для интеграции с amoCRM
            -- аналогично Flocktory

,"integration.bitrix24" -- получить ссылку для интеграции с Bitrix24
            -- аналогично Flocktory

--

 "tde.topic.*"                 : { -- настройки всех тематик Обогащения Данных разом
                                  "код-тематики" : [ "код-группы", "код-группы", .. ]
                                 ,"код-тематики" : [ "код-группы", "код-группы", .. ]
                                 ...
                                 }

Список настроек которые можно и прочесть и поменять


 "about.owner.email" : [ "email", "email", ... ],  -- email-адрес/адреса ответственного лица.
                             -- Используется как получатель для всех писем системы когда не указан другой адрес

 "support.access" : 0|1, -- Службе поддержки разрешён вход для помощи

 "email.utf8" : 0|1, -- поддержка национальных почтовых адресов
                     -- выключена для старых аккаунтов для сохранения совместимости
                     -- включена для новых аккаунтов
                     --
                     -- изменить настройку можно только в одну сторону - с выключено (0) на включено (1)
                     -- для старых аккаунтов есть необходимость начать использовать национальные адреса
                     --
                     -- для новых аккаунтов всегда включено и не выключается

 -- Использование шаблонов информационных писем

 "infolett.confirm.need" : "код шаблона", -- Высылается когда адрес регистрируется или при использовании вызова
                                          -- "member.sendconfirm" без указания желаемого черновика.
                                          -- Пусто - стандартный системный текст

 "infolett.confirm.done" : "код шаблона", -- Высылается сразу после подтверждения регистрации.
                                          -- Пусто - не высылается ничего

 "infolett.info"         : "код шаблона", -- Высылается когда требуется сообщить подписчику его регистрационные
                                          -- данные и он уже подтвердил регистрации.
                                          -- Пусто - стандартный сиситемный тест
 --

 "msisdn.country" : "число", -- код страны (без +) для нормализации регистрируемых не полных номеров телефонов и viber

 "msisdn.zone"    : "число", -- код зоны для нормализации регистрируемых не полных номеров телефонов и viber

 --

 "issue.paused" : 0||1 -- глобально остановить-возобновить запуск новых рассылок

                       -- останавливает (1) запуск новых заданий на рассылку - они сразу переходят в состояние "Приостановлено" 

                       -- возобновляете (0) запуск новых заданий на рассылку - обычный режим работы

                       -- глобальная остановка НЕ останавливает уже формирующиеся выпуски - для этого есть issue.running.pause

                        -- глобальное возобновление НЕ запускает приостановленные выпуски - для этого есть issue.running.resume

 "issue.dkim.id" : "идентификатор dkim-ключа или пусто" -- будет использоваться если у выпуска на задан свой
                                                        -- пусто - будет использоваться системный

 "issue.link.qid" : "строка", -- добавка к ссылкам в письмах

                              -- переходы из писем рассылок плохо видны в статистике именно как переходы из рассылки
                              -- и часто попадают в общую кучу "прямые переходы" (typed in).

                              -- Вы можете указать параметр (например source=pro) который будет добавляться ко всем
                              -- ссылкам в вашем выпуске (например, было http://site.tld/,а станет http://site.tld/?source=pro)
                              -- и, соответственно, позволит однозначно определить при анализе посещаемости сайта
                              -- что посетитель пришёл именно из выпуска рассылки.

 "issue.link.notest" :  0|1 -- 0 - проверка ссылок как указано в настройках выпуска (по умолчанию)
                            -- 1 - проверка ссылок производится только если это явно указано в теге A в параметре x-do-link-relink,
                                   иначе не производится не смотря ни на какие настройки выпуска

 "issue.dontsend.550" : 0|1, -- автоматически исключать из выпусков рассылок адреса у которых последняя ошибка smtp = 550 - неизвестный получатель

 "issue.tz.observance" : {
                          ... структура как при issue.send или null
                         }
--

 "issue.push.welcome" : "номер черновика" -- номер черновика содержащего приветственный push

 "issue.push.require_interaction" : 0|1 -- настройка параметра для всех выпусков у которых он не указан явно
                                        -- по умолчанию 1 - требуется явное закрытие

--

 "issue.weak_draft" : 0|1 -- параметры содержимого выпуска указанные одновременно с черновиком сильнее таких же параметров черновика (1)
                          -- черновик сильнее - 0 - по умолчанию

--

 "issue.accumulate_by" : "month|week|day" -- интервал по умолчанию для issue.send параметра accumulate_by. По умолчанию - month

--

 "issue.personal.groupby" : пусто или "label01" -- группировка personal. пусто - как обычно. label01 - по первым трём меткам выпусков.

--

 "target.сookie.ttl", : положительное-число -- "Время отслеживания после перехода" для сервиса "Целевые страницы". Задаётся в минутах. По умолчанию 360 - 6 часов

 -- Перенаправления пользователя после совершения действий

 "redirect.member.join"        : "url", -- После регистрации через внешнюю форму
                                        -- когда пользователь зарегистрирован как новый
                                        -- Может быть переопределено или отменено в конкретной
                                        -- форме на сайте параметром redirect_to.

 "redirect.member.join.exists" : "url", -- После регистрации через внешнюю форму
                                        -- когда пользователь уже существует
                                        -- Может быть переопределено или отменено в конкретной
                                        -- форме на сайте параметром redirect_exists_to

 "redirect.member.confirm"     : "url", -- После подтверждения регистрации

 "redirect.member.remove"      : "url", -- После удаления регистрации

--

 "callback.format"             : "old|json|json-stream", -- формат передачи событий Callback/Webhook

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

 ,"callback.url.read" : "url"        -- событие чтения
 ,"callback.url.click" : "url"       -- событие перехода
 ,"callback.url.target" : "url"      -- событие достижения целевой страницы
 ,"callback.url.unsub" : "url"       -- событие отписки
 ,"callback.url.deliv" : "url"       -- событие доставки
 ,"callback.url.form" : "url"        -- событие заполнения формы
 ,"callback.url.tracker" : "url"     -- событие изменения статуса трекера
 ,"callback.url.confirm" : "url"     -- событие подтверждения регистрации
 ,"callback.url.draft" : "url"       -- событие изменения черновика
 ,"callback.url.emailreply" : "url"  -- событие получения ответа на письмо рассылки

--

 "tde.topic.код-тематики"      : [ "код-группы", "код-группы", .. ] -- настройка одной тематики Обогащения Данных. null для удаления

Обращение в саппорт


{

 "action" : "sys.message" 

 "email" :  "email для связи" 

,"message" : "текст сообщения" 

,"attach" : "base64-кодированне cодержимое прикрепляемого файла" -- не обязательно
}

ответ


{

 <общие поля>

}

вверх

Cписок возможных прав пользователя


{

 "action" : "sys.rights.list" 

}

ответ


{

 <общие поля>

,"list" : [
           .......
          ]

}

вверх

Отправить пожелание Деду Морозу

Использовать только перед новым годом


{

 "action" : "sys.dedmoroz" 

 "email" :  "email для ответа" 

,"message" : "пожелание" 

}

ответ


{

 <общие поля>

}

вверх

Дополнительные пользователи аккаунта

Эти вызовы предназначены для администрирования дополнительных логинов аккаунта (саблогинов). Поэтому права на них не должны быть доступны ни кому кроме ответственных за это.

Учитывайте это раздавая права новым саблогинам.

Обычным саблогинам достаточно права sys.password.set что бы они могли поменять пароль сами себе (если это им вообще нужно).

Cеcсия запоминает not_after и действительна пока не прошёл сроке её существования и пока не настанет not_after - последующие изменения not_after логина влияния на сессиию не оказывают.

Cрок действия jwt-токена превышающий not_after ни чему не помешает, но и преодолеть not_after не поможет.

Список пользователей


{

 "action": "sys.user.list" 

}

ответ


{

 <общие поля>

 ,"list" : [

           {

             "sublogin" : "саблогин пользователя",

             "status" : "-1 - ожидает смены пароля |0 - активен | 1 - заблокирован",

             "email" : "адрес саблогина",

             "name" : "имя саблогина",

             "role.id"   : "идентификатор роли",
             "role.name" : "название роли",

             "nba_valid" : 0|1 - интервал действия not_before/after наступил (1), прошёл/не начался (0)
            },

           ..

         ]

}

вверх

Создание пользователя

Созданный пользователь получает состояние "Требуется сменить пароль"

Созданный пользователь не имеет ни каких прав - выдайте их ему или назначьте роль


{

 "action" : "sys.user.create",

 "id"       : "саблогин",

 "password" : "пароль",

 "email" : "адрес". -- необязательно

 "name" : "имя саблогина", -- не обязательно

 "not_before" : "дата-время ранее которой пользователь не действует, Ys", -- не обязательно, null - нет ограничения

 "not_after"  : "дата-время после которой пользователь не действует, Ys", -- не обязательно, null - нет ограничения

}

ответ


{

  <общие поля>

}

вверх

Чтение данных пользователя


{

 "action" : "sys.user.get",

 "id"     : "логин пользователя", -- не обязательно. по умолчанию - текущий пользователь

}

ответ


{

 <общие поля>

 "id"     : "логин пользователя",

 "status" : "-1 - требуется сменить пароль | 0 - активен | 1 - заблокирован",

 "email" : "адрес",

 "name" : "имя",

 "role.id"   : "идентификатор роли", -- если пользователю назначена роль
 "role.name" : "название роли",

 "not_before" : "дата-время ранее которой пользователь не действует", -- Ys, null - нет ограничения

 "not_after"  : "дата-время после которой пользователь не действует", -- Ys, null - нет ограничения

 "nba_valid" : 0|1 - интервал действия not_before/after наступил (1), прошёл/не начался (0)
}

вверх

Изменение данных пользователя

Не указанные параметры не меняются

Настройки основному пользователю (sublogin пусто при авторизации) может менять только он сам.


{

 "action" : "sys.user.set",

 "id"     : "логин пользователя",

 "status" : "-1 - заставить сменить пароль | 0 - активировать | 1 - заблокировать" -- необязательно
            -- если текущий статус -1 то изменить его возможно только сменой пароля

 "email" : "адрес", -- не обязательное поле

 "name" : "имя", -- не обязательно

 "not_before" : "дата-время ранее которой пользователь не действует, Ys", -- не обязательно, null - нет ограничения

 "not_after"  : "дата-время после которой пользователь не действует, Ys", -- не обязательно, null - нет ограничения

-- одно из

 "password" : "новый пароль",

 "password.old" : "старый пароль", -- обязательно, если указан password

-- или

 "password.gen" : 1, -- сгенерировать новый пароль
}

ответ


{

 <общие поля>

}

вверх

Удаление пользователя

Нельзя удалить пользователя с api-ключём. С начала удалите у него api-ключ.


{

 "action" : "sys.user.delete",

 "id" : "саблогин" 

}

ответ


{

  <общие поля>

}

вверх

Изменение пароля себе

Меняет пароль текущему пользователю


{

 "action" : "sys.password.set",

 "password.new" : "новый пароль",

 "password.old" : "старый пароль",
}

ответ


{

 <общие поля>

}

вверх

Ключи api пользователей

Создание/замена ключа api

Нельзя удалить пользователя с api-ключём. С начала удалите у него api-ключ.

{

 "action" : "sys.user.apikey.create",

 "login" : "логин" -- не обязательно, по умолчанию текущий саблогин

}

ответ


{
  <общие поля>

 ,"apikey" : "ключ api" 
}

Чтение ключа api

{

 "action" : "sys.user.apikey.get",

 "login" : "логин" -- не обязательно, по умолчанию текущий саблогин

}

ответ


{
  <общие поля>

 ,"apikey" : "ключ api" -- null если ключа нет
}

Удаление ключа api

{

 "action" : "sys.user.apikey.delete",

 "login" : "логин" -- не обязательно, по умолчанию текущий саблогин

}

ответ


{
  <общие поля>
}

Права дополнительных пользователи

Чтение прав пользователя


{

"action": "sys.user.rights.get",

"id" : "логин пользователя",

"list" : [  -- список запрашиваемых проверяемых вызовов, если пусто - то все

           "action1",

           "action2",

           ...

         ]

}

ответ


{

 <общие поля>,

 "list" : {  -- список вызовов: 1 - доступно, 0 - недоступно

            "action1" : " 1 | 0 " 

            "action2" : " 1 | 0 ",

            ..

          }

}

Установка прав пользователя


{

 "action": "sys.user.rights.set",

 "id" : "логин пользователя",

 "list" : {  -- список устанавливаемых прав: 1 - доступно, 0 - недоступно.
             -- Изменяются только значения перечисленных в списке прав.

            "action1" : " 1 | 0 ",

            "action2" : " 1 | 0 ",

            ...

          }

}

ответ


{

 <общие поля>

}

вверх

Журнал работы


{

 "action" : "sys.log",

 "from"   : "YYYY-MM-DD hh:mm:ss" -- дата события от, не обязательно

 "upto"   : "YYYY-MM-DD hh:mm:ss" -- дата события по, не обязательно

}

ответ


{

 <общие поля>,

 "list" : [ -- по возрастанию времени

           {

             "dt" : "дата события",

             "ip" : "ip адрес инициатора",

             "login" : "логин инициатора",

             "object" : "объект",

             "action" : "действие",

             "title" : "заголовок",

             "value.old" : "старое значение (если применимо)",

             "value.new" : "новое значение (если применимо)" 

           }

           ..........

          ]

}

вверх

Хранимые данные

Абстрактное хранилище структурированных данных.

Предназначено для хранения относительно небольших данных типа настроек интерфейса, конфигураций интерфейса и подобного, для чего создавать отдельные объекты API не целесообразно из-за того что непосредственно к системе эта информация не относится.

Это экспериментальная возможность. Она может быть изменена или отменена.

Список хранимых данных


{

  "action" : "sys.storage.list" 

}

ответ


{

 <общие поля>

,"list" : [
           "идентификатор хранимых данных-1" 

           "идентификатор хранимых данных-2",

           ...

           ]

}

Чтение хранимых данных

При указании в id шаблона (символ "*" - любое количество символов) будут возвращены данные о записях идентификаторы которых под него подходят.

Например: "key1:*" или "key1:*part2*"


{

  "action" : "sys.storage.get" 

 ,"id" : "идентификатор хранимых данных или шаблон идентификаторов" 

 ,"datakey" : [ "dk1","dk2",....] - вернуть только части объекта указанные ключами данных. не обязательно
}

ответ


{

  <общие поля>

-- ответ при запросе без шаблона

  "obj" : {

            "id" : "идентификатор хранимых данных" 

           ,"data" : .... -- данные

           ,"create.date" : "дата и время создания" -- Ys, null

           ,"update.date" : "дата и время последнего изменения" -- Ys, null
          }

-- ответ при запросе с шаблоном

  "list" : [
            { структура как obj }
           ,{ структура как obj }
           ,{ структура как obj }
           .........
           ]

}

Создание или изменение хранимых данных

{

  "action" : "sys.storage.set" 

  ,"id" : "идентификатор хранимых данных" -- строка до 120 символов
                                          -- при отсутствии данных с таким идентификатором запись создаётся

  ,"obj" : {

-- одно из

            "data" : {
                      -- данные
                     }
-- или

            "data" : [
                      -- данные
                     ]

           }

 -- необязательные

 ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

}

ответ

{

 <общие поля>

 ,"obj" :  { ... } -- данные в формате sys.storage.get если "return_fresh_obj" : 1

}

Удаление хранимых данных


{

  "action" : "sys.storage.delete" 

  ,"id" : "идентификатор хранимых данных" 

--- или несколько

  ,"id" : [ "id1", "id2", .. ]

}

ответ


{

 <общие поля>

}

вверх

Ключи данных

"Ключ Данных" это строка описывающая путь к данным хранимым для данного адреса.

Этот новый формат доступы к данным разработан для замены форматы АВО и поддержки хранения сложных структур данных.

На самом деле вы уже знакомы в ключами данных если:

- впускаете Экспресс-Выпуск с данными в формате JSON-массив

- используете внешние данные для персонализации писем

- даже просто пользуетесь персонализацией выпуска (запись [% anketa.person.name %] это доступ по ключу данных из трёх элементов :)

Синтаксис

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

Путь состоит из отдельных элементов (минимум из одного) разделяемых точками:

Ключ из трёх элементов - "aaa","bbb","ccc"

aaa.bbb.ccc

Набор символов элемента пути технически не ограничен, однако стоит проявить разумную сдержанность:

Ключ с на китайском

關鍵.數據

Точка внутри сегмента экранируется обратным слэшем aaa.b@b\.b.ccc

Ключ по прежнему из трёх элементов - "aaa","b@b.b","ccc"

aaa.b@b\.b.ccc

Для доступа к элементу объекта его ключ в объекте записывается через точку после пути до объекта.

В элементе "aaa" взять "bbb", а от него "ccc"

aaa.bbb.ccc

Для доступа к элементу массива его индекс записывается в квадратных скобках сразу после пути до массива.

aaa.bbb[5].ccc

Есть специальные ключи данных - "*", "$" и "@" - они описаны в вызовах которые их поддерживают.

Преимущества

- Данные формата АВО полностью доступны через КД

- Нет ограничения на набор хранимых данных

- Нет ограничений на сложность иерархии хранения данных и их сочетание (например, "адреса подписчика" могут представлять собой массив из объектов хранящих структуру одного адреса по компонентам (страна, город, улица, дом, квартира, координаты), а компоненты адреса в свою очередь там же быть объектами - "координаты" это "широта" и "долгота").

- Нет ограничений на формат данных

- У разных подписчиков по одному и тому же ключу данных можно хранить разные структуры и типы данных

- Доступ к данным в выпуске рассылки осуществляется как и прежде путём прямой записи [% anketa.ключ.данных ] или через функцию datakey() - [ datakey(anketa,keyvar) %], где keyvar это переменная значением которой является ключ данных (для доступа по динамически формируемым ключам или в случае когда ключ содержит символы не подходящие для прямой записи anketa.ключ.данных)

Совместимость

Все данные старого формата АВО доступны через КД без каких-либо ухищрений.

Код "Анекты" становится первым элементом пути в ключе данных, код вопроса - вторым: "aid.qid"

Большинство вызовов уже поддерживает КД. Не поддерживают КД только вызовы form.*, format.*. Это будет сделано в ближайшее время.

Есть только два момента, которые необходимо учесть:

- значение для того, что в АВО называется "вопрос с выбором" это объект, а не массив и не скалар. Ключами объекта - кода ответов, значения - null.

- внутренний формат даты в модели АВО это "YYYYMMDDhhmmss", а в модели КД это "YYYY-MM-DD hh:mm:ss".

Это учитывается автоматически при использовании вызова member.get, но при персонализации выпуска рассылки эта разница может быть заметна.

При использовании member.set за этим необходимо следить самостоятельно

- при записи вопроса с множественным вызовом необходимо записывать правильный объект с правильным количеством (не болей одного если вопрос с единственным выбором) ключей (ключи должны быть из тех что описаны в анкете)
- при записи даты, если только у вас что-то не зависит сильно от её старого формата, лучше использовать новый формат "YYYY-MM-DD hh:mm:ss"

Если вы проверили формат КД для решения своих задач и убедились что он вам подходит, то мы можем помочь вам с автоматизированным переводом формата хранения даты в новый вид - обратитесь в Службу поддержки. (формально вы и сами можете провести такое преобразование, но мы сделаем это для вас проще и быстрее).

Данные формата КД ограниченно доступны при использовании доступа по модели АВО так как только самые простые варианты структур данных доступны через АВО.

Возможные изменения

Длина одного компонента пути ключа данных и всего ключа данных в данный момент не зафиксирована окончательно что бы изучить потребности клиентов. Скорее всего общая длина будет ограничена 255 символами.

Длина строки данных в данный момент не зафиксирована окончательно что бы изучить потребности клиентов. Скорее всего она длина будет ограничена 255 символами, но с предоставлением альтернативы "больших строк".

Для численных операций, минимальное и максимальное целое число в данный момент не определены, но ориентируйтесь на возможности предоставляемые 64-битными вычислениями.

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

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

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

В данный момент не проводится проверки данных вносимых через вызовы member.set и member.import на соответствие тому или иному типу данных (кроме проверки дат и времени). С введением типизации и словаря данных такие проверки станут как минимум возможны по вашему желанию, а в некоторых случаях проводится автоматически.

Форматы данных

Все данных поддерживают набор символов Unicode.

Для хранения и доступа с использованием Ключей Данных доступны следующие типы величин:

Cкаляр

"Просто значение" представляющее собой число или строку.

1
"бла-бла
"ещё одна строка" 

null

Специальный вариант скаляра обозначающий "неопределённое значение".

Записывается просто как

null

Без кавычек !

Массив (список)

Упорядоченный набор других величин (любых).

Каждая величина имеет свой "индекс" (начиная в 0) в массиве описывающих её позицию.

Например, массив из трёх величин, последняя из которых - другой массив

 [ 1, "хихи", [ 4, 99 ] ]

Объект (словарь)

Набор пар "ключ - значение", где "ключ" - скаляр, а "значение" - любая величина.

Каждый ключ объекта уникален и имеет только одну соответствующую ему величину.

Например

{
 "один" : "111" 

,"зима" : [ декабрь", "январь", "февраль"]

,"килограм" : {
                "грамм" : 1000
               ,"унция" : 36.81
               }

,"關鍵" : { 數據" : "關鍵數據是最好的事情" }
}

Пример структуры данных

Пример структуры данных описывающей несколько адресов у одного из подписчиков:

{
 "member" : { ............}

,"contact" : [

             {
              "title" : "Центральный офис" 

             ,"phone:" : [ "+78216550011" ,"88003336963" ]

             ,"is_office" : "1" 

             ,"address" : {
                           "country" : "Россия" 

                          ,"index" : "197022" 

                          ,"city" : ", Санкт-Петербург" 

                          ,"street" : "ул. Профессора Попова " 

                          ,"building" : "д.23 лит.Д" 

                          ,"apartment" : "пом. 27Н (офис 413)" 

                          ,"alias" : "БЦ «Гайот»" 

                          ,"location" : {
                                         "latitude" : "59.972256" 
                                        ,"longitude" : "30.311468" 
                                        }
                         }
              }

            ,{
              "title" : "Московский офис" 

             ,"phone:" : [ "+74952695015" ,"88003336963" ]

             ,"is_office" : "1" 

             ,"address" : {
                           "country" : "Россия" 

                          ,"index" : "121087" 

                          ,"city" : "Москва" 

                          ,"street" : "Улица Барклая" 

                          ,"building" : "д. 6 стр. 5" 

                          ,"apartment" : "офис № 217" 

                          ,"alias" : "бизнес-центр «Барклай Плаза»" 

                          ,"location" : {
                                         "latitude" : "55.752678" 
                                        ,"longitude" : "37.702110" 
                                        }
                         }
              }

            ]
}

Второй номер телефона основного офиса - "сontact[0].phone[1]"

Город московского офиса - "сontact[1].address.city"

Координаты основного офиса - "contact[0].location"

Широта основного офиса - "contact[0].location.latitude"

Хранение данных о подписчике

Данные об одном подписчике представляются как корневой объект имеющий как минимум ключ "member" по которому во вложенном объекте хранятся системные данных об адресе, а остальные ключи корневого объекта вы используете по своему усмотрению.

{
 "member" : {
             "id" : ....
            ,"email" : ....
            ,"domain" : ....
            ,....
            }

,"ваш ключ-1" : ............

,"ваш ключ-2" : ............

,"ваш ключ-3" : ............

}

Использование в персонализации писем

Доступ к данным подписчика в выпуске рассылки осуществляется как и прежде - через объект anketa:

- путём прямой записи [% anketa.ключ.данных %]
- через функцию datakey().

Функция datakey() полезна когда ключ данных содержит символы, не допустимые напрямую в командах персонализатора (это временно), или когда ключ данных хранится в другой переменной (например в результатах фильтра с has/save).

[% datakey(anketa,keyvar) %], где keyvar это переменная значением которой является ключ данных.

[% datakey(anketa,"ключ.данных") %], где "ключ.данных" это строка, непосредственно задающая ключ данных.

Формально datakey() предназначен для помощи в косвенной адресации и первым параметром функции может быть любая переменная персонализации, не обязательно только anketa.

вверх

Фильтр отбора в группе (ДК)

Этот фильтр работает только по данным доступным по формату ДК.

Фильтр отбора состоит из списка элементов задающих условия отбора или условия объединения других условий.

Пустой фильтр (т.е. не содержащий ни одного элемента) ни когда не совпадает ни с чем и результат его использования всегда пустой.

По своей структуре фильтр аналогичен условию отбора Универсальной Статистики - у них общая структура одного условия фильтра, общие логически и группирующие условия и многие операции.

Фильтр представляет из себя список элементарных условий объединяемых через "И".

Большинство условий позволяют сравнивать значение из основного ключа данных (задаётся в "a") как с константой (задаётся в "v") так и со значением из другого ключа данных (задаётся в "b").

Ознакомьтесь в разделе "Общие замечания" с особенностями сравнения чисел и дат при пустых или отсутствующих значениях.

вверх

Пример фильтра ДК

Адрес имеет 2 и меньше ошибки доставки И регион проживания один из RU01,RU05,RU40 И день рождения завтра И ( входи в группу abc ИЛИ входит в группу xyz ) И регион проживания совпадает с регионом заказа


[

          {
            "a" : "member.error.error"      -- Адрес имеет 2 и меньше ошибки доставки

            "op" : "<=",

            "v" : "2" 
          },

          {
            "a" : "personal.region",     -- регион проживания один из RU01,RU05,RU40

            "op" : "in",

            "v" : [ "RU01" ,"RU13" ,"RU40" ]
          },

          {
            "a" : "personal.birthday:YD",     -- день рождения завтра

            "op" : "==",

            "v" : "current +1 day",
          },

          {
           "op" : "OR" 

          ,"v" : [
                  {
                   "op" : "in_group", -- входит в группу abc

                   "v" : "abc",
                  },

                  {
                   "op" : "in_group", -- входит в группу xyz

                   "v" : "xyz",
                  }
                 ]
          ],

          {
            "a" : "personal.region",     -- регион проживания

            "op" : "in",

            "b" : "order.region" 
          }
]

вверх

Метки условий

Метки условий позволяют узнать каким условиям сработал фильтр.

Это можно использовать для тонкой персонализации писем рассылки и красивого оформления сложных отчётов из member.list (например колонка в отчёте в которую выводится значение метки будет содержать или не содержать какое-то слово в зависимости от того сработало условие или нет).

Меткой может быть дополнено любое условии кроме "ИЛИ" (формат не позволяет, но метками можно снабдить внутренние условия этого "ИЛИ").

{
 <прочие условия фильтра>

 "label" : "lalala" -- имя метки фильтра, обязательно если хотите отметить это условие

,"label.yes" : "yyy" -- не обязательно, значение метки при выполнении условия

,"label.no" : "nnn" -- не обязательно, значение метки при не выполнении условия

}

Если указано только label, то при выполнении условия значением метки становится её же имя (lalala=lalala).

Если указано не пустое label.yes, то при выполнении условия значением метки становится значение label.yes (lalala=yyy)

Если указано не пустое label.no, то при не выполнении условия значением метки становится значение label.no (lalala=nnn)

Если значение присваивается метке, которая уже имеет значение, то старое значение заменяется новым.

Набор меток по результатам фильтрации доступен:

- для каждого адреса рассылки при персонализации текста письма - через объект member.LABEL.*

- в вызове member.list в ответе - через параметр label

- в вызове member.list в формате - через элемент { "label" : "lalala" }

Условия объединения в фильтре ДК

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

Условия вычисляются только до момента достаточного для принятия решения об их исходе.

Список условий ИЛИ прекращает вычисляться после первого же условия завершившегося положительно.

Список условий И прекращает вычислять после первого же условия завершившегося отрицательно.

Условие "И" в фильтре ДК


 {

  "op" : "AND" 

 ,"v" : [
          массив описывающий элементы фильтра. "И" между элементами
        ]

 }


 {

  "op" : "!AND" 

 ,"v" : [
          массив описывающий элементы фильтра. "И" между элементами потом отрицание полученного результата
        ]

 }

Условие "ИЛИ" в фильтре ДК

Действует между элементами прочих списков условий кроме основного списка фильтра и списка группировки.


 {

  "op" : "OR" 

 ,"v" : [
          массив описывающий элементы фильтра. "ИЛИ" между элементами
        ]

 }


 {

  "op" : "!OR" 

 ,"v" : [
          массив описывающий элементы фильтра. "ИЛИ" между элементами потом отрицание полученного результата
        ]

 }

вверх

Вхождение в состав группы в фильтре ДК


 {

  "op" : "in_group" или "!in_group" 

 ,"v"  : "код группы" 

 }

вверх

Попадание в выборку Универсальной Статистики в фильтре ДК


 {

  "op" : "stat.uni" или "!stat.uni" 

-- параметры stat.uni

  -- подразумевается unique = 1

 ,"v" : [ условие выборки как у запроса в вызове stat.uni ]

 ,"cache"  : [ настройки кэширования как у запроса в вызове stat.uni ]

 ,"select" : [ ... ] -- используйте только если не подходит значение по умолчанию [ "member.id" ]

 ,"have" : [ условие фильтрации агрегирования как у запроса в вызове stat.uni ]

 }

вверх

Целочисленное сравнение в фильтре ДК

Скалярное значение по ключу данных "a" как число не равно (!=), равно (==), меньше (<), меньше или равно (<=), больше или равно (>=), больше (>) чем число в "v" или величина по ключу данных "b"


 {

 ,"a"  : "ключ данных" 

 ,"op" : "!= | == | < | <= | => | >" 

-- или

 ,"v" : "целое число" 

-- или

 ,"b" : "ключ данных" 

 }

вверх

Cравнение "между"

Скалярное значение по ключу данных "a" больше или равно величина-1 и меньше или равно величина-2.

Сравнение целочисленное. Если у "a" указана точность даты, то сравниваются даты и величины-1/-2 трактуются как даты соответствующей точности.

!between работает как отрицание результата between.


 {

 ,"a"  : "ключ данных" 

 ,"op" : "between" или "!between" 

 ,"v" : [ величина-1, величина-2 ]
 }

вверх

Строчное сравнение

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

Скалярное значение по ключу данных "a" как строка не равно (ne), равно (eq), меньше (lt), меньше или равно (le), больше или равно (ge), больше (gt) чем строка в "v" или величина по ключу данных "b"


 {

 ,"a"  : "ключ данных" 

 ,"op" : "ne | eq | lt | le | ge | gt" 

-- или

 ,"v" : "строка" 

-- или

 ,"b" : "ключ данных" 
 }

вверх

Совпадение подстроки

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

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

begins / !begins - строка по ключу данных "a" начинается/не начинается со строки "v" или строки по ключу данных "b"

сontains / !contains - строка по ключу данных "a" содержит/не содержит строки "v" или строки по ключу данных "b"

ends / !ends - строка по ключу данных "a" оканчивается/не оканчивается на строку "v" или строку по ключу данных "b"


 {

 ,"a"  : "ключ данных" 

 ,"op" : "begins | !begins | contains | !contains | ends | !ends" 

-- или

 ,"v" : "строка" 

-- или

 ,"b" : "ключ данных" 
 }

вверх

Величина null

Скалярное значение по ключу есть (is_null) или не есть (!is_null) неопределённое значение null

 {

 ,"a"  : "ключ данных" 

 ,"op" : "is_null" или "!is_null" 

 }

вверх

- in/!in (есть в stat.uni) - скаляр в списке / не в списке (строчное сравнение)

Вхождение в список

Скалярное значение по ключу входит (in) или не входит (!in) в список. Сравнение строчное.

Если данные по ключу не существуют, то in не совпадёт ни когда, а !in совпадёт всегда.

Если данные по ключу не скаляр, то in не совпадёт ни когда, а !in совпадёт всегда.

Если данные по ключу это null, то он совпадёт с "" в списке.

 {

 ,"a"  : "ключ данных" 

 ,"op" : "in" или "!in" 

 ,"v"  : [ "строка-1", "cтрока-2" ... ]
 }

Пусто

Ключ не существует или существует и есть null.

Если существует, то: скаляр пуст, объект без ключей, массив без элементов.

!is empty - отрицание is empty

 {

 ,"a"  : "ключ данных" 

 ,"op" : "is empty" или "!is empty" 
 }

вверх

Процент аудитории

{
"op" : "percent" "!percent"
,"v" : "целое число от 0 до 100"
}

Отбирает процент аудитории примерно равный указанному

Некая "быстрая" примерная замена А-Б тестирования или параметра выпуска "процент аудитории"

При одной и той же аудитории и при одном и том же проценте результат один и тот же.

Не правильно думать, что percent(40) это те, кто не попал в percent(60)

Те кто не попал в 60 это !percent(60)

Т..е percent(XX) + !percent(XX) всегда даст всю аудиторию

вверх

Поиск по ключам объекта

has any of - cреди ключей объекта "a" есть хоть один из списка "v" (строчное сравнение). если "a" не объект то не совпадёт

has each of - cреди ключей объекта "a" есть каждый из списка "v" (строчное сравнение). если "a" не объект то не совпадёт

!has any of - отрицание has any of. если "a" не объект то не совпадёт

!has each of - отрицание has each of. если "a" не объект то не совпадёт

 {

 ,"a"  : "ключ данных" 

 ,"op" : "has any of"  | "has each of" | "!has any of"  | "!has each of" 

 ,"v"  : [ "строка-1", "cтрока-2" ... ]
 }

Итератор

Итератор предназначен для перебора всех значений списка или объекта и применения к ним суб-фильтра.

Итератор "has" хоть один элемент подходит под условие суб-фильтра.

 {

 ,"a"  : "базовый ключ данных" 

 ,"op" : "has" 

 ,"v"  : [
          суб-фильтр группы. подразумевается "И" 
         ]

 ,"save" : "id-сохранения" -- не обязательно
 }

Итератор "!has" ни один элемент не подходит под условие суб-фильтр.

 {

 ,"a"  : "базовый ключ данных" 

 ,"op" : "!has" 

 ,"v"  : [
          суб-фильтр группы. подразумевается "И" 
         ]

 }

Если базовый ключ:
- не существует или null - результат итератора "не правда"
- если скаляр - фильтр вызывается один раз
- если массив - фильтр вызывается для каждого элемента, по порядку начиная с 0
- если объект - фильтр вызывается для каждого элемента, порядок не определён

При переборе существуют специальные ключи данных для доступа к перебираемым данных:

- "@"   - значение текущего перебираемого элемента: элемент объекта или элемент массива или скаляр
- "@id" - "позиция" для текущего перебираемого элемента: индекса массива или ключ объекта текущего элемента или null для скаляра
- "$" - весь пользователь

При вложенных переборах
- "@[ddd]" - элемент индекса или массива или скаляря от внешнего has на ddd уровней выше
- "@id[ddd]" - величина индекса массива или ключа объекта текущего элемента или null для скаляра от внешнего has на ddd уровней выше

Множественные совпадения итератора has

Существуют ситуации, когда необходимо не просто выяснить результат итератора "совпал-не совпал", а что именно вызвало совпадение.

Например, у подписчика хранится список купленных им лицензий с указанием, среди прочего, даты окончания лицензии.

И надо выпустить напоминание тем, кто ещё не продлил лицензии, оканчивающиеся завтра.

{
 "a" : "product.license" -- ключ списка лицензий

 ,"op" : "has" 

 ,"v" : [
          { -- лицензия истекает завтра
           "a" : "@.expired:YD" 

          ,"op" : "==" 

          ,"v" : "current + 1 day" 
          }

         ,{ -- и ещё не продлена
           "a" : "@.prolongation" 

          ,"op" : "==" 

          ,"v" : "0" 
          }

         ,{ -- и адрес не в домене test.ru
            -- (формально эту проверку надо делать вне итератора, так как её итог не зависит от данных
            -- в элементе "license" и тут она приведена только для примера специального ключа "$")
           "a" : "$.member.domain" 

          ,"op" : "ne" 

          ,"v"  : "test.ru" 
          }

        ]
}

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

А как определить какие именно лицензии вызвали совпадение и что делать если таких лицензий несколько, а писем должно быть по одному на лицензию ?

Для этого предназначен параметр сохранения "save" - каждое совпадение вызывает сохранение ключа данных (а не самих данных) тестирование которого дало совпадение.

{
  "op" : "has" 

 .......

 ,"save" : "lic" 
}

Если у подписчика совпало, допустим, три лицензии, то их ключи данных будут записаны в специальный ключ данных в системной анкете member.FILTER.<величина-из-save>


{
 "member" : {
             ...

            ,"FILTER" : {
                         "lic" : [
                                  "product.license[2]" 
                                 ,"product.license[5]" 
                                 ,"product.license[6]" 
                                 ]
                        }

            ...
            }

}

При выпуске рассылки, в зависимости от параметра выпуска "multiple", сохранённые данные будут доступны как:

- multiple = 0 - как описано выше. member.FILTER хранит все совпадения всех итераторов группы-фильтра. подписчик получит одно письмо

- multiple = 1 - в группе фильтре должен сработать только один итератор c save. подписчик получит несколько писем в данных персонализации которых ключ сохранения будет скаляром последовательно принимающим все совпавшие значения.

В примере с лицензиями, это будет письмо с member.FILTER.lic = "product.license[2]", потом письмо с member.FILTER.lic = "product.license[5]" и ещё письмо с member.FILTER.lic = "product.license[6]".

Для доступа к данным совпавшей лицензии используйте команду [% datakey(anketa,member.FILTER.lic) %]

Так же, можно просто получить список совпадения воспользовавшись вызовом member.list - в ответе в параметре "save" будет структура со всеми совпадениями аналогичная member.FILTER

вверх

Работа с датой и временем

Дата и время хранятся как обычные строки.

Однако при их записи в базу (member.set, member.import) рекомендуется указать тип данных, что бы при записи происходила верификация и нормализация величины.

Без этого, строка представляющая дату-время может не верно проходить проверку с величиной current, так как такие операции требуют строки строго в формате "YYYY-ММ-DD hh:mm:ss" - т.е. год из четырёх цифр, а остальные из двух (с ведущим 0, если надо).

Для работы с датой-временем требуется указать квалификатор точности в ключе данных параметра "a" (или, при наличии, в ключе данных "b"). Это автоматически включит режим работы с датой-временем.

Он задаётся аналогично stat.uni - две буквы точности ("от" и "до") после двоеточия. Например "a.b[0].c[7]:Ym"

Указание квалификатора позволяет понять, что вы хотите использовать значение указанного ключа данных как дату-время, а не как строку или число.

При указании квалификатора, величина "v":

- при задании даты-времени константой - проверяется на соответствие указанной точности. иначе - ошибка

- при задании относительного времени (current) - его результат вычисляется с указанной точностью

- при задании квалификатора у "а", величина "b" без квалификатора считается указанной с таким же квалификатором.

- при задании квалификатора у "b", величина "a" без квалификатора считается указанной с таким же квалификатором.

- при задании квалификаторов и у "а" и у "b" они должны совпадать

При задании квалификатора, точность значения "a" ("b") приводится к указанной точности путём её уменьшения (или сохранения, если они совпадают).

Если "a" ("b") меньшей точность чем указано в квалификаторе, то значение останется как есть. В любом случае дата-время в "a" ("b") должны быть в нормализованном формате "YYYY-MM-DD hh:mm:ss" (пример для точности Ys) и с ведущими нулями для компонентов меньших 10 (т.е не "1971-5-4 3:2:1", а "1971-05-04 03:02:01"). Для этого используйте параметр "type" когда вносите данные через member.set и member.import

Относительное время задаётся аналогично stat.uni - использованием слова "сurrent" и добавлением/вычитанием необходимых компонентов (лет, месяцев, дней, часов..).

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

Если значение по ключу данных не дата-время в верном формате, null, массив или объект, то результат не предсказуем и зависит как от значения, там и от используемого "op".

Допустимые операции c датой-временем

- численные и строчные сравнения (в данном случае они ведут себя одинаково так как явно указан тип данных)

- in и !in

- begins/!begins/contains/!contains/ends/!ends

- between и !between

Если вы уверены, что никогда не ошибётесь в значении "v" при внесении в базу и задании даты-времени константой и не используете относительное время, то квалификатор точности можно не использовать - операции строчного сравнения, in/!in, begins/!begins/contains/!contains/ends/!ends сработают как и ожидается.

вверх

Функция size()

Обычный ключ для size

К величине на которую указывает ключ данных в параметре "a" можно применить функцию размера size().

{

 "a" : "size(member.email)" 

 .....

}

Если величина null, то результат - null.

Если величина скаляр, то результат - его длина в символах.

Если величина массив, то результат - количество элементов в массиве.

Если величина объект, то результат - количество пар "ключ-значение" в объекте.

По результатам has/save для size

Применение size() к результатам, сохранённым в итераторе has, даст количество сохранённых элементов для данного id-сохранения

{

 "a" : "save_size(id-cохранения)" 

 .....

}

Функции агрегации

Доступны функции суммы величин sum(), минимальной величины min(), максимальной величины max(), среднего значения - avg().

В примерах используется agr как имя любой их этих функций

Обычный ключ для агрегации

Если ключ показывает на скаляр, то результат - значение скаляра.

Если ключ показывает на массив, то результат - сумма/минимальный/максимальный/cреднее из элементов массива которые должный быть скалярами. Элемент null пропускается. Строки преобразуются в числа. Не скалярный элемент вызовет ошибку фильтра.

{
 "a" : "agr(КД)" 
}

Если ключ показывает на объект, то результат - сумма/минимальный/максимальный/cреднее из элементов (которые должный быть скалярами) взятых взятых от каждой пары объекта по относительному ключу. Элемент null пропускается. Строки преобразуются в числа. Не скалярный элемент вызовет ошибку фильтра.

{
 "a" : "agr(КД-ОБЪЕКТ,@.КД-ОТНОСИТЕЛЬНO-ОБЪЕКТА)" 
}

По результатам has/save для агрегации

Применение к результатам, сохранённым в итераторе has, даст соответствующую функции величину из элементов взятых по относительному ключу от каждого ключа из сохранённых в id-сохранения. Элемент null пропускается. Строки преобразуются в числа. Не скалярный элемент вызовет ошибку фильтра.

{

 "a" : "save_agr(id-cохранения,@.КД-ОТНОСИТЕЛЬНЫЙ)" 

 .....

}

вверх

Фильтр отбора в группе (АВО)

Этот фильтр работает только по данным доступным по формату АВО и его признаком является наличие "which" в первом элементе.

Без этого фильтр считается заданным в формате ДК, описанном выше.

Фильтр отбора состоит из списка элементов задающих условия отбора или условия объединения других условий.

Пустой фильтр (т.е. не содержащий ни одного элемента) ни когда не совпадает ни с чем и результат его использования всегда пустой.

Каждый элемент содержит поле "which" задающее его тип и возможные дополнительные поля в зависимости от типа:

При получении описания фильтра вставляются дополнительные поля aid.name, qid.name и pid.name содержащие названия, соответствующие кодам.

И значения ключей хэша resp так же становятся названиями соответствующих ответов.

Это позволяет сократить количество запросов к API для визуализации фильтра на стороне клиента.

При установке значения фильтра эти дополнительные поля и значения ключей хэша resp игнорируются.

Ознакомьтесь в разделе "Общие замечания" с особенностями сравнения чисел и дат при пустых или отсутствующих значениях.

вверх

Формальное описание фильтра АВО


[

  {

   "which" : "тип элемента 1" 

   <возможно дополнительные поля>

  }

 ,{

   "which" : "тип элемента 2" 

   <возможно дополнительные поля>

  }

 ...

 ,{

   "which" : "тип элемента N" 

   <возможно дополнительные поля>

  }

]

вверх

Пример фильтра АВО

Адрес имеет 2 и меньше ошибки доставки И регион проживания один из RU01,RU05,RU40 И день рождения завтра И ( входи в группу abc ИЛИ входит в группу xyz )

При записи фильтра


[

          {

            "which" : "<=",      -- Адрес имеет 2 и меньше ошибки доставки

            "aid" : "member",

            "qid" : "error",

            "resp" : "2" 

          },

          {

            "which" : "AND" 

          },

          {

            "which" : "any",     -- регион проживания один из RU01,RU05,RU40

            "aid" : "personal",

            "qid" : "region",

            "resp" : {

                        "RU01" : null

                       ,"RU13" : null

                       ,"RU40" : null

                     }

          },

          {

            "which" : "AND" 

          },

          {

            "which" : "dtnowmd",     -- день рождения завтра

            "aid" : "personal",

            "qid" : "birthday",

            "resp" : "+1" 

          },

          {

            "which" : "AND" 

          },

          {

            "which" : "group",

            "group" : [

                         {

                           "which" : "PRF", -- входи в группу abc

                           "resp" : 1,

                           "pid" : "abc" 

                         },

                         {

                           "which" : "OR" 

                         },

                         {

                           "which" : "PRF", -- входи в группу xyz

                           "resp" : 1,

                           "pid" : "xyz" 

                         }

          }

При получении фильтра


[

          {

            "which" : "<=",      -- Адрес имеет 2 и меньше ошибки доставки

            "aid" : "member",

            "qid" : "error",

            "resp" : "2",

            "aid.name" : "Cистемная анкета",

            "qid.name" : "Количество ошибок доставки" 

          },

          {

            "which" : "AND" 

          },

          {

            "which" : "any",     -- регион проживания один из RU01,RU05,RU40

            "aid" : "personal",

            "qid" : "region",

            "aid.name" : "Персональные данные",

            "qid.name" : "Регион" 

            "resp" : {

                        "RU01" : "Адыгея" 

                       ,"RU13" : "Мордовия" 

                       ,"RU40" : "Калуга" 

                     }

          },

          {

            "which" : "AND" 

          },

          {

            "which" : "dtnowmd",     -- день рождения завтра

            "aid" : "personal",

            "qid" : "birthday",

            "resp" : "+1" 

            "aid.name" : "Персональные данные",

            "qid.name" : "День рождения" 

          },

          {

            "which" : "AND" 

          },

          {

            "which" : "group",

            "group" : [

                         {

                           "which" : "PRF", -- входи в группу abc

                           "resp" : 1,

                           "pid" : "abc" 

                           "pid.name" : "Группа знающих буквы A-B-C" 

                         },

                         {

                           "which" : "OR" 

                         },

                         {

                           "which" : "PRF", -- входи в группу xyz

                           "resp" : 1,

                           "pid" : "xyz" 

                           "pid.name" : "Группы знающих буквы X-Y-Z" 

                         }

          }

вверх

Условия объединения в фильтре АВО

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

Условие "И" в фильтре АВО


 {

  "which" : "AND" 

 }

Условие "ИЛИ" в фильтре АВО


 {

  "which" : "OR" 

 }

Группа условий (скобки)


 {

  "which" : "group" 

  "group" : [

              массив описывающий фильтр

            ]

 }

вверх

Вхождение в состав группы в фильтре АВО


 {

  "which" : "PRF" 

 ,"resp"  : "0 - не входит в группу, 1 - входит в группу" 

 ,"pid"   : "код проверяемой группы" 

 }

вверх

Попадание в выборку Универсальной Статистики в фильтре АВО


 {

  "which" : "stat.uni" 

 ,"resp"  : "0 - не попадает, 1 - попадает" 

-- параметры stat.uni

  -- подразумевается unique = 1

 ,"filter" : [ условие выборки как у запроса в вызове stat.uni ]

 ,"have"   : [ не обязательно. условие отбора поле группировки в фильтре ]

 ,"cache"  : [ настройки кэширования как у запроса в вызове stat.uni ]

 ,"select" : [ ... ] -- используйте только если не подходит значение по умолчанию [ "member.id" ]

 ,"have" : [ условие фильтрации агрегирования как у запроса в вызове stat.uni ]

 }

вверх

Любой из списка ответов

Хотя бы одни ответ из списка есть в ответах подписчика


 {

  "which" : "any" 

 ,"aid"  : "код анкеты" 

 ,"qid"  : "код вопроса" 

 ,"resp"   : {

              "код ответа 1" : null

             ,"код ответа 2" : null

              .....

             }

 }

вверх

Каждый из списка ответов

Каждый ответ из списка есть в ответах подписчика


 {

  "which" : "each" 

 ,"aid"  : "код анкеты" 

 ,"qid"  : "код вопроса" 

 ,"resp"   : {

              "код ответа 1" : null

             ,"код ответа 2" : null

              .....

             }

 }

вверх

Есть хоть один ответ

Выбран хотя бы один ответ


 {

  "which" : "atleast" 

 ,"aid"  : "код анкеты" 

 ,"qid"  : "код вопроса" 

 }

вверх

Нет ни одного ответа

На вопрос не выбран ни один ответ


 {

  "which" : "empty" 

 ,"aid"  : "код анкеты" 

 ,"qid"  : "код вопроса" 

 }

вверх

Целочисленное сравнение в фильтре АВО

Ответ на вопрос как число не равен (!=), равен (==), меньше(<), меньше или равен (<=), больше или равен (>=), больше (>) чем число в "resp"


 {

  "which" : "!= | == | < | <= | => | >" 

 ,"aid"  : "код анкеты" 

 ,"qid"  : "код вопроса" 

 ,"resp" : "целое число" 

 }

вверх

Числено равно сегодня

Ответ на вопрос числено равен текущему году (nowy), месяцу (nowm) или дню (nowd)


 {

  "which" : "nowy | nowm | nowd" 

 ,"aid"  : "код анкеты" 

 ,"qid"  : "код вопроса" 

 }

вверх

Дата равна сегодня

Компонента "дата" в ответе равна (dtnow), не равна (dtnowne), ранее (dtnowlt), ранее или равна (dtnowle), позже или равна (dtnowge), позже (dtnowgl) чем "дата сегодня +/- сдвиг"


 {

  "which" : " dtnow | dtnowne | dtnowlt | dtnowle | dtnowge | dtnowgt " 

 ,"aid"  : "код анкеты" 

 ,"qid"  : "код вопроса" 

 ,"resp" : "сдвиг в днях от сегодня" -- не обязательно. например: -2 - позавчера, +1 - завтра

 }

вверх

Месяц и день равны сегодня

Компонента "месяц и день" в ответе равны месяцу и дню в "дате сегодня +/- сдвиг"


 {

  "which" : "dtnowmd" 

 ,"aid"  : "код анкеты" 

 ,"qid"  : "код вопроса" 

 ,"resp" : "сдвиг в днях от сегодня" -- не обязательно. например: -2 - позавчера, +1 - завтра

 }

вверх

Год равен текущему

Компонент "год" в ответе равен текущему году


 {

  "which" : "dtnowy" 

 ,"aid"  : "код анкеты" 

 ,"qid"  : "код вопроса" 

 }

вверх

Месяц равен текущему

Компонент "месяц" в ответе равен текущему месяцу


 {

  "which" : "dtnowm" 

 ,"aid"  : "код анкеты" 

 ,"qid"  : "код вопроса" 

 }

вверх

День равен текущему

Компонент "день" в ответе равен текущему дню +/- сдвиг


 {

  "which" : "dtnowd" 

 ,"aid"  : "код анкеты" 

 ,"qid"  : "код вопроса" 

 ,"resp" : "сдвиг в днях от сегодня" -- не обязательно. например: -2 - позавчера, +1 - завтра

 }

вверх

Строка пуста

Ответ пустой


 {

  "which" : "emptyS" 

 ,"aid"  : "код анкеты" 

 ,"qid"  : "код вопроса" 

 }

вверх

Строка не пуста


 {

  "which" : "dirtyS" 

 ,"aid"  : "код анкеты" 

 ,"qid"  : "код вопроса" 

 }

вверх

Cтрочное сравнение

Это медленый фильтр.

В будущем он может быть удалён.

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

Строка ответа на вопрос не равна (ne), равна (eq), меньше(lt), меньше или равна (le), больше или равна (ge), больше (gt) чем строка в "resp"

Для вопросов типа дата-время сравнение производится с учётом точности resp.

Например, при содержимом ответа на вопрос "2015-03-26 18:36:25" (от года до секунды) сравнение с "2015-03-30" (от года до дня) вызовет
сначала снижение точности ответа до аналогичной - "2015-03-26" и только потом будет проведено сравнение.

Если точность resp выше точности ответа на вопрос, то точности уравниваться не будут и произойдёт обычное сравнение строк.


 {

  "which" : "ne | eq | lt | le | ge | gt" 

 ,"aid"  : "код анкеты" 

 ,"qid"  : "код вопроса" 

 ,"resp" : "строка для сравнения" 

 }

вверх

Наличие подстроки в строке

Это медленый фильтр.

В будущем он может быть удалён.

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

Строка ответа начинается (beg) или не начинается (nbeg) со строки "resp"; содержит (has) или не содержит (nhas) строку "resp"; заканчивает (end) или не заканчивается (nend) строкой "resp"


 {

  "which" : "has | nhas | beg | nbeg | end | nend" 

 ,"aid"  : "код анкеты" 

 ,"qid"  : "код вопроса" 

 ,"resp" : "строка" 

 }

вверх

Примеры запросов универсальной статистики

Переходы по ссылкам

Суммарное количество переходов по ссылкам


{

  "select" : ["issue.name" ,"issue.group.name" ,"issue.dt" ,"issue.clicked" ,"issue.members"]

 ,"order"  : ["issue.dt"]

}


{

  "select" : ["issue.name" ,"issue.group.name" ,"issue.dt" ,"issue.clicked" ,"issue.members"]

 ,"filter" : [{ "a" : "issue.group.gid", "op" : "==" , "v" : "gdfhdfh" }]

 ,"order"  : ["issue.dt"]

}


{

  "select" : ["issue.name" ,"issue.group.name" ,"issue.dt" ,"issue.clicked" ,"issue.members"]

 ,"filter" : [ {"a" : "issue.group.gid", "op" : "==", "v" : "gdfhdfh" }
             , {"a" : "issue.dt", "op" : ">=", "v" : "2011-03-17 11:30:27"}]

}


{

  "select" : ["issue.name" ,"issue.group.name" ,"issue.dt" ,"issue.clicked" ,"issue.members"]

 ,"filter" : [{"a" : "issue.id", "op" : "==", "v" : 15 }]

 ,"order"  : ["issue.dt"]

}


{

  "select" : ["issue.name" ,"issue.group.name" ,"issue.dt" ,"issue.clicked" ,"issue.members"]

 ,"filter" : [ {"a" : "issue.group.gid", "op" : "==", "v" : "gdfhdfh" }
             , {"a" : "issue.dt", "op" : "==", "v" : "2011-03-17 11:30:27" }]

 ,"order"  : ["issue.dt"]

}

Переходы по ссылкам за период с разбивкой по времени


{

  "select" : ["click.dt:YD" ,"count(*)"]

 ,"order"  : ["click.dt:YD"]

}


{

  "select" : ["click.dt:YM" ,"count(*)"]

 ,"order"  : ["click.dt:YM"]

}


{

  "select" : ["click.dt:YY" ,"count(*)"]

 ,"order"  : ["click.dt:YY"]

}


{

  "select" : ["click.dt:YD" ,"count(*)"]

 ,"filter" : [ { "a" : "click.dt:YM", "op" : ">=", "v" : "2011-05" }
             , { "a" : "click.dt:YM", "op" : "<=", "v" : "2012-05" } ]

 ,"order"  : ["click.dt:YD"]

}


{

  "select" : ["click.dt:YM" ,"count(*)"]

 ,"filter" : [ { "a" : "click.dt:YY", "v" : "2011", "op" : ">=" }
             , { "a" : "click.dt:YY", "op" : "<=", "v" : "2010" } ]

 ,"order"  : ["click.dt:YM"]

}


{

  "select" : ["click.dt:YD" ,"count(*)"]

 ,"filter" : [ { "a" : "issue.id", "op":"==", "v" : 15}
             , { "a" : "click.dt:YM", "op" : "<=", "v" : "2011-05" }]

 ,"order"  : ["click.dt:YD"]

}


{

  "select" : ["click.dt:YD" ,"count(*)"]

 ,"filter" : [{"a" : "issue.dt:YY", "op":">", "v" : "2011"} ]

 ,"order"  : ["click.dt:YD"]

}


{

  "select" : ["click.dt:YD" ,"count(*)"]

 ,"filter" : [ { "a" : "issue.dt:YY", "op":">", "v" : "2011"}
             , { "a" : "issue.group.gid", "op" : "==", "v" : "gdfhdfh" }]

 ,"order"  : ["click.dt:YD"]

}

Переходы по каждой ссылке в выпуске


{

  "select" : ["click.link.url" ,"count(*)"]

 ,"order"  : ["click.link.url"]

}


{

  "select" : ["click.link.url" ,"count(*)"]

 ,"order"  : ["click.link.url"]

 ,"filter" : [ { "a" : "click.dt:YY", "op" : "==", "v" : "2010" } ]

}


{

  "select" : ["click.link.url" ,"count(*)"]

 ,"filter" : [ { "a" : "click.dt:YY", "op" : "==", "v" : "2011" }
             , { "a" : "issue.id", "op" : "==", "v" : "16" } ]

 ,"order"  : ["click.link.url"]

}


{

  "select" : ["click.link.url" ,"count(*)"]

 ,"order" : ["click.link.url"]

 ,"filter" : [ { "a" : "click.dt:YY", "op" : ">=", "v" : "2011" }
             , { "a":"issue.group.gid", "op":"==", "v":"all"}
             , { "a" : "issue.dt", "op" :  "==", "v" : "2010-01-26 16:51:26" } ]

}

Переходы по определенной ссылке


{

  "select" : ["click.dt:YD" ,"count(*)"]

 ,"filter" : [ {"a" : "click.link.url", "op" : "==", "v" : "http://www.disney.com/" }]

}


{

  "select" : ["click.dt:YD" ,"count(*)"]

 ,"filter" : [ {"a" : "click.link.url", "op" : "==", "v" : "http://www.disney.com/" }
             , { "a" : "click.dt", "op" : ">=", "v" : "2011-05-11 14:25:54" }]

}


{

  "select" : ["click.dt:YD" ,"count(*)"]

 ,"filter" : [ {"a" : "click.linkgroup.id", "op" : "==", "v" : "16" }
             , { "a" : "click.dt", "op" : ">=", "v" : "2011-05-11 14:25:54" }]

}


{

  "select" : ["click.dt:YD" ,"count(*)"]

 ,"filter" : [ {"a" : "click.linkgroup.name", "op" : "==", "v" : "Профиль all" }
             , {"a" : "click.dt", "op" : ">=", "v" : "2011-05-11 14:25:54" }]

}

Переходы определенного подписчика по ссылкам


{

  "select" : ["click.dt:YD" ,"click.link.url" ,"count(*)"]

 ,"filter" : [{"a" : "member.email", "op" : "==", "v" : "vadim\@iprojects.ru" }]

}


{

  "select" : ["click.dt:YD","click.link.url ","count(*)"]

 ,"filter" : [ {"a" : "member.email", "op" : "==", "v" : "ask\@subscribe.ru" }
             , { "a" : "click.dt:Ym", "op" : ">=", "v" : "2011-08-10 13:30" } ]

}

Чтения

Все чтения выпуска


{

  "select" : ["issue.name", "issue.group.name" ,"issue.dt" ,"issue.readed" ,"issue.members"]

}


{

  "select" : ["issue.name", "issue.group.name" ,"issue.dt" ,"issue.readed" ,"issue.members"]

 ,"filter" : [ {"a" : "issue.group.gid", "op" : "==", "v" : "import20110627175254" }
             , { "a" : "issue.dt", "op" : "==", "v" : "2011-06-28 13:54:48" } ]

}

Чтения выпуска за период с разбивкой по времени (по дням, месяцам, годам)


{

  "select" : ["read.dt:YY" ,"count(*)"]

}


{

  "select" : ["read.dt:YM" ,"count(*)"]

}


{

  "select" : ["read.dt:YD" ,"count(*)"]

}


{

  "select" : ["read.dt:YD" ,"count(*)"]

 ,"filter" : [ {"a" : "issue.group.gid", "op" : "==", "v" : "import20100720185533" }
             , { "a" : "issue.dt", "op" : "==", "v" : "2010-10-18 17:17:17" }]

}


{

  "select" : ["read.dt:YD" ,"count(*)"]

 ,"filter" : [{"a" : "issue.dt:Ym", "op" : ">", "v" : "2010-10-18 17:30" }]

}

Зафиксированные чтения выпусков подписчиками


{

 "select" : ["read.dt" ,"member.email"]

}


{

  "select" : ["read.dt" ,"member.email"]

 ,"filter" : [ {"a":"read.dt:YD", "op" : "<=", "v" : "2010-10-20" }]

 ,"order"  : [ "read.dt"]

}


{

  "select" : ["read.dt" ,"member.email"]

 ,"filter" : [ { "a" : "read.dt:YD", "op" : "<=", "v" : "2010-10-20" }
             , { "a" : "issue.group.gid", "op" : "==", "v" : "import20100720185533" }
             , { "a" : "issue.dt", "op" : "==", "v" : "2010-10-18 17:17:17" }
             ]

 ,"order"  : [ "read.dt"]

}

Доставка выпусков

Успешная доставка


{

  "select" : ["issue.name" ,"issue.group.name" ,"issue.dt" ,"issue.deliv_ok" ,"issue.members"]

 ,"filter" : [ { "a" : "deliv.status", "op" : ">", "v" : "0" } ]

}


{

  "select" : ["issue.name","issue.group.name","issue.dt","issue.deliv_ok","issue.members"]

 ,"filter" : [ { "a" : "deliv.status", "op" : ">", "v" : "0" }
             , { "a" : "issue.group.gid", "op" : "==", "v" : "masssending" } ]

}


{

  "select" : ["issue.name","issue.group.name","issue.dt","issue.deliv_ok","issue.members"]

 ,"filter" : [ { "a" : "deliv.status", "op" : ">", "v" : "0" }
             , { "a" : "issue.group.gid", "op" : "==", "v" : "masssending" }
             , { "a" : "issue.dt", "op" : "==", "v" : "2010-05-31 16:23:47" } ]

}

Ошибки при доставке выпусков


{

  "select" : ["issue.name","issue.group.name","issue.dt","member.email","deliv.status"]

 ,"filter" : [ { "a" : "deliv.status", "op" : "<", "v" : "0" } ]

}


{

  "select" : ["member.email","deliv.status"]

 ,"filter" : [ { "a" : "deliv.status", "op" : "<", "v" : "0" }
             , { "a" : "issue.group.gid", "op" : "==", "v" : "p212" }
             , { "a" : "issue.dt", "op" : "==", "v" : "2010-12-31 23:02:29" } ]

}

Доставка выпусков определенному подписчику


{

  "select" : ["issue.name","issue.group.name","issue.dt","deliv.status"]

 ,"filter" : [ { "a" : "member.email", "op" : "==", "v" : "test@test.ru" } ]

}


{

  "select" : ["issue.name","issue.group.name","issue.dt","deliv.status"]

 ,"filter" : [ { "a" : "member.email", "op" : "==", "v" : "test@test.ru"}
             , { "a" : "issue.dt:YM", "op" : ">=", "v" : "2010-12" } ]

}

Замена вызова stat.issue


{

  "select" : ["issue.id","issue.group.gid","issue.dt","member.email","deliv.status"]

 ,"filter" : [ {"a" : "issue.dt:YM", "op" : ">=", "v" : "2011-08" }
             , {"a":"deliv.status", "op":"<","v":"0"} ]

 ,"order"  : ["issue.id","issue.dt","member.email"]

}


{

  "select" : ["member.email","count(deliv.*)"]

 ,"filter" : [ {"a" : "issue.dt:YM", "op" : ">=", "v" : "2011-08" }
             , { "a":"deliv.status", "op":"<","v":"0"} ]

 ,"order"  : ["member.email"]

}


{

  "select" : ["member.email","count(deliv.*)"]

 ,"filter" : [{"a" : "issue.dt:YM", "op" : ">=", "v" : "2011-08" }]

 ,"order"  : ["member.email"]

}

Сводные отчеты

Суммарное число чтений, кликов по дням


{

  "select" : ["*.dt:YD", "count(click)", "count(read)" ]

 ,"order"  : ["-*.dt:YD"]

}

Суммарное число чтений, кликов по месяцам с 2021 года


{

  "select" : ["*.dt:YM", "count(click)", "count(read)" ]

 ,"filter" : [{"a":"*.dt:YY", "op" : ">=", "v" : "2021" }]

 ,"order"  : ["-*.dt:YM"]

}

Суммарное число много чего по дате события

Указание *.dt:YM ограничивает дату события - т.е. дату клика, чтения, выпуска. Даты событий не взаимосвязаны и клики и чтения берутся все случившиеся

в указанный интервал. Сравните со следующим отчётом.


{

  "select" : ["*.dt:YM","sum(issue.members)","count(deliv)","count(read)","count(unique read.member.id)","count(click)", "count(unique click.member.id)"]

 ,"filter" : [ {"a" : "*.dt:YM", "op" : ">=", "v" : "2021-06" }
             , {"a" : "deliv.status", "op" : ">", "v" : "0" }]

 ,"order"  : ["-*.dt:YM"]

}

Суммарное число много чего для даты выпуска

Указание *.issue.dt:YM ограничивает дату выпуска - т.е. клики и чтения считаются только от выпусков указанного интервала


{

  "select" : ["issue.dt:YM","sum(issue.members)","sum(issue.deliv_ok)","sum(issue.clicked)","sum(issue.readed)"]

 ,"filter" : [ {"a" : "issue.dt:YM", "op" : ">=", "v" : "2021-06" } ]

 ,"order"  : ["issue.dt:YM"]

}

Статистика активности по выпускам

Быстрый вызов по заранее подготовленным данным с небольшим (5-10 минут) отставанием от реального времени


{

  "select" : [ "issue.name", "issue.dt", "issue.group.name", "issue.members",
             , "issue.deliv_ok", "issue.deliv_bad", "issue.clicked", "issue.u_clicked",
             , "issue.readed", "issue.u_readed", "issue.unsubed" 
             ]

 ,"filter" : [ {"a" : "issue.dt", "op" : ">=", "v" : "2021-06-01 00:00:00" } ]

 ,"order"  : [ "issue.dt" ]

}

Аналогичный, но заметно более медленный на больших списках, вызов по данным в реальном времени

{

  "select" : [ "*.issue.name", "*.issue.dt", "*.issue.group.name", "*.issue.members" 
             , "count(deliv_ok)", "count(deliv_bad)", "count(click)", "count(unique click.member.id)" 
             , "count(read)", "count(unique read.member.id)" 
             ]

 ,"filter" : [ {"a" : "*.issue.dt", "op" : ">=", "v" : "2021-06-01 00:00:00" } ]

 ,"order"  : ["*.issue.dt"]

}

Запрос с объединением двух единичных запросов

Для каждого участника выпуска номер 50 выбирается его адрес, дата внесения в базу, дата выпуска рассылки, статус доставки, дата первого клика в письме рассылки, общее число кликов в письме

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

{
 "action":"stat.uni" 

,"joinby" : 2 -- размер ключа

,"join" : [

   {  -- Первый запрос. На основе списков доставки выбираются участники выпуска 50 и их данные
    "select":[
             -- уникальный ключ
              "deliv.member.id" 
             ,"deliv.issue.id" 

             -- данные об адресе и доставке
             ,"deliv.member.email" 
             ,"deliv.member.anketa.member.create.time" 
             ,"deliv.issue.dt" 
             ,"deliv.status" 
             ]

   ,"filter":[
              {"a":"deliv.issue.id","op":"==","v":"50"}
             ]
  }
,

  {   -- Второй вопрос. Данные о кликах выпуска 50
   "select":[
            -- уникальный ключ
             "click.member.id" 
            ,"click.issue.id" 

            -- данные о первом клике и их количество
            ,"min(click.dt)" 
            ,"count(*)" 
            ]

  ,"filter":[
             {"a":"click.issue.id","op":"==","v":"50"}
            ]
  }
       ]

,"caption":[
           -- Один раз колонки уникального ключа

            "Номер получателя" 
           ,"Номер рассылки" 

           -- Колонки первого запроса

           ,"Адрес получателя" 
           ,"Дата регистрации" 
           ,"Дата рассылки" 
           ,"Статус доставки" 

           -- Колонки второго запроса

           ,"Дата первого клика" 
           ,"Число кликов" 
           ]
}

В результате получится примерно следующее.

Пользователь второй из строки не кликал, что отражается как null в соответствующий колонках.

Номер получателя Номер рассылки Адрес получателя Дата регистрации Дата рассылки Статус доставки Дата первого клика Число кликов
34 50 2013-08-27 13:49:14 2013-10-04 16:26:03 1 2013-10-04 16:45:28 1
21 50 2013-08-27 10:07:17 2013-10-04 16:26:03 1 null null
78 50 2013-08-27 10:36:32 2013-10-04 16:26:03 1 2013-10-04 16:45:28 3

Форматы данных для импортирования и Экспресс-выпуска

Указание дат и времени для модели АВО

В зависимости от точности заданной в анкете для вопроса типа "дата", необходимо использовать один из следующих форматов записи даты или даты-времени в данных импорта:

точность yd - "YYYY-MM-DD" 
точность yh - "YYYY-MM-DD hh"
точность ym - "YYYY-MM-DD hh:mm"
точность ys - "YYYY-MM-DD hh:mm:ss"

Указание дат и времени для модели КД

Рекомендации для указания даты и времени те же, что и описанные в вызове member.set

Присоединение или удаление дополнительных идентификаторов для модели АВО

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

Специальные колонки называются member.head.attach и member.head.detach и ведут себя аналогично одноимённым вызовам.

В отличии от обычных колонок данных эти специальные колонки могут быть указаны не по одному разу.

Допустимое значение может быть

Если идентификаторы в колонке имеют одни и тот же тип который не определяется автоматически, то тип можно указать в названии колонки. Например "member.head.attach:csid".

Указание типа в названии колонки полезно и при внесении автоматически определяемых типов для строго контроля. Например при добавлении номеров телефонов и без
строгой типизации случайно попавший в данные email будет обработан. А при строгой типизации - нет.

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

Пример:

email,a265.q827,member.head.attach:email,a265.q300,member.head.attach:csid
30_m-1@test.ru,30-M1-265+,30_m-1-A@test.ru,30-M1-300,30_m-1-B@test.ru
30_m-2@test.ru,30-M2-265+,,30-M1-300,30_m-2-B@test.ru
30_m-3@test.ru,30-M3-265+,30_m-3-A@test.ru,30-M3-300,30_m-3-B@test.ru|30_m-3-C@test.ru
30_m-4@test.ru,30-M2-265+,,,xxxxxx

Присоединение или удаление дополнительных идентификаторов для модели КД

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

Специальные ключи данных называются member.head.attach и member.head.detach и ведут себя аналогично одноимённым вызовам.

{
 "datakey" : "member.head.attach|detach" 
,"addr_type" : "тип адреса" -- не обязательно. строгая типизация типа идентификатора.
}

Допустимое значение может быть

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

Указание типа полезно и при внесении автоматически определяемых типов для строго контроля. Например при добавлении номеров телефонов и без
строгой типизации случайно попавший в данные email будет обработан. А при строгой типизации - нет.

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

Описание форматов

Для импортирования данных или выпуска рассылки через Экспресс-Выпуск доступны четыре формата описания адресов и их данных.

Данные задаваемые непосредственно в вызове указываются в параметре users.list

Внешние данные указываются ссылкой в параметре users.url

Вместо настоящего идентификатора подписчика вы можете указывать номер подпиcчика в системе - id:DDDD - где DDD номер подписчика доступный через member.id
(только для уже существующих подписчиков)

При использовании в модели ABO значение для вопроса с выбором может быть как кодом ответа, так и названием ответа.

Если значение совпало с имеющимся кодом - значит это код.

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

Если два ответа имеют одинаковое название - выбирается первый по порядку строчной сортировки их кодов.

JSON-массив

Непосредственно задание массива содержащего объекты каждый из которых описывает один адрес и его данных любой структуры. (только Экспресс-выпуск)

Данные трактуются по модели КД.

При выпуске почтовой рассылки адрес получателя задаётся ключом member.email

При выпуске sms рассылки номер получателя задаётся ключом member.cellphone

При выпуске viber рассылки номер получателя задаётся ключом member.viber

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

JSON-объект-АВО

Непосредственно задание объекта содержащего массив caption для описания соответствия столбцов данных анкетам и массив rows для самих данных.

При выпуске почтовой рассылки адрес получателя находится в колонке member.email.

При выпуске sms рассылки номер получателя находится в колонке member.cellphone.

При выпуске viber рассылки номер получателя находится в колонке member.viber.

Остальные данные не обязательны, но при их наличии каждая колонка содержит только по одному значению для каждого адреса (не может быть массивом или объектом) и должна быть описана в caption.

JSON-объект-КД

Непосредственно задание объекта содержащего массив caption для описания соответствия столбцов ключам данных и массив rows для самих данных. (только импорт)

XLSX

Представление содержимого файла XLSX (Excel 2007) в виде строки.

Данные трактуются по модели АВО

Рекомендуется всё же CSV - XLSX всё равно преобразуется внутри в CSV для того, что бы подключить многопоточную обработку, а преобразование хоть и быстрое (тысячи строк с секунду) но на большом реестре всё равно займёт заметное время.

Первый лист должен содержать в первой строке описание какой анкете и ответу соответствует данная колонка (в формате коданкеты.кодвопроса).

При импорте возможно задание этого соответствия в параметре caption вместо первой строки первого листа.

Одна ячейка может содержать только одно значение для каждого адреса (не может быть массивом или объектом).

Понимаются данных сжатые архиватором zip, хотя пользы от этого ни какой - xlsx и так является zip-архивом и повторное сжатие нечего не даст.

CSV

Представление содержимого файла CSV в виде строки.

Данные трактуются по модели АВО

Разделитель колонок - запятая или точка с запятой.

Cимвол экранирования - двойная кавычка.

Содержимое ячейки может быть заключено в кавычки.

Если надо внести сразу несколько ответов на вопрос с множественным выбором, то ячейка должна содержать кода необходимых ответов разделенной символом вертикальной черты "|".

Первая может содержать "#charset=кошка" для однозначного определения кодировки.

Тогда следующая строка должна содержать описание какой анкете и ответу соответствует данная колонка (в формате коданкеты.кодвопроса).

При импорте возможно задание этого соответствия в параметре caption вместо строки.

Одна ячейка может содержать только одно значение для каждого адреса (не может быть массивом или объектом).

Понимаются данных сжатые архиватором zip.

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

Верно:

test@test.ru,"ЗАО ""Рога и копыта""",+70000000000
test@test.ru,"Рога, копыта и хвосты",+70000000000

НЕ верно:

test@test.ru,ЗАО "Рога и копыта",+70000000000
test@test.ru,Рога, копыта и хвосты,+70000000000

Порядок определения формата при использовании users.list

1) Если значение массив, то данные считаются заданными в формате JSON-массив

2) Если значение объект, то данные считаются заданными в формате JSON-объект

3) Если значение строка представляющая zip-архив и он содержит файл workbook.xml, то считается что это XLSX (технически XLSX это zip-архив со специальным набором файлов)

4) Если значение строка представляющая zip-архив и он не содержит файл workbook.xml, берётся первый по порядку файл архива и cчитается, что это данные в формате CSV

5) Если значение строка не-zip-архив, то считается, что это данные в формате CSV

Порядок определения формата при использовании users.url

1) Если content-type ответа application/json, то данные должны описывать массив или объект и формат выбирается как описано выше в пунктах 1 и 2.

2) При другом значении content-type выбор происходит как описано выше в пунктах 3, 4 и 5.

Пример задания JSON-массив

Доступ к этим данным при Экспресс-выпуске осуществляется с использованием префикса "anketa".

Например: [% anketa.member.email %] [% anketa.hash.DEF %] [% anketa.peronal.something[0].aaa %]

"users.list" : [

{
 "member" : { "email" : "test@test.ru" },

-- простые данные

 "string" : "строка текста какой-то длинные",

 "hash" : {
           "DEF" : "значение ключа DEF" 
          ,"ABC" : "значение ключа ABC" 
          },

 "array" : [ "первый элемент" ,"второй элемент" ,"третий элемент" ....],

-- структура с вложенными данными

 "personal" : {

      "fio" : {
               "fam" : "Фамилия" 
              ,"name" : "Имя" 
              }

      "homephone" : "112",

      "gender" : "есть",

      "something" : [ { "aaa" : "bbb", "ccc" : "ddd" }, "eeeee", [ "ffff", "ggg", "hhh"] ]
 }

-- ещё структура

"kredit-payment-by-month" : [
                              {
                               "month" : "2012-12" 
                              ,"summa" : "1234" 
                              },
                              {
                               "month" : "2013-01" 
                              ,"summa" : "4567" 
                              },
                              {
                               "month" : "2013-02" 
                              ,"summa" : "8901" 
                              }
                            ]
},

{
 "member" : { "email" : "test2@test.ru" },

 .........

},

.....

]

Пример задания JSON-объект-АВО

"users.list" : {

  "caption" : [
      {
       "anketa" : "member" 
      ,"quest" : "cellphone" 
      },
      {
       "anketa" : "info" 
      ,"quest" : "firstname" 
      },
      {
       "anketa" : "info" 
      ,"quest" : "title" 
      },
      {
       "anketa" : "info" 
      ,"quest" : "middlename" 
      },
  ],

 "rows" : [
     [ "+70000000000","Павел","Иванович","Уважаемый" ],
     [ "+70000000000","Алексей","Алексеевич","Глубоко уважаемый" ],
     ......
 ]

}

Пример задания JSON-объект-КД

Параметр caption может содержать

- ignore - со значение 1 для игнорирования столбца
- описание ключа данных datakey, режима mode и, не обязательно, тип данных "type" - в ячейке данных любая структура
- указание на динамический ключ dynamic - в ячейке данных структура аналогичная единичной записи изменения данных как в member.set c datakey или null (ячейка игнорируется)

Изменение данных происходит по тем же правилам, что и при вызове member.set

Можно думать о импорте JSON-объект-КД как о массовом member.set с индивидуальными данными для каждого.

"users.list" : {

  "caption":[
             {
               "datakey" : "ключ данных 1" 
              ,"mode"    : "режим 1" 
              ,"type"    : "тип данных-1" 
              }
             ,
              {
               "ignore" : 1
              }
             ,
              {
               "datakey" : "ключ данных 2" 
              ,"mode"    : "режим 2" 
              ,"type"    : "тип данных-2" 
              }
             ,
              {
               "dynamic" : 1
              }
             .......
            ]

 "rows" : [ -- в колонках данные любой структуры
     [ "+70000000000","Павел","Иванович",[1,2,3] ],
     [ "+70000000000","Алексей","Алексеевич",{ "a" : "B", "c" : "D" } ],
     ......
 ]

}

Пример задания XLSX

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

В виду двоичного содержимого файла, он не может быть приведён в документации и доступен по ссылке

https://sendsay.ru/api/sample.xlsx

Пример задания CSV

"users.list" : "member.cellphone,info.firstname,info.title,info.meddlename\n+70000000000,Павел,Иванович,Уважаемый\n+70000000000,Алексей,Алексеевич,Глубоко уважаемый\n......" 

вверх

Общие замечания

Ошибки, опечатки, не соответствия

В данном документе могут быть ошибки и опечатки. Реальное поведение API может иногда отличаться от описанного.

При доработке и изменении API мы оставляем обратную совместимость (на время или на всегда), но при исправлении ошибок вида "поведение API не соответствует документации" такого не происходит, так как исправляется явная ошибка.

Если реальное поведение API отличается от описанного здесь, то настоятельно рекомендуется не ориентироваться на "то что есть", а связаться с нами написав на ask@sendsay.ru сообщение о найденной проблеме.

Формат дробных чисел

Разделителем целой и дробной частей является точка

123.456

точность дробной части и её наличие зависит от источника числа.

Кастомизированные ссылки

Все ссылки указываемые в вызовах и предназначенные для получения по ним данных (списков, файлов, картинок..) могут содержать метки для подстановки элементов текущих даты и +/- сдвиг и для подстановки случайного числа.

Кастомизация случайном числом записывается в виде

{RND}

Результат - число из 19 цифр. Несколько RND в одном url дадут один и тот же результат.

Кастомизация временем записывается в виде

{DT.format +D day +h hour round +m minute round +s second}

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

Знак сдвига обязателен и может быть "+" (как в примере) или "-".

К названию величины можно добавлять на конце "s"

Формат вывода даты-времени задаваемый в format не должен содержать пробельных символов (запишите две подстановки {DT}), а следующие последовательности заменяются в нём на компоненты даты текущей даты в момент использования ссылки для получения данных +/- сдвиг.

YYYY - год (4 цифры)

MM -  месяц (2 цифры)
DD -  день (2 цифры)
hh -  час (2 цифры)
mm -  минута (2 цифры)
ss -  секунда (2 цифры)

M -  месяц (1 или 2 цифры)
D -  день (1 или 2 цифры)
h -  час (1 или 2 цифры)
m -  минута (1 или 2 цифры)
s -  секунда (1 или 2 цифры)

Основное назначение этой особенности - облегчение создания действий по расписанию. Например, вам требуется ежедневно импортировать данные, но файл с ними имеет в названии текущий месяц и день. Тогда параметр со ссылкой на данные будет выглядеть как "http://test.ru/a/b/file{DT.MMDD}.csv".

Примеры

{DT.YYYY-MM-DD_hh:mm:ss + 1 day} - завтрашняя дата с точностью до секунды. "2015-03-24_18:00:43"

{DT.YYYY-MM-DD + 1 day} {DT.YYYY-MM-DD + 1 day} - завтрашняя дата с точностью до секунды через пробел. "2015-03-24 18:00:43"

{DT.DDMMYYYY - 2 days} - позавчерашняя дата с точностью до дня записанная слитно."22032015"

Указание на количество попыток получения данных

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

Создавать действие с несколькими запусками в этом случае не верно - после появления данных запланированное действие выполнится несколько раз, а не один.

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

Это работает во всех случаях когда в api-вызове требуется указать адрес для получения данных.

Система будет пытаться получить данные пока не наступит одно из событий

- Данные будут получены (ответ http с кодом 200)

- Закончатся все попытки

- Закончится день в который было сделан api-вызов

Если ссылка содержит кастомизацию временем с помощью {DT} или случайным числом {RND}, то кастомизация будет вычисляться каждый раз заново при каждой новой попытке.

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

{
 "uri" : "cсылка на данные" 

,"retrys" : "количество попыток" -- число от 2 до 288

,"retrys.delay" : "секунд между попытками" -- число от 60 до 3600

}

Например для импорта

{
 "action" : "member.import" 

.......

 ,"users.url" : {
                 "uri" : "http://test.ru/data{DT.YYYYMMDD}.csv" 

                ,"retrys" : 100

                ,"retrys.delay" : 300
                }

.......

}

Особенности работы сравнений в фильтрах.

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

Для строк при сравнении null или отсутствующее значение считается равным "".

Для чисел пустое, null или отсутствующее значение считается равным 0.

Для даты пустое, null или отсутствующее значение считается равным "0001-01-01" и обычным образом укорачивается до необходимой точности если надо.

Для даты-времени пустое или отсутствующее значение считается равным "0001-01-01 00:00:00" и обычным образом укорачивается до необходимой точности если надо.

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

Например, у вас есть подписчик А со значением параметра dt равным "2023-12-01" и подписчик Б со значением параметра dt равным "2023-09-01"

Как и ожидается, в группу 1 с фильтром "dt < '2023-10-15" подпадёт только подписчик Б

А в группу 2 с фильтром "dt >= '2023-10-15" попадёт только подписчик А

И при использовании групп 1 и 2 в других группах с условием отрицания вхождения (!in_group) всё тоже будет ожидаемо.

Под условие !in_group(1) попадёт А, а под условие !in_group(2) попадёт Б.

Теперь добавим подписчика Ё c пустым значением dt.

Если не приравнивать пустую дату к 0001-01-00, то он не будет подходить под условия ни группы 1 ни группы 2.

Но будет подходить и под !in_group(1) и под !in_group(2) обе одновременно.

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

В целом, вопрос не однозначный и зависит от бизнес-логики клиента.

Мы приняли решение как описано выше - пусто, null и отсутствие это некое значение - которое в 99.9999% устраивает всех клиентов.

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

JSON и числа

Обратите внимание, что если указывать числа (целые и дробные) именно как числа (а не как стоки - в кавычках), то надо помнить о строгости JSON в этом вопросе

Пользовательские метки объектов

Часть типов объектов (в перспективе все типы) позволяют присвоить каждому их экземпляру пару чисел для отметки их в пользовательских целях.

Такие объекты имеют параметры reltype (предполагается что это значение хранит некий обобщённый тип классификации) и relref (указатель на что-то внутри классификации).

По умолчанию при создании оба поля получают значения равные 0.

У обоих полей все отрицательные значения зарезервированы для системных нужд. Используйте для себя положительные значения во избежание конфликтов.

Тестирование с локальными адресами

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

К таким адресам относятся, например:

     10.0.0.0        -   10.255.255.255  (10/8 prefix)
     127.0.0.0       -   127.255.255.255 (127/8 prefix)
     172.16.0.0      -   172.31.255.255  (172.16/12 prefix)
     192.168.0.0     -   192.168.255.255 (192.168/16 prefix)

С полным списком можно ознакомиться в RFC 5735 http://tools.ietf.org/html/rfc5735

вверх

Бухгалтерские документы

Для получения списка доступных связанных с них бухгалтерских документов используйте объект pase.doc.* вызова stat.uni

Для получения самих документов - вызовы rfs.list/rfs.file.get или соответствующий раздел веб-интерфейса.

Ближайшие не совместимые изменения

01 июля 2022 - удаление устаревших вызовов stat.activity, stat.issue, stat.group.portrait, stat.group.common.

История изменений

0.232     2024-02-17      * НОВОЕ ! Остановка асинхронного действий через вызов track.set
                          * Событие триггеров "Доставка письма" 
                          * Новые параметры вызова member.list.count : addr_type и member.haslock
                          * Новое значение в отчёте импорта - ignored - количество проигнорированных строк
                          * Правильное название значений в ответе group.list - create.time и update.time
                          * Ссылки на t.me не перелинковываются при выпуске Телеграмм

0.231     2024-01-17      * Новые коды ошибок доставки Телеграм для более подробного описания причин недоставки
                          * Статистика по типам голов теперь возвращается всегда при member.list.count
                          * Новый параметр вызова issue.send : basedraft.id
                          * Унификация поведения member.list и member.list.count при работе для всех и
                            для непосредственно заданного фильтра - возворащаются адреса любых типов.
                          * Описание особенности работы условий фильтров если сравниваемое значение в анкете пусто
                            или отсутствует

0.230     2023-12-17      * Боты Telegram
                            - новый параметр reply_disabled
                            - автозаполнение proxy.url при регистрации бота
                          * Обновление описание DKIM-ключей
                            - новый раздел "Кастомизация выпуска" 
                            - новый вызов issue.dkim.set
                            - новое описание issue.dkim.create|get
                          * Параметр выпуска dkim.id применим и не для email-выпусков
                          * Универсальная Статистика - забытое описание параметров issue.hourly.deliv|deliv_ok|deliv_bad
                          * Новые параметры sys.setting.set|get: callback.url.*

0.229     2023-11-17      * Поддрежка TLSv1.3 для https
                          * Поддержка GOST2012/GOST8912 для https
                          * Новые поля для универсальной статистики по тригерам: sequence.progress.addr_type sequence.progress.dt_step
                          * Возможность удалять из пользовательского стоп-листа в пределах небольшого месячного лимимта

0.228     2023-10-17      * Новое тригерное событие "Время сейчас" 
                          * В списке подписчиков при использовании ключей данных
                            - answers : decode - теперь тоже работает как и для АВО
                            - answers : unroll - новая возможность форматирования ответа
                            - caption : name - теперь тоже работает как и для АВО

0.227     2023-09-17      * НОВОЕ ! Обогащение данных подписчиков
                          * Новые параметры в ответе group.filter.get для оценки сложности фильтра: usage,depth,max_usage
                          * member.sendconfirm параметр unsubsendercancel это 0|1, а не адрес

0.226     2023-08-17      * НОВОЕ ! Рассылки VK Notify
                          * Новое действие send.confrim в триггерах - выслать письмо подтверждения

0.225     2023-07-17      * НОВОЕ ! Подробный лог действий с формами stat.uni formfilling.*
                          * Переработанное описание как транзакционные письма группируются в выпуски
                          * Новый параметр лент новостей - время последнего использования lenta.list/get : issued
                          * Новые параметры sys.setting.get: issue.personal.groupby invite.month.limit invite.month.rest

0.224     2023-06-17      * НОВОЕ ! Получение ответа на письмо рассылки через callback
                          * НОВОЕ ! Указание нескольких групп для внесения/исключения при импорте
                          * НОВОЕ ! Фильтр исключения из последовательности не связанный с группой-фильтром
                          * Возможность самостоятельно указывать защитный интервал от повторов важных вызовов
                          * При удалении формы удаление связанной анкеты
                          * Ответ 406 при передаче callback считается успехом
                          * Уточнение асинхронности для вызовов sequence.member.*
                          * Уточнение асинхронности для вызовов stoplist..*

0.223     2023-05-17      * НОВОЕ ! Тригерное событие "random" 
                          * НОВОЕ ! Возможность про импорте в явном указать на какие триггерные последовательни запустить участников импорта, а по каким остановить их прохождение
                          * Група не допуска на триггерную последовательность теперь может быть не только группой-списком, но и группой-фильтром
                          * Возможность при удалении из стоп-листа указать "сразу и из глобального и из всех по отправителю" 

0.222     2023-04-17      * НОВОЕ ! Свойство protected - Защита объектов от непренамеренного изменения/удаления
                          * НОВОЕ ! Для создания чат-ботов - события от Telegram в триггерах
                          * Количество меток выпуска увеличено до 10
                          * Уточнение описания проксирования команд для Telegram

0.221     2023-03-17      * В issue.send описание нового способа выпуска с помощью третьих лиц без разглашения им адресов
                          * Новый параметр импорта member.import: no_member для поддержания нового способа выпуска
                          * В результатах пробного импорта member.import.probe дополнительная полезная информация про колонки - quest.type/subtype/width
                          * Параметры управления перелинковкой ссылок переименованы из x-* в data-*, прежние варианты продолжают работать
                          * Новый справочный параметр "start.winner.at" для вариантов АБ-тестирования

0.220     2023-02-17      * НОВОЕ ! Автоматическая отмена письма с не правильной персонализацией
                            Параметр care_vars у выпуска и черновика позволяет указать какие переменные персонализации не должны быть пустыми.
                          * Интеграция с Тильдой теперь позволяет работать с формами с любым названием
                          * В трекере импорта теперь доступен изначальный запрос api

0.219     2023-01-17      * НОВОЕ ! Правила выполнения команд Telegram
                          * НОВОЕ ! Проксирование вызовов от Telegram
                          * НОВОЕ ! Inline-клавиатура в сообщениях Telegram
                          * НОВОЕ ! Reply-клавиатура в сообщениях Telegram
                          * НОВОЕ ! Запрос номер телефона в сообщениях Telegram
                          * НОВОЕ ! Запрос гео-положения в сообщениях Telegram

0.218     2022-12-17      * В события от формы код формы теперь не обязателен, но тогда обязателен источник
                          * Вызов переименования файлов rfs.rename поддерживает до 100 переименований за раз
                          * Вызов удаления файлов rfs.file.delete поддерживает до 100 удалений за раз
                          * Вызов удаления каталогов rfs.dir.delete поддерживает до 100 удалений за раз
                          * Уточнение описания label для Источников
                          * Исправление описания token для Внешней авторизации с типом 0

0.217     2022-11-17      * НОВЫЙ способ получения данных callback/webhook - сохранение в Отчёты
                          * НОВАЯ сущность "Источник" - вызовы origin.*
                          * Формы поддерживают назначение Источника
                            - параметр origin  вызовах form.*
                            - form.origin.* в Универсальной статистике
                            - origin.id в callback
                            - origin.id в триггерных событиях форм
                          * Новый код недоставки sms:  -2013 blacklisted

0.216     2022-10-17      * Уточнение описания null для read.duration
                          * Уточнение описания datakey autotedect для Импорта подписчиков
                          * Уточнение, что по умолчанию для высылки результата на почту используется адрес владельца аккаунта
                          * Уточнение описание ошибки cannot_import

0.215     2022-09-17      * НОВОЕ. Возможность ограничить срок действия логина sys.user.* - not_before/not_after
                          * НОВОЕ. Управление интервалом накопления для выпусков с накоплением - месяц, неделя, день
                            - issue.send - accumulate_by
                            - issue.draft.* - accumulate_by и accumulate
                            - sys.settings.* - issue.accumulate_by
                          * Новый параметр конфигурации sys.settings.get - issue.limit.join
                          * Новый параметр скрипта отслеживания пути по сайту target.script: keeputm
                          * Уточнение, что content-type для вызовов callback в формате json-stream это application/x-ndjson

0.214     2022-08-17      * Новые данные в Универсальной Статистике: member.confirm.time/host
                          * Новый код недоставки sms: -2012 wrong recepient phone number
                          * Новый параметр member.list: answers
                          * Новый параметр group.filter.get: with_check

0.213     2022-07-17      * Триггерные события member.change могут предавать информацию об изменении в выпускаемое письмо
                          * Расширенное сравнение данные в триггерном событии member.change
                          * Универсальная статистика - member.(create|update|import)* могут быть null

0.212     2022-06-17      * Возможность импортировать уже имеющиеся контакты Телеграм
                          * В member.set/member.import возможно вносить дату как текущее время со сдвигом
                          * В member.list.count новый параметр with_minmax

0.211     2022-05-17      * Новые счётчики в статистике импорта: needconfirm, datarows, upserted, uniqs
                          * Новый код завершения прохождения триггера: -9 - с шага не уйти
                          * Новый параметр снимка группы - sequence.event
                          * Новые параметры в трекерах большинства вызовов работающих со списками подписчиков: records, method, filter, url, query
                          * Новые параметры issue.get: letter, with_archive, with_name
                          * Новый параметр issue.get.attach: letter
                          * Правильное название параметров group.get - create.time и update.time

0.210     2022-04-17      * Возможность не подтверждать адрес отправителя если он совпадает с доменом DKIM выпуска
                          * Новый параметр черновика basegroup.id
                          * Новые параметры выпуска ignore_stoplist, unsub_list, issue_memeber_list
                          * Новый статус недоставки -100023 - адрес в спам-листе
                          * Вызов group.snapshot поддерживает параметр addr_type
                          * Единообразное название параметров фильтра и объекта create|update.time для вызовов cron.list, group.list, issue.draft.list, issue.class.list
                          * Исправленное описание параметра weak_draft вызова issue.send
                          * Уточнение какие параметры имеют целочисленный тип в колбеках

0.209     2022-03-17      * Возможность глобально отключить проверку ссылок при выпуске: sys.settings.get параметр issue.link.notest
                          * Устаревшие вызовы stat.activity, stat.issue, stat.group.portrait, stat.group.common будут удалены 01 июля 2022

0.208     2022-02-17      * Удаление саблогина имеющего api-ключ не возможно без предварительного удаления api-ключа
                          * Новые параметры Универсальной Статистики для выпуск issue.*_rate - заранее рассчитанные
                            стандартные статистические показатели
                          * Новый способ указания обработки ошибок персональный аттачей - "!?" - не ошибка если url пустой
                          * Триггеры / Событийные действия
                            - Новое свойство триггера "тип адресов" - addr_type
                            - Новое способ работы действия member.update - через datakey
                          * Callback/Webhook
                            - для form новая причина вызова - "Подтверждение формы" - event.type = confirm
                            - для draft добавлены variables - используемые в черновике переменные
                            - для read добавлен duration - длительность чтения
                            - для read и clik добавлены geo.id и geo.name - код и название географии
                            - для issue добавлено dkim.id
                            - для track добавлено track.id в более удобном месте
                            - уточнены статусы null для многих полей
                          * Исправлено описание issue.draft.get - variables возвращается в корне ответа
                          * Явно описан формат чисел с плавающей точкой
                          * Уточнено использование в Экспресс-Выпуске параметров issue_exclude и include_filter

0.207     2022-01-17      * Возможность указать предпочитаемый час по умолчанию при выпуске в наилучшее время
                          * Возможность указать предпочитаемый час по умолчанию при выпуске с учётом поясного времени
                          * Новый фильтра списка в sequence.list
                          * Возможность получить фильтр группы с развёрнутыми условиями in_group: group.filter.get - expand
                          * Новые статусы доставки -100020, -100021, -100022
                          * Возможность шаблонизировать url для callback - SS_EVENT_TYPE

0.206     2021-12-17      * Триггерные события member.change и memeber.match теперь понимают произвольный фильтр в параметрах was.cond,new.cond, match.cond
                          * Новый тип Внешней Авторизации - telegram
                          * Расширенная информация об ошибке в параметр еrror.info трекера выпуска
                          * Вызов ручного выпуска победителя сплит тестирования теперь возвращает трекер выпуска
                          * Новые объект custid.* - Клиентские метки письма - в Универсальной статистике
                          * Проверка ссылок теперь игнорирует домены apps.apple.com itunes.apple.com play.google.com
                          * Проверка ссылок теперь считает успешными кода ответа 307 и 308
                          * Исправление описания group.filter.get - фильтр возвращается в ключе obj

0.205     2021-11-17      * Новые параметры при получении фильтра группы group.filter.get: in_group и update.time
                          * Подсчёт количества участников member.list.count: возможность указать фильтра прямо в вызове
                          * Метка отписки unsub.label - произвольный параметр для собственной классификации отписок
                          * Webhook/Callback - новый параметр issue.login

0.204     2021-10-17      * Триггерные последовательности - список исключения - те кто не попадут на последовательность
                          * track.info - передача произвольной информации в трекер для последующего использования

0.203     2021-09-17      * Новый формат json-stream передачи событий для Callback/Webhook
                          * Триггерное событие "Всегда" заменено событием "Иначе" 
                          * Уточнено поведение параметра limit.usage.raw для тиражей транзакционных писем
                          * Описаны новые проверки при изменении шагов триггерных событий

0.202     2021-08-17      * НОВОЕ ! Параметр next в вариантах Триггерных Действий позволяет организовать схемы исполнения любой сложности
                          * НОВОЕ ! Параметр выпуска weak_draft для возможности переопределить при выпуске параметры содержимого черновика
                          * НОВОЕ ! Возможно использование фильтра have Универсальной Статистики в фильтрах групп подписчиков.
                          * НОВОЕ ! Вызов "Выслать письмо подтверждения" теперь можно использовать и для отправки ссылок самостоятельного удаления из стоп-листа
                          * Новое событие "Всегда" для Триггерных действий
                          * Параметр info Триггерных действий для хранения сопутствующей информации
                          * Параметры issue.split.id/variant/winner в Трекера выпуска рассылки для более полной информации о ней
                          * Параметр issue.weak_draft в Параметрах Системы для управления настройкой "Слабый черновик" для всего аккаунта

0.201     2021-07-17      * НОВЫЕ триггерные события unsub.all, unsub.sender, unsub.topic
                          * Вместо события member.unsubscribe используйте unsub.all
                          * НОВЫЙ параметр customer.id для стандартизации параметра клиентской идентификации письма

0.200     2021-06-17      * НОВОЕ. Системные настройки (sys.settings.get) limit.ratio и limit.usage.raw для получения значение описывающих
                            текущие лимиты тиражей и адресов и их использование.
                          * НОВОЕ. Вызов изменения данных подписчика без автосоздания (if_exists : must)
                          * НОВОЕ. Импорт подписчиков без автосоздания (if_exists : must)
                          * Новое свойства выпуска issue.feature_fast
                          * Изменить время отложенного выпуска на уже прошедшее время не возможно, используйте "now" для выпуска "сейчас" 
                          * Уточнение описания настройки issue.month.limit.excess

0.199     2021-05-17      * НОВОЕ ! Рассылки через Telegram
                          * Возможность использовать cron.runonce для тестового/внеочередного импорта YML
                          * Новые свойства выпуска issue.feature_csid и issue.feature_pte

0.198     2021-04-17      * НОВОЕ ! Транзакционные письма для ВКонтакте: issue.send basegroup.id
                          * issue.dkim.list - возможность использования фильтрации
                          * Получить данные подписчика member.get: уточнение поведения параметра missing_too

0.197     2021-03-17      * НОВОЕ ! Аутентификация с помощью JWT
                          * НОВОЕ ! История операций с головами подписчика stat.uni:niptuck
                          * Возможность использовать канал с шифрованием ГОСТ
                          * Уточнение что время отложенного выпуска не должно быть в прошлом

0.196     2021-02-17      * НЕ СОВМЕСТИМОЕ ИЗМЕНЕНИЕ- Протоколы TLSv1/TLSv1.1 будут отключены 15 апреля 2021 года
                          * Новый параметр Универсальной статистики unsub.reason - текст причины, написанный подписчиком при отписке
                          * А/Б-тестирование. Новое состояние -1 варианта тестирования для сигнализации об ошибке при выпуске
                          * Список трекеров для вызова issue.send не возвращает letter->message. Используйте track.get
                          * Добавлено забытое описание параметра about.user.name в получении настроек

0.195     2021-01-17      * НОВОЕ ! Экспресс-Импорт - импортируйте сотни тысяч подписчиков за пару минут
                          * Универсальная Статистика (stat.uni) - новый объект email.*
                          * Вызов sys.use.get - параметр id не обязателен

вверх