Перейти к содержанию

Подписчики

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

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

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

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

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

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

Набор данных, связанный с идентификатором, тоже имеет свой номер - member.dataset. Но этот номер не постоянен. Если вы создали, удалили и опять создали подписчика с одним и тем же идентификатором, то его системный номер member.id будет в обоих случаях один и тот же, а номер набора данных будет другой. Номер набора данных напрямую не требуется для работы и доступен только для того, чтобы: - можно было сравнить эти номера у двух разных подписчиков и таким образом определить, не объединены ли они в одного (подраздел "Мультиканальность" нижe) - использовать его в вызове member.get для чтения разом всех подписчиков, связанных с данным набором данных (например, для отображения "Карточки подписчика)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ещё один параметр индивидуальный для каждой головы - Источник создания - member.origin

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Системная анкета 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) набора данных при импорте
                       }

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

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

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

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

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

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

           ,"origin" : 2             -- номер Источника указаный при создании головы или изменении головы.

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

           ,"mobile_os" : "android | ios| huawei" -- операционная система подписчика для pushapp. для остальных - пусто
                                                  -- в фильтрах должны использоваться только сравнение == и !=

           ,"mobile_app" : номер authext -- номер внешней аутентификации под которой зарегистрирован подписчик pushapp
                                         -- каждому мобильному приложению создаётся своя внешняя аутентификация
                                         -- для не pushapp - пусто
                                         -- в фильтрах должны использоваться только сравнения  == и !=

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

           ,"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., member.max.* в 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|pushapp|max|id -- тип идентификатора. Не обязательно, система сама распознает email или msisdn
}

ответ

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

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

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

}

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

Одна и та же строка символов может быть использована как идентификатор подписчика для разных типов идентификаторов. Также усложняет жизнь то, что идентификаторы типа 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" -- нормализованный
           }

           ......

          ]
}

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

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

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

Ещё раз прочтите раздел "Последовательность обработки" - два конкурентных запроса для одного и того же "тела" могут дать не тот результат что ожидается. Значительно снизить вероятность "неожиданного результата" можно запроси в Службе Поддержки включение настройки "Версионирование member.set" - вместо "успеха" один из конкурирующих запросов будет заканчиваться с ошибкой error/member/versioning и будет ясно, что его надо повторить. Это НЕ 100% защита, но версионирование сначительно снижает уровень "неожиданности". Единственное 100% решение - серилизация на вашей стороне.

{
  "action" : "member.set" 

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

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|pushapp|max|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" ]
 ,[ "ключ-данных-2", "режим-2", "новое-значение-2", "тип-2" ]

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

 ,[ "ключ-данных-5", "delete" ]

 ........
]

."copy_email" : "идентификатор подписчика" -- не обязательно, не совеместимо с copy_data
                                           -- подписчик данные которого будут использоваться в *.copy

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

,"copy_missing" : "error | do | skip" -- отсутствие в базе copy_email
                         -- error - по умолчанию - ошибка вызова
                         -- do    - изменение ключей данных по командам *.copy будет записывать значение undef
                         -- skip  - изменение ключей данных по командам *.copy выполнятся не будет - сохранятся прежние значения

,"copy_data" :  { -- не обязательно, не совеместимо с copy_email
                 ......
                 -- данные для копирования
                 -- позволяет иметь неизменный параметр datakey вынеся сюда сами данные
                  ......
                }

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

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

--     "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", "merge_copy", "merge_update.copy", "merge_insert.copy", "push.copy", "unshift.copy" - работают аналогично
-- режимам без .copy, но значением является то, что хранится в указанном в третьем элементе "ключе-данных-из".
--
-- источником данных для копирования является подписчик указанный в copy_email или непосредственно данные указаные в copy_data или сам же подписчик указанный в email если ни одного 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), и в выражении (current:Yh), то используется точность из выражения current
--
--  если несколько ключей изменяются с использованием выражения current, то они все вычисляются от какого-то одного и того же времени, запомненного в начале вызова
--
--  при импортировании все вычисления current для всех строк импорта также используют какое-то одно и то же время, запомненное в начале импорта
--

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

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

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

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

