

Добрый день, коллеги!
Я хочу рассказать вам как можно расширить возможности модуля Email-маркетинг.
Статья состоит из нескольких частей:
- Общая схема работы модуля.
- Источники адресов, коннекторы
- Шаблоны
- Почтовые блоки
- Кастомизация формы отписки.
[spoiler]
1. Общая схема работы модуля
- Одна рассылка может содержать множество выпусков.
- Для рассылки задается список групп адресов.
- Группа получателей содержит набор коннекторов к источникам адресов
- Выпуск имеет шаблон и расписание.
Этот набор получается из всех коннекторов, которые перечислены в группах адресов.
Если выпуск периодический, то перед отправкой набор адресов получателей обновляется.
Таким образом, если раз в неделю выполняется отправка новостей оформившим заказ на сайте, то новые покупатели с момента последней отправки также попадут в получатели.
Если выпуск отправляется разово в определенное время, то список получателей также обновится перед отправкой.
2. Источники адресов, коннекторы
Одной из отличительных черт модуля в том, что не нужно вручную брать список адресов и добавлять его в модуль.
Модуль может самостоятельно выбирать адреса по произвольным критериям из разных источников.
Для этого используется коннектор к источнику.
Пользователи сайта, инфоблоки, веб-формы, файл или сторонний сайт - это источники адресов.
Но модуль не знает в каком инфоблоке и в каком поле инфоблока хранится адрес.
Для этого используется коннектор, который реализует общий интерфейс для доступа к адресам из источника.
Теперь, разберем как делать свой коннектор к источнику адресов.
1) Допустим, у нас задача отправлять поздравительные письма зарегистрированным пользователям в их день рождения.
Для начала нам нужен коннектор, который будет выбирать пользователей сайта с заполненными датами рождения.
Создадим файл: my_sender_connector.php
в папке: bitrix/php_interface
В него добавим класс: SenderConnectorUserBirthday - который расширяет базовый класс: \Bitrix\Sender\Connector
| <?php
class SenderConnectorUserBirthday extends \Bitrix\Sender\Connector
{
    public function getName()
    {
     return 'Пользователи - день рождения';
    }
    public function getCode()
    {
     return "my_user_birthday";
    }
    /** @return \CDBResult */
    public function getData()
    {
     $currentDate = new \Bitrix\Main\Type\Date();
     $filter = array(
      "PERSONAL_BIRTHDAY_DATE" => $currentDate->format("m-d"),
     );
     $resultDb = CUser::GetList(($by="ID"), ($order="asc"), $filter);
     return $resultDb;
    }
    public function getForm()
    {
     return "Все пользователи, у которых день рождения на момент рассылки";
    }
}  | 
- getName - название источника адресов. Оно будет выводиться в админке.
- getCode - служебная функция, нужно указывать уникальный код.
- getData - функция, возвращающая адреса. Как видим, в ней выбираются пользователи, у которых день рождения на текущую дату. Должна возвращать объект CDBResult
- getForm - возвращает форму настройки коннектора. Форма выводится при создании группы адресов в админке.
Для этого в файле bitrix/php_interface/init.php добавим такой код:
| AddEventHandler("sender", "OnConnectorList", array("MyEventHandler","senderConnectorHandler"));
class MyEventHandler
{
    public static function senderConnectorHandler($arData)
    {
     $arAutoLoadClasses = array(
      'SenderConnectorUserBirthday' => '/bitrix/php_interface/my_sender_connector.php'
     );
     \Bitrix\Main\Loader::registerAutoLoadClasses(null, $arAutoLoadClasses);
     $arData['CONNECTOR'] = 'SenderConnectorUserBirthday';
     return $arData;
    }
}  | 
В этом обработчике события возвращается поступивший параметр с указанием названия класса коннектора:
| $arData['CONNECTOR'] = 'SenderConnectorUserBirthday'; | 
Идем в админку, на страницу "Рабочий стол -> Сервисы -> Email-маркетинг -> Группы адресов"
Переходим к созданию/редактированию группы.
В списке появился наш коннектор "Пользователи - день рождения":

