Добрый день, коллеги!
Я хочу рассказать вам как можно расширить возможности модуля 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'
];
Поэтому для производительности выбран не массив, а \CDBResult.
Вообще, мне не понятно, почему загрузку 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-маркетинг - Список адресов" и указываем группу с названием как у нашего кастомного.
Мне вот просто интересно - вы вообще проверяли работу примера, который написан в статье?
документации - нет, комментариев в коде - нет, единственный источник информации - данная статья. У меня нет слов, если честно...
Однако, где импорт подписчиков вместе с именами? У многих базы на тысячи пользователей, ну не в ручную же имена добавлять?
Без возможности обратиться по имени к адресату ценность функционала импорта существенно снижается.
И многие даже не попробуют этот функционал из-за такой неприятности.
не работает $arProps = $ob->GetProperties();
OnConnectorList +
OnPresetTemplateList +
OnPresetMailBlockList +
OnAfterRecipientClick ?
OnAfterPostingSendRecipient ?
OnTriggerList ?
От себя добавлю - живой класс-код можно посмотреть тут
/bitrix/modules/form/lib/sender/connector.php
Это для веб-форм.
Хак как можно отменить рассылку в своем источнике:
Каким образом можно добавить служебный заголовок к письму REPLY-TO?
Возможно кто-то уже делился рецептом настройки сервера/использования сторонних серверов для рассылки?
Пытался рассылать и через pdd.yandex.ru и гугл, но везде проходит около 80ти писем, а дальше тишина.
SPF/PTR/DKIM, лимиты на отправку настроены, но все как об стенку горох =(
Есть у кого рецепт?
Спасибо за статью!
Вопрос следующий: при попытке заменить картинки в стандартных шаблонах, ничего не происходит. Стандартные картинки удаляются, а новые не загружаются (даже из медиабиблиотеки). В чем может быть дело?
Возвращает список всех подписок на рассылки для подписчика., но какие ему параметры передать непонятно
В описании модуля
Также можно не отправлять выпуск, если недостаточно указанного количества новых новостей - это регулируется одноименной опцией.
Эта опция работает? У меня почему-то нет.
Это только у меня не работает или вообще?
Судя по описанию, я поняла, что нужно настроить расписание рассылки (например, раз в день, или раз в неделю или...). Потом в шаблоне рассылки разместить компонент Компонент - Список новостей для почты (bitrix:news.list.mail). И в настройках этого компонента нужно указать, что не отправлять выпуск, если недостаточно указанного количества новых новостей (у меня количество 1).
Если я все правильно поняла, то выпуск не должен уходить по расписанию, если новостей нет.
Я все так и сделала.
Но у меня новостей нет, а выпуск все равно уходит по расписанию.
Что я сделала не так?
С техподдержкой веду переговоры уже два месяца по этому вопросу. Они либо ничего не отвечают, либо задают какие-то вопросы не по существу.
Кто-нибудь может ответить, как это делаестя?
Или это вообще не работает в стандартном модуле и надо свой скрипт писать?
На сайте размещена форма подписки, нужно после подтверждения подписки пользователем производить некоторые действия, отправлять письмо и купоном.
Можно как обработать данное событие?