-- для работы с Данными прохождения триггера используйте @member.sequence_data@ для чтения и @member.sequence_data.store@ для записи

-- для работы с Источником создания используйте member.origin - можно указать как номер так и метку Источника

-- все прочие изменения по ключам данных "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|pushapp|max|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" => "состояние удаления" 

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

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

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

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

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

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

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

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

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

                         }
          }

}

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

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

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

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

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

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

{

  "action" : "member.set" 

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

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

или

 ,"list" : [

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

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

           ]

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

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

 ,"head_addr_type": email|msisdn|viber|csid|push|vk|tg|vknotify|pushapp|max|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" : ....
}

ответ

{

 <общие поля>

 ,"obj"  { ... } -- объект в формате member.get если "return_fresh_obj" : 1

}

Список идентификаторов

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

{

 "action" : "member.head.list" 

 ,"email" : "один из идентификаторов существующего пользователя" 

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

}

ответ

{

 <общие поля>

,"list" : [

            {

             "email" : "идентификатор пользователя" -- email, номер телефона,....

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

            ,"dataset" : "номер тела" 

            -- структуры состояния головы как описано в member.get

            ,"has_lock" : ...

            ,"lockerror" : ...

            ,"confirm" : { ... }

            ,"error" : { ... }

            }

            ...

           ]

}

Присоединение идентификатора

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

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

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

{

 "action" : "member.head.attach" 

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

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

 ,"head" : "присоединяемый идентификатор" 

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

 ,"head_origin" : "номер или метка Источника для member.origin" -- не обязательно

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

ответ

{

 <общие поля>

}

Удаление идентификатора

При split= 0

*Идентификатор, указанный в email, удаляется от пользователя и становится более ни с кем не связан.

*Также прекращается участие указанного идентификатора в группах-списках, в которых он состоял.

При split = 1

*Идентификатор, указанный в email, удаляется от пользователя и становится самостоятельным пользователем.

*За ним остаётся участие в группах-списках, в которых он состоял.

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

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

После удаления у пользователя, от которого удаляется идентификатор, должен остаться ещё хотя бы один другой идентификатор (для удаления всего пользователя есть member.delete).

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

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

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

{

 "action" : "member.head.detach" 

 ,"email"  : "удаляемый один из идентификаторов существующего пользователя" 

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|pushapp|max|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|pushapp|max|id -- тип текущего идентификатора. не обязательно, система сама распознает email или msisdn

 ,"head" : "новый идентификатор" 

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

 ,"head_origin" : "номер или метка Источника для member.origin" -- не обязательно

 ,"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|pushapp|max|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, то подойдут адреса любых типов

-- для всех источников

 ,"addr_type": "тип выбираемых адресов подписчиков (email|msisdn|viber|csid|push|vk|tg|vknotify|pushapp|max)" 
                -- при отсутствии определяется по типу адресов в указанной группе
                -- если указан, то должен совпадать с типом адресов в группе, если и та указана
                -- если не указан и не указана группа, то выбираются адреса любого типа

 ,"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дрес" : {
                           список сработавших меток и их значений
                           В описании фильтра формата КД описано когда и чем это полезно
                        }
            }

}

Специальный фильтр по количеству голов у подписчика

  { -- ограничение по количеству голов. один раз
   "a"  : "size(member.heads)" 
   ,"op" : "<|<=|==|!=|>=|>" 
   ,"v"  : "количество голов" -- >= 1
  }

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

 { -- ограничение по типу голов. можно несколько раз
  "a"  : "member.addr_type" 
  ,"op" : "eq" 
  ,"v"  : "тип головы" -- email,msisdn,push,viber....
 }

Выборка других голов подписчика

С использованием указываемого вы вызове формата format.

{
 "action" : "member.list" 

,"addr_type" : "xxxx" -- обязательно, не "any" 

,"group|group.filter" : .... -- не обязательно

,"format" : [

             .......

             {
              'datakey' : "member.head.email" | "member.head.id" | "member.head.addr_type" | "member.head.origin" 

             ,"addr_type" : "xxxx" -- не обязательно. интересующий тип адресов
                                   -- все member.head.* или без addr_type или с addr_type

             ,"outer" : 0|1 -- не обязательно. по умолчанию 0
                            -- для одинаковых addr_type значения outer  должны быть одинаковы
                            -- возможность отсутствия такого типа головы у тела
                            -- 0 - должна быть
                            -- 1 - может не быть, будет выбрано null

             }

             .......

            ]

}