Добавив в группу адресов данный коннектор, мы можем использовать эту группу адресов в рассылках.
Затем создадим выпуск, которому укажем периодическую отправку каждый день.
Готово. Раз в день будет выполняться выпуск рассылки, выбирая все адреса пользователей, у которых день рождения на день запуска.
2) Теперь посмотрим, как можно сделать интерфейсные настройки.
Функция getForm может возвращать html с элементами формы и javascript
Но, чтобы элементы формы были обработаны, необходимо писать вместо:
| $s = '<input type="text" name="NAME">'; | 
| $s = '<input type="text" name="'.$this->getFieldName('NAME').'">';  | 
Вместо:
| $s = '<input type="text" id="NAME">'; | 
| $s = '<input type="text" id="'.$this->getFieldId('NAME').'">';  | 
Вместо:
| $s = '<input type="text" name="NAME" value="">'; | 
| $s = '<input type="text" name="'.$this->getFieldName('NAME').'" value="'.$this->getFieldValue('NAME', '').'">';  | 
Допустим, в нашем коннекторе нам потребовалась возможность использовать адреса не только тех, у кого день рождения на текущую дату.
Нужна возможность выбрать:
- либо на текущую дату;
- либо за 3 дня до наступления;
- либо возможность пользователю админки указать произвольное количество дней до наступления дня рождения.
| <sc ript>
    function myShowBirthdayDayField(ctrl, hiddenCtrl)
    {
     if(ctrl.value=="CUSTOM")
         BX(hiddenCtrl).style.display = "";
     else
         BX(hiddenCtrl).style.display = "none";
    }
</sc ript>
<table>
    <tr>
     <td>Дата рождения:</td>
     <td>
      <select name="BIRTHDAY" 
          on change="myShowBirthdayDayField(this, 'DAYS');">
          <option value="">Сегодня</option>
          <option value="3">За три дня</option>
          <option value="CUSTOM">Указать за сколько дней</option>
      </select>
      <input 
          type="text" 
          name="DAYS" 
          id="DAYS" 
          value="" 
          style="display: none;"
      >
     </td>
    </tr>
</table>  | 
Тогда сама форма после использования getFieldId, getFieldsName, GetFieldValue будет выглядеть так:
| public function getForm()
{
   return '
     <sc ript>
     function myShowField(ctrl, hiddenCtrl)
     {
      if(ctrl.value=="CUSTOM")
          BX(hiddenCtrl).style.display = "";
      else
          BX(hiddenCtrl).style.display = "none";
     }
     </sc ript>
     <table>
     <tr>
      <td>Дата рождения:</td>
      <td>
          <select name="'.$this->getFieldName('BIRTHDAY').'"
           on change="myShowField(this, \''.$this->getFieldId('DAYS').'\');">
           <option value="">Сегодня</option>
           <option value="3" '.('3' == $this->getFieldValue('BIRTHDAY', "") ? 'selected' : '').'>За три дня</option>
           <option value="CUSTOM" '.('CUSTOM' == $this->getFieldValue('BIRTHDAY', "") ? 'selected' : '').'>Указать за сколько дней</option>
          </select>
          <input
           type="text"
           name="'.$this->getFieldName('DAYS').'"
           id="'.$this->getFieldId('DAYS').'"
           value="'.$this->getFieldValue('DAYS', "").'"
           '.($this->getFieldValue('BIRTHDAY', "")!="CUSTOM" ? 'style="display: none;"' : "").'
          >
      </td>
     </tr>
     </table>
   ';
} 
 | 
И теперь изменим функцию getData. Для получения установленных значений полей нужно вызывать ту же функцию getFieldValue.
Используем значения из формы:
| public function getData()
{
    $birthday = $this->getFieldValue('BIRTHDAY', 0);
    $days = $this->getFieldValue('DAYS', 0);
    if(is_numeric($birthday))
     $dayToAdd = $birthday;
    elseif($birthday=="CUSTOM" && is_numeric($days))
     $dayToAdd = $days;
    else
     $dayToAdd = 0;
    $currentDate = new \Bitrix\Main\Type\Date();
    $filter = array(
     "PERSONAL_BIRTHDAY_DATE" => $currentDate->add("-".$dayToAdd." day")->format("m-d"),
    );
    $resultDb = CUser::GetList(($by="ID"), ($order="asc"), $filter);
    return $resultDb;
} 
 | 
Теперь добавим в группу наш коннектор два раза и посмотрим результат, они независимо друг от друга работают и сохраняются:

3) Важные замечания
В модуле есть собственная база адресов, в админке она выводится на странице "Списки адресов".
В нее можно добавлять как вручную, так и из источников:

Так, если вы не хотите, чтобы из источника пользователь мог сюда добавить адреса, то в классе коннектора нужно добавить функцию:
| /** @return bool */
public function requireConfigure()
{
   return true;
} 
 | 
В этом случае, он не будет отображен в данном интерфейсе.
Но если ваш коннектор подразумевает импортирование в "Списки адресов", то он должен возвращать отсортированные данные в строгом порядке.
То есть, в нашем примере мы возвращаем адреса пользователей. Значит нужно сортировать по ID и возрастанию, чтобы новые пользователи были в конце выборки.
Так как импорт пошаговый, мы исключаем ситуации, когда при разных выборках один и тот же адрес будет то в начале выборки, то в середине, то в конце.
И этот адрес может быть пропущен.
3. Шаблоны
При создании выпуска рассылки есть возможность выбрать не выбирать шаблон(пустой шаблон), либо выбрать шаблон.
Есть три вида шаблонов: Штатные, Мои шаблоны и Дополнительные шаблоны.

В Дополнительные шаблоны можно добавить свои опять же через события.
Для этого в bitrix/php_interface/init.php добавим свой обработчик события onPresetTemplateList модуля sender:
| AddEventHandler("sender", "onPresetTemplateList", array("MyEventHandler","senderTemplateList"));
class MyEventHandler
{
    public static function senderTemplateList()
    {
     $resultList = array();
     $resultList[] = array(
      'TYPE' => 'ADDITIONAL',                             // Указываем, что хотим добавить в пункт Дополнительные шаблоны
      'NAME' => 'Простой шаблон 1',                          // Выводимое название шаблона
      'ICON' => '/bitrix/images/sender/preset/template/empty.png', // URL иконки шаблона
      'HTML' => '<b>Это текст шаблона #1.</b>'            // содержимое шаблона
     );
     $resultList[] = array(
      'TYPE' => 'ADDITIONAL',
      'NAME' => 'Простой шаблон 2',
      'ICON' => '/bitrix/images/sender/preset/template/empty.png',
      'HTML' => '<b>Это текст шаблона #2.</b>'
     );
     return $resultList;
    }
} 
 | 
В данном примере мы возвращаем два шаблона.
Идем в создание/редактирование выпуска и проверяем:

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

Их тоже можно расширить, добавив в свои вложенные папки.
Для этого, как и для шаблонов, в /bitrix/php_interface/init.php добавим свой обработчик события onPresetMailBlockList модуля sender:
| AddEventHandler("sender", "onPresetMailBlockList", array("MyEventHandler","senderMailBlockList"));
class MyEventHandler
{
    public static function senderMailBlockList()
    {
     $resultList = array();
     $resultList[] = array(
      'TYPE' => 'Мои блоки', // Название папки, в которой покажется блок
      'CODE' => 'mybreakline', // Уникальный код
      'NAME' => 'Разделительная линия', // Название блока
      'DESC' => 'Красная разделительная линия', // Описание блока, выводимое при наведении курсора
      'ICON' => '',
      'HTML' => '<hr style="color: red">' // Содержимое блока
     );
     $resultList[] = array(
      'TYPE' => 'Мои блоки',
      'CODE' => 'mylogo',
      'NAME' => 'Логотип',
      'DESC' => 'Логотип нашей компании',
      'ICON' => '',
      'HTML' => '<table><tr><td><img src="/images/logo.gif"></td></tr></table>'
     );
     return $resultList;
    }
} 
 | 
В данном примере мы возвращаем два шаблона.
Идем в создание/редактирование выпуска и проверяем:

5. Кастомизация формы отписки
За форму отписки отвечает компонент main.mail.unsubscribe
Достаточно скопировать его шаблон
/bitrix/modules/main/install/components/bitrix/main.mail.unsubscribe/templates/.default
в шаблон сайта, к примеру дефолтный шаблон сайта:
/bitrix/templates/.default/components/bitrix/main.mail.unsubscribe/.default
Скопированный шаблон можете изменять.
В ближайшее время появится возможность указать путь к собственной странице отписки, где можно будет поменять вид не только шаблона компонента, но и вид всей страницы.
Заключение
Таким образом, вы можете расширить возможности модуля, используя адреса из разнообразных источников по собственным критериям.
И сможете делать решения для маркетплейса, связанные как с источниками адресов, так и с разнообразными шаблонами писем.
Фото:
					 
															 
			 
      
И да, все время хотел спросить, появится ли когда-нибудь возможность человеческой регистрации расширений\поведений, в вашем примере это connector
Например, нечто вроде:
return new \P\Filter();
}, true);
...
$filter = $di->getFilter();
$filter->add('alpha[ja-jp]', function($value){
return preg_replace('/[^a-z|\p{Hiragana}|\p{Han}|\p{Katakana}]+/iu', '', $value);
});
\CDBResult используется для поддержки функционала, написанного на старом ядре.
Что-то в продукте еще не перенесено на новое ядро, также у клиентов есть функционал на старом ядре.
Так клиенты имеют возможность использовать новый функционал не переписывая свой функционал.
В дальнейшем будет и поддержка нового ядра, без необходимости возвращать \CDBResult.
Сейчас же, результат запроса \Bitrix\Main\Application::getConnection()->query()
или результат getList из ORM
можно передать параметром в \CDBResult:
Обсуждение общего вопроса о регистрации расширений\поведений, мне кажется лучше перенести на форум в специальную тему, чем пытаться поговорить в комментариях к статье о рассылках.
'email@qq.qq',
'email2@qq.qq'
];
$arr = array(); $arr[] = array("EMAIL" => "email1@qq.qq"); $arr[] = array("EMAIL" => "email2@qq.qq"); $rs = new \CDBResult; return $rs->InitFromArray($arr);Поэтому для производительности выбран не массив, а \CDBResult.
$dataDb = $connector->getData(); if(!is_subclass_of($dataDb, 'CDBResultMysql')) { $rowsInPage = 50; $on lyOneLoop = true; }Вообще, мне не понятно, почему загрузку email-ов из файла вы предусмотрели, но загрузку этих же email-ов из файла ВМЕСТЕ с именами - нет?
P.S.: для загрузки списка я использовал раздел "Список адресов"
так есть ли какой то способ добавить кастомно список адрессов в рассылку
внутри функции public function getData() получил массив вида
Представим что у нас есть 4-5 рассылок. Как пользователю подписаться на них? Будет компонент?
А все потому что бываю постоянные рассылки, а бывают "срочно, горит, делайте б..., а то пристрелю"
Рассылки второго типа показывать пользователям для подписки нельзя никогда. А для отписки - пожалуй можно, хотя под вопросом.
И этот компонент может, к примеру, выводить список забытых товаров в корзине.
В направлении решений подобных задач мы и готовим следующее обновление модуля.
Всегда возвращает 0 адресов. Даже если отбирать в коннекторе вообще всех пользователей.
Такая же проблема, подскажите пож. как сделать так чтобы не отправлялись пустые письма.
Как можно отловить событие если bitrix:news.list.mail ничего не выдает.
Ситуация дикая, учитывая что модуль подписки генерит ссылку как одно целое.
Как в почтовый шаблон вставить адрес отписки?
Скорей мы добавим текст к шаблону, что если вы не подписывались на данную рассылку, проигнорируйте письмо.
Спасибо.
1. Вы внесли свой емеил для подписки.
2. Вам приходит уведомление, о подтверждение (зачем вам тут отписываться, если вы еще не чего не получали, и сами захотели подписаться)
3. Вы не инициировали подписку, игнорируете письмо, и они к вам больше не приходят.
4. Вы подписались и подтвердили, в первом же письме пришла отписка, если вам не понравилась первая рассылка и вы посчитали, что она не интересна6 отписались.
В вашей js-функции ConnectorSettingGetCount все значения, которые по ajax улетают на сайт для подсчета найденных записей получаются через .value.
Для multiple select обращение .value вернет значение только 1-го option'a ()
Проблема в том, что да, в списке источников наш кастомный появляется, однако группу с таким источником невозможно сохранить, т.к. при сохранении получаем ошибку, что не выбрана ни одна группа. Причина в том, что в битриксовских файлах, работающих с группами (редактирование/обработка и т.п.) - в частности \modules\sender\admin\group_edit.php стоит условие
Единственный способ обойти это, без правки кода модулей, это воспользоваться импортом данных. Т.е. идем в раздел "Список адресов" и делаем импорт адресов из нашего источника, после чего в редакторе группы адресов добавляем "Email-маркетинг - Список адресов" и указываем группу с названием как у нашего кастомного.
Мне вот просто интересно - вы вообще проверяли работу примера, который написан в статье?
документации - нет, комментариев в коде - нет, единственный источник информации - данная статья. У меня нет слов, если честно...
Однако, где импорт подписчиков вместе с именами? У многих базы на тысячи пользователей, ну не в ручную же имена добавлять?
Без возможности обратиться по имени к адресату ценность функционала импорта существенно снижается.
И многие даже не попробуют этот функционал из-за такой неприятности.
public function getForm() { CModule::IncludeModule("iblock"; $arSelect = array("ID", "NAME", "PROPERTY_*"; $arFilter = array("IBLOCK_ID"=>50); $res = CIBlockElement::GetList(array(), $arFilter, false, false, $arSelect); $strsity .= '<select name="'.$this->getFieldName('SITY').'">'; while($ob = $res->GetNextElement()){ $arFields = $ob->GetFields(); $arProps = $ob->GetProperties(); $res_summ["SITY"][] = $arProps["SITY"]["~VALUE"]; $res_summ["CATEGORY"][] = $arProps["CATEGORY"]["~VALUE"]; $strsity .= '<option value="'.$arProps["SITY"]["PROPERTY_VALUE_ID"].'">'; $strsity .= $arProps["SITY"]["VALUE"]; $strsity .= '</option>'; } $strsity .= '</select>'; return $strsity; }не работает $arProps = $ob->GetProperties();
OnConnectorList +
OnPresetTemplateList +
OnPresetMailBlockList +
OnAfterRecipientClick ?
OnAfterPostingSendRecipient ?
OnTriggerList ?
От себя добавлю - живой класс-код можно посмотреть тут
/bitrix/modules/form/lib/sender/connector.php
Это для веб-форм.
Хак как можно отменить рассылку в своем источнике:
if (/*если по какому-то условию ваша рассылка вообще не должна уходить*/) { //если была - отменяем рассылку хаком $res = \Bitrix\Sender\ContactTable::getList(array('filter' => array('ID' => -1))); return new \CDBResult($res); }Каким образом можно добавить служебный заголовок к письму REPLY-TO?
Возможно кто-то уже делился рецептом настройки сервера/использования сторонних серверов для рассылки?
Пытался рассылать и через pdd.yandex.ru и гугл, но везде проходит около 80ти писем, а дальше тишина.
SPF/PTR/DKIM, лимиты на отправку настроены, но все как об стенку горох =(
Есть у кого рецепт?
Спасибо за статью!
Вопрос следующий: при попытке заменить картинки в стандартных шаблонах, ничего не происходит. Стандартные картинки удаляются, а новые не загружаются (даже из медиабиблиотеки). В чем может быть дело?
Возвращает список всех подписок на рассылки для подписчика., но какие ему параметры передать непонятно
В описании модуля указано:
Также можно не отправлять выпуск, если недостаточно указанного количества новых новостей - это регулируется одноименной опцией.
Эта опция работает? У меня почему-то нет.
Это только у меня не работает или вообще?
Судя по описанию, я поняла, что нужно настроить расписание рассылки (например, раз в день, или раз в неделю или...). Потом в шаблоне рассылки разместить компонент Компонент - Список новостей для почты (bitrix:news.list.mail). И в настройках этого компонента нужно указать, что не отправлять выпуск, если недостаточно указанного количества новых новостей (у меня количество 1).
Если я все правильно поняла, то выпуск не должен уходить по расписанию, если новостей нет.
Я все так и сделала.
Но у меня новостей нет, а выпуск все равно уходит по расписанию.
Что я сделала не так?
С техподдержкой веду переговоры уже два месяца по этому вопросу. Они либо ничего не отвечают, либо задают какие-то вопросы не по существу.
Кто-нибудь может ответить, как это делаестя?
Или это вообще не работает в стандартном модуле и надо свой скрипт писать?
На сайте размещена форма подписки, нужно после подтверждения подписки пользователем производить некоторые действия, отправлять письмо и купоном.
Можно как обработать данное событие?