Примеры:

Выбрать головы типа email и к ним данные других голов для тел у которых есть головы email и какие-то другие головы (возможно тоже email)

{
 "action":"member.list" 

,"addr_type":"email" 

,"format":[
           { "datakey" : "member.email" }

          ,{ "datakey" : "member.head.id"        }
          ,{ "datakey" : "member.head.addr_type" }
          ,{ "datakey" : "member.head.email"     } 

          ]
}

Выбрать головы типа email и к ним данные других голов для тел у которых есть головы email

Если других голов не одна, то будет несколько строк в ответе - для всех сочетаний

{
 "action":"member.list" 

,"addr_type":"email" 

,"format":[
           { "datakey" : "member.email" }

          ,{ "datakey" : "member.head.id"          ,"outer" : 1 }
          ,{ "datakey" : "member.head.addr_type"   ,"outer" : 1 }
          ,{ "datakey" : "member.head.email"       ,"outer" : 1 } 

          ]
}

Выбрать головы типа email и к ним данные про головы pushapp того же тела когда у тела есть и email и pushapp

Если других голов не одна, то будет несколько строк в ответе - для всех сочетаний

{
 "action":"member.list" 

,"addr_type":"email" 

,"format":[
           { "datakey" : "member.email" }

          ,{ "datakey" : "member.head.id"         ,"addr_type" : "pushapp" }
          ,{ "datakey" : "member.head.addr_type"  ,"addr_type" : "pushapp" }
          ,{ "datakey" : "member.head.email"      ,"addr_type" : "pushapp" }

          ]
}

Выбрать головы типа email и к ним данные про головы pushapp того же тела когда у тела есть email и, возможно, pushapp

Если других голов не одна, то будет несколько строк в ответе - для всех сочетаний

{
 "action":"member.list" 

,"addr_type":"email" 

,"format":[
           { "datakey" : "member.email" }

          ,{ "datakey" : "member.head.id"         ,"addr_type" : "pushapp"  ,"outer" : 1 }
          ,{ "datakey" : "member.head.addr_type"  ,"addr_type" : "pushapp"  ,"outer" : 1 }
          ,{ "datakey" : "member.head.email"      ,"addr_type" : "pushapp"  ,"outer" : 1 } 

          ]
}

Выбрать головы типа email и к ним данные про головы pushapp того же тела и про головы umid того же тела когда у тела есть и email и pushapp и umid

Если голова pushapp одна и umid одна, то в ответе одна строка

Если чего-то не одна, то в ответе все сочетания разных pushapp и umid

{
 "action":"member.list" 

,"addr_type":"email" 

,"format":[
           { "datakey" : "member.email" }

          ,{ "datakey" : "member.head.id"         ,"addr_type" : "pushapp" }
          ,{ "datakey" : "member.head.addr_type"  ,"addr_type" : "pushapp" }
          ,{ "datakey" : "member.head.email"      ,"addr_type" : "pushapp" }

          ,{ "datakey" : "member.head.id"         ,"addr_type" : "umid" }
          ,{ "datakey" : "member.head.addr_type"  ,"addr_type" : "umid" }
          ,{ "datakey" : "member.head.email"      ,"addr_type" : "umid" }

          ]
}

Количество подписчиков

Расчёт быстр, если ведётся по "всем", по группам-спискам, по группам-фильтрам, состоящим из групп-списков.

Расчёт по группе-фильтру может быть долог - в зависимости от сложности фильтра и общего числа подписчиков.

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

Для групп-сообществ ВК ответ немного другой для получения дополнительной информации про сообщество.

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

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

{

  "action" : "member.list.count" 

-- источник адресов, одно из двух или вообще ничего (тогда по всем адресам вообще)

 ,"group" : "идентификатор группы" 

-- или

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

-- для всех источников

 ,"addr_type": "тип выбираемых адресов подписчиков (email|msisdn|viber|csid|push|vk|tg|vknotify|pushapp|max)" 
                -- при отсутствии определяется по типу адресов в указанной группе
                -- если указан, то должен совпадать с типом адресов в группе, если и та указана
                -- если не указан и не указана группа, то выбираются адреса любого типа

 ,"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.pushapp" : всего именно pushapp-подписок

           ,"total.max" : всего именно max-подписок

           ,"total.vknotify" : всего именно vknotify-подписок

           ,"active.email" : способных получать именно адреса

           ,"active.msisdn" : способных получать именно телефоны

           ,"active.viber" : способных получать именно viber-номера

           ,"active.push" : способных получать именно push-подписки

           ,"active.tg" : способных получать именно tg-подписки

           ,"active.vknotify" : способных получать именно vknotify-подписки

           ,"active.pushapp" : способных получать именно pushapp-подписки

           ,"active.max" : способных получать именно max-подписки

           -- минимальный и максимальный номер подписчика
           -- при 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.tg" : число | null
           ,"id_min.vknotify" : число | null
           ,"id_min.pushapp" : число | null
           ,"id_min.max" : число | 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
           ,"id_max.pushapp" : число | null
           ,"id_max.max" : число | 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предназначается для проверки того, что думает система импорта о ваших входных данных.

В ответ будет сообщено, как произошло авто-определение кодировки (charset), разделителя столбцов, определилась ли первая строка данных как строка конфигурации (firstline), и каким ответом каких анкет приписаны столбцы данных.

Если вы знаете заранее ответы на эти вопросы, то можете сразу указать эти данные при вызове: тогда они будут иметь приоритет над авто-определением системы.

Вызовmember.importреально импортирует данные. Описанные выше параметры будет определены автоматически, если их не указать сразу при вызове.

Вызов member.import асинхронный- получение положительного ответа означает, что задание на импорт поставлено в очередь. Это не означает успешность импорта - он может не состоятся из ошибок, возникших позже. Следить за его исходом можно по возвращаемому номеру асинхронного запроса.

Обратите внимание:Все возможные проблемы (фатальные или нет - смотрите по cannot_import), связанные именно с импортом (настройки, данные) при вызове member.import.probe сообщаются только через параметр warnings. А параметр errors служит для сообщения о проблемах вызова только с точки зрения API. При вызове же самого импорта, не фатальные ошибки остаются по-прежнему в warnings, а фатальные переносятся в errors. Это может выглядеть не логично, но это так из-за назначения вызова member.import.probe - предупредить вас о возможных ошибках, а не выполнять само импортирование.

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

Вызовmember.importсохраняет отчёты о строчках, которые он не смог обработать. Имена файлов с отчётами можно получить из трекера.

Имеется два вида импорта:

Обычный

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

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

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

Импорт Телеграм

Это обычный импорт, но требуется:

  • указать тип адресовaddr_type : tg
  • указать обязательный параметрbasegroup.id-- код базовой группы телеграм-бота
  • в колонкеmember.tgуказывать номера пользователей в Телеграм (номера, не имена)
  • при желании использовать специальную колонку/statusдля указания как вносить подписчиков - колонка отсутствует или содержит1,activeили пусто - подписчик добавляется как активный, всё прочее - как отписанный

Экспресс-Импорт

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

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

Если по каким-то причинам для выпуска рассылок по реестру не используется Экспресс-выпуск, а работа ведётся по сценарию "Импортировать данные, а потом сделать по ним выпуск", то использование Экспресс-импорта вместо обычного импорта как раз то, что лучше всего подходит для этого.

Внесённые в Экспресс-импорте подписчики и их данные сохраняются в отдельную новую группу-список, всегда создаваемую автоматически. Её название и, при желании, код можно указать в задании Экспресс-импорта.

В дальнейшем, именно по этой группе надо выпускать рассылки.

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

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

Отличить её можно по параметру 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|pushapp|max)" 

  ,"origin" : "номер или метка Источника для member.origin" -- не обязательно
                                                             -- для заполнение member.origin новых подписчиков
                                                             -- если в данных импорта есть колонка member.origin, то она имеет приоритет
                                                             -- и этот параметр бесполезен

и одни из источников данных:

   ,"users.list": адреса и данные для импортирования -- непосредственно в JSON,  CSV или XSLX
                                                     -- подробнее в разделе "Форматы данных для импортирования и Экспресс-Выпуска" 

   ,"encoding" : "base64" -- не обязательно.
                          -- пусто или отсутствует - данные в users.list представлены непосредственно
                          -- base64                - данные в users.list закодированы в base64
или

   ,"users.url": "URL данные для импортирования" 

                  -- Данные забираются в момент вызова для member.import.probe и в момент начала импортирования для member.import

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

                  -- === Внешние ссылки http / https / ftp / ftps / sftp или из хранилища загрузок rfs://upload/путь-до-файла

                  -- По ссылке ожидается файл в котором находятся список адресов и данных для импортирования.

                  -- Но, если ссылка заканчивается на "/", то появляется возможно указать дополнительные параметры импорта

                  -- Сначала запрашивается json-файл для дополнительный параметров импорта URL/request.json
                  -- Его файла нет, то это ошибка

                  -- Потом запрашивается файл с данными для импорта URL/data.EXT, а если его не получить, то URL/import.EXT

                  -- где EXT это параметр content_type из request.json, а при его отсутствии - csv

                  -- === 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, используется далее вместо самих данных

** прочие параметры

  ,"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" 

                -- при addr_type = pushapp  колонка с клиентским идентификатором - это анкета "member" вопрос "pushapp" 

                -- при addr_type = max  колонка с клиентским идентификатором - это анкета "member" вопрос "max" 

                {

                 "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" 

                -- при addr_type = pushapp  колонка с клиентским идентификатором - это анкета "member" вопрос "pushapp" 

                -- при addr_type = max  колонка с клиентским идентификатором - это анкета "member" вопрос "max" 

                {

                 "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" : "под-тип вопроса" -- если применимо к type

                 "quest.width" : "длина поля"  -- если применимо к 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|pushapp|max|id -- тип идентификатора. не обязательно, система сама распознает email или msisdn

или

 ,"list" : [

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

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

            ........

           ]

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

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

ответ

{

  <общие поля>

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

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

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

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

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

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

}

Выслать письмо-подтверждение

Вызов позволяет отправить письма, в которых будут работать специальные команды шаблонизатора для подтверждения внесения в базу ([% param.url_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|pushapp|max|id -- тип идентификатора. Параметр необязателен, система сама распознает email или msisdn

или

 ,"list" : [

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

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

            ........

           ]

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|pushapp|max|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|pushapp|max|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|pushapp|max|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|pushapp|max|id -- тип идентификатора. Параметр необязателен, система сама распознает email или msisdn.

 ,"with_stoplist" : 2 -- параметр необязателен, 2 - в информации о стоп-листе перечислить записи по отправителям
                      -- при отсутствии или любом другом значении - прежний формат с записями только из глобального стоп-листа
}

ответ

{
 <общие поля>
   "obj" : {

-- информация об адресе по структуре аналогичная ключу member вызова member.get

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

         "addr_type" : "email|msisdn|viber|csid|push|vk|tg|vknotify|pushapp|max" -- тип адреса
         "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|pushapp|max|id -- тип идентификаторов. Параметр необязателен, система сама распознает email или msisdn

или

 ,"list" : [

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

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

            ........

           ]

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|pushapp|max|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|pushapp|max|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|pushapp|max|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|pushapp|max|id -- тип идентификатора. Параметр необязателен, система сама распознает email или msisdn

или

 ,"list" : [

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

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

            ........

           ]

 ,"addr_type" : email|msisdn|viber|csid|push|vk|tg|vknotify|pushapp|max|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|pushapp|max|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|pushapp|max|id -- тип идентификаторов. Параметр необязателен, система сама распознает email или msisdn

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

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

ответ

{

    <общие поля>

   "list" : { -- ключ - оригинальное значение адреса из запроса

             "email1" :  результат -- null - адрес синтаксически не верен
                                    -- 1 - ошибки доставки удалены, подписчик разблокирован (если был заблокирован)
                                    -- 0 - не было ошибок доставки
            ,"email2" : ...

         }

}