[EmailQueue] - Очередь писем

С сайта бывает требуется отсылать много писем. Но многие хостеры ограничивают число писем что можно сразу отправить. Например на одном хостинге можно отправить только 60 писем в минуту. Чтобы обойти это ограничение нужно организовывать очередь писем и отправлять письма частями по, например, 50 штук. Чтобы не писать такую очередь каждый раз когда отправка многих писем нужна в компоненте, написал отдельный компонент что организует такую очередь.


В админке сделано относительно удобное управление очередью.
Для отправки писем из очереди поставите в cron файл core/components/emailqueue/cron/send.php с нужной периодичностью.
Настройки:
emailqueue_limit — сколько писем отправлять за раз.
emailqueue_store_days — сколько дней письма хранить в очереди.
Для помещения писемь в очередь используйте примерно такой код:
if (!$EmailQueue = $modx->getService('emailqueue', 'EmailQueue', $modx->getOption('emailqueue_core_path', null,
	$modx->getOption('core_path') . 'components/emailqueue/') . 'model/emailqueue/', array())) {
	return;
}
$queue_email = $modx->newObject('EmailQueueItem');
$data1 = array(
	'sender_package'=>'UserTest', //дополнение отправитель письма
	'to'=>$invite->user_email, //емаил
	'subject'=>$modx->lexicon('usertest_invite_subject',array('test_name' => $test->name)), // тема письма
	'body'=>$modx->getChunk('tpl.UserTest.InviteEmail',array('test_name' => $test->name,'link'=>$invite->url)), // тело письма
	'date'=>date("Y-m-d H:i:s"), // дата в очереди. Нужно чтоб письма удалялись по истечении срока хранения.
);
if($modx->getOption('usertest_invite_email_from', null, false))
	$data1['from'] = $modx->getOption('usertest_invite_email_from'); //необязательно
if($modx->getOption('usertest_invite_email_from_name', null, false))
	$data1['from_name'] = $modx->getOption('usertest_invite_email_from_name'); //необязательно
$queue_email->fromArray($data1);
if($queue_email->save())
	$modx->log(modX::LOG_LEVEL_INFO,"Приглашение на $email добавлено в очередь писем!");
Дополнительно можно задать:
$queue_email->reply_to // обратный адрес 
$queue_email->attachments //полные имена приклепляемых файлов через запятую
Компонент доступен в Modstore https://modstore.pro/packages/alerts-mailing/emailqueue.

Думаю небольшие компоненты для разработчиков лучше делать бесплатными. Так что компонетнт по крайней мере пока бесплатный :). Но от доната не откажусь. Кто захочет поблагодарить Номер кошелька Яндекс 410011280920822

Гитхаб https://github.com/touol/EmailQueue
Александр
10 июня 2018, 05:44
8
129
+12

Комментарии: 41

Дмитрий
10 июня 2018, 16:00
+1
Скоро придется отправить 4000 писем пользователям сайта, который переношу с WP, и как раз об этом думал, как же решить это. Большое спасибо за компонентище!
    Александр
    10 июня 2018, 16:22
    +1
    Надеюсь скоро опубликуют. Рад что вам пригодиться. Забыл только очистку всей очереди добавить :(, но попозже добавлю. Если вам быстрее компонент надо будет могу выслать по почте.
      Дмитрий
      10 июня 2018, 16:43
      0
      да не, не раньше, чем через 2 недели нужно будет)
tvset
11 июня 2018, 19:16
0
Очень здорово! очень нужно, спасибо автор!
Тоже со временем воспользуюсь, возьму на заметку.
Василий Столейков
13 июня 2018, 16:05
0
Спасибо большое!
Использовал для этих целей очереди Sendex, добавлял туда письма и дёргал очередь через крон. Ваше дополнение удобнее и видна статистика отправленных писем.
    Александр
    13 июня 2018, 17:49
    +2
    Еще дополнение на модерации и его еще в глаза никто не видел, а уже 3 спасибо :). Всем пожалуйста. Сейчас думаю на всякий случай в гитхаб выложу. Вдруг кому-то уже надо.
Александр
13 июня 2018, 18:03
+2
Выложил на гитхаб
Иван Бондаренко
18 июня 2018, 14:40
0
Александр, обнови пост. Компонент доступен в Модстор — modstore.pro/packages/alerts-mailing/emailqueue
    Александр
    18 июня 2018, 16:21
    0
    Спасибо. Как-то из головы вылетело.
Василий Столейков
18 июня 2018, 18:34
0
Какая-то странная дата отправки на демо-сайте: -1-11-30 00:00:00
    Александр
    18 июня 2018, 18:53
    +1
    Это такая дата по умолчанию была. Так 0000-00-00 00:00:00 mysql записывает. Модераторы модсторе тоже сказали это поменять. В новой версии по умолчанию дата отправки null и в поле пусто. Сейчас обновил компонент на демо, чтоб вас это не смущало.
      Василий Столейков
      18 июня 2018, 18:56
      0
      Да, спасибо, пустота будет лучше нулей и непонятных дат.
      Я обычно даты в свои таблицы записываю в unix-формате и никаких проблем с этим. Пример беру с дат в таблице ресурсов, там все они в unix-е.
      Василий Столейков
      18 июня 2018, 18:58
      0
      Да, и ещё не сразу понял назначение двух одинаковых кнопок с самолётиками в панели с кнопками над таблицей, запутало как-то.
        Александр
        18 июня 2018, 19:10
        0
        Хм… плохо у меня с дизайном :(. Воображения на картинки не хватает. Если есть идеи какие значки повешать на эти действия, напишите плиз :). Если писать словами, то в 1 слово не обойдешься и ширины экрана в итоге не хватит.
          Василий Столейков
          18 июня 2018, 19:15
          0
          А что если сделать кнопки выпадающим списком как в других компонентах:

          Тогда и текстом можно будет нормально написать, и сколько угодно кнопок туда напихать по мере развития компонента.
          Хотя бы для некоторых кнопок.
            Александр
            18 июня 2018, 19:26
            0
            Хорошая идея. Только выпадающие списки я такие еще не делал. посмотрю как там в этом компоненте сделано.
            Развития компонента пока не планируется :). В смысле, а есть куда его развивать? Рассылки все равно лучше делать через специализированные сервисы. Хостеры, если блокируют письма, то ничего не сообщают и не понятно ушло письмо или нет. Этот компонент на случай если сервис еще не имеет смысла подключать.
              Василий Столейков
              18 июня 2018, 19:31
              0
              а есть куда его развивать?
              Есть!

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

              Это можно например реализовать так: отдельное поле template в таблице, в который можно будет указывать id существующего шаблона или чанка. В форме можно это селектом сделать самоподгружающим эти эти элементы, но на первое время и простое поле тоже сойдёт, главное чтобы работало. В самом шаблоне чтобы были доступны переменными (желательно через Fenom если установлен) все поля письма. Ну и если шаблон указан, то отправляется письмо красиво оформленное, если нет, то простой текст.

              Для многих это довольно важно, т.к. кастомизация, сохранения брендового стиля и т.д.
                Александр
                18 июня 2018, 19:50
                0
                Ну тут идея компонента не совсем такая :). В очереди хранятся уже готовые письма. А письма в очередь добавляются кодом, в котором как раз письмо красиво и оформляется.
                'body'=>$modx->getChunk('tpl.UserTest.InviteEmail',array('test_name' => $test->name,'link'=>$invite->url)),
                Создание и редактирование писем в компоненте это просто на всякий случай. Чтоб протестировать компонент или просто отправку писем. Посмотреть что вообше в очереди лежит.
                  Василий Столейков
                  18 июня 2018, 20:20
                  0
                  Просто дело в том, что при редактировании письма код не очень-то и подредактируешь, будет каша. Тогда уже лучше редактор кода привязать к полю редактирования в админке…
                  Спасибо за объяснение, забыл про getChunk (что компонент больше для разработчиков) и сосредоточился на удобстве формы из админки…
          Василий Столейков
          18 июня 2018, 19:25
          0
          Например для кнопки «Восстановить письма с ошибками в очереди» больше подойдёт значок icon-refresh.
          Кнопки Ошибки и Все непонятно что делают, просят больше описания действий. Ну и значков просят, а то высота кнопок прыгает.
          Две кнопки удалить — тоже можно было бы их в выпадалку запрятать и текст написать.

          Я просто не сразу заметил, что у кнопок есть подсказка при наведении, да и не всегда она у меня срабатывает, видно потому что навешана на иконку значка, а не на всю кнопку.
            Александр
            18 июня 2018, 19:32
            0
            Кнопки Ошибки и Все — это не кнопки вообще. Это подписи что следующие кнопки к Ошибкам относяться и к всем письмам. Мда… замученный у меня дизайн получился. Буду выпадающим списком делать, но не пока занят через неделю думаю сделаю.
              Василий Столейков
              18 июня 2018, 19:33
              0
              Ок, я тебе только идей подкинул для рихтовки, а так молодец — компонент очень классный и очень нужный! Буду его использовать на многих сайтах.
        Александр
        18 июня 2018, 19:15
        0
        В новой версии еще и 2 значка удаления. 1 удаляет письма с ошибками. Другой очищает всю очередь.
          Василий Столейков
          18 июня 2018, 19:17
          0
          Не, это будет путаница однозначно!
          Лучше в выпадающий список запрятать некоторые кнопки.
Ксения
25 июня 2018, 09:34
0
Добрый день! Подскажите пожалуйста, как лучше организовать помещение в очередь если используется сниппет (вызывается по cron)
users()
    ->members('en')
    ->profile()
    ->where('modUser.active = 1 AND DAYOFMONTH(FROM_UNIXTIME(Profile.dob)) = DAYOFMONTH(now()) AND MONTH(FROM_UNIXTIME(Profile.dob)) = MONTH(now())')
    ->joinGroup('group2')
    ->each(function($user, $idx) {
        if (is_email($user['email'])) {
            email()->to($user['email'])->subject('Поздравляем с Днём рождения!')->tpl('chunkName', $user)->send();
        }
    });
который использует компонент modHelpers?
На первый взгляд кажется, что проще переписать весь сниппет и сделать по вашему шаблону, но непонятно, что вписывать в графе
$data1 = array(
	'sender_package'=>'UserTest', //дополнение отправитель письма
    Александр
    25 июня 2018, 09:52
    0
    Примерно так думаю
    if (!$EmailQueue = $modx->getService('emailqueue', 'EmailQueue', $modx->getOption('emailqueue_core_path', null,
    	$modx->getOption('core_path') . 'components/emailqueue/') . 'model/emailqueue/', array())) {
    	return;
    }
    users()
        ->members('en')
        ->profile()
        ->where('modUser.active = 1 AND DAYOFMONTH(FROM_UNIXTIME(Profile.dob)) = DAYOFMONTH(now()) AND MONTH(FROM_UNIXTIME(Profile.dob)) = MONTH(now())')
        ->joinGroup('group2')
        ->each(function($user, $idx) {
            if (is_email($user['email'])) {
                $data1 = array(
    			'sender_package'=>'UsersSend', //дополнение отправитель письма
    			'to'=>$user['email'], //емаил
    			'subject'=>'Поздравляем с Днём рождения!', // тема письма
    			'body'=>$modx->getChunk('chunkName', $user), // тело письма
    			'date'=>date("Y-m-d H:i:s"), // дата в очереди. Нужно чтоб письма удалялись по истечении срока хранения.
    		);
    		$queue_email->fromArray($data1);
    		if(!$queue_email->save())
    			$modx->log(1,"Письмо для ".$user['email']." не удалось добавить в очередь писем!");
            }
        });
    sender_package Нужно только чтоб видеть откуда письмо в очереди. Если вдруг несколько компонентов сразу отправляют. Можно написать что угодно или даже оставить пустым.
Александр
25 июня 2018, 10:06
+1
ошибку заметил
if (!$EmailQueue = $modx->getService('emailqueue', 'EmailQueue', $modx->getOption('emailqueue_core_path', null,
	$modx->getOption('core_path') . 'components/emailqueue/') . 'model/emailqueue/', array())) {
	return;
}
users()
    ->members('en')
    ->profile()
    ->where('modUser.active = 1 AND DAYOFMONTH(FROM_UNIXTIME(Profile.dob)) = DAYOFMONTH(now()) AND MONTH(FROM_UNIXTIME(Profile.dob)) = MONTH(now())')
    ->joinGroup('group2')
    ->each(function($user, $idx) {
        if (is_email($user['email'])) {
            	$queue_email->newObject('EmailQueueItem'); //забыл создать объект :(
		$data1 = array(
			'sender_package'=>'UsersSend', //дополнение отправитель письма
			'to'=>$user['email'], //емаил
			'subject'=>'Поздравляем с Днём рождения!', // тема письма
			'body'=>$modx->getChunk('chunkName', $user), // тело письма
			'date'=>date("Y-m-d H:i:s"), // дата в очереди. Нужно чтоб письма удалялись по истечении срока хранения.
		);
		$queue_email->fromArray($data1);
		if(!$queue_email->save())
			$modx->log(1,"Письмо для ".$user['email']." не удалось добавить в очередь писем!");
        }
    });
    Ксения
    25 июня 2018, 10:36
    0
    Спасибо, буду пробовать.
    ps пока помню — есть небольшая ошибка в верхнем меню компонента.
    тэг title (всплывающая подсказка) при наведении на кнопки не выводится в FF, а в chrome выводится только при наведении на иконку внутри кнопки, а не на саму кнопку. Может пригодится.
      Александр
      25 июня 2018, 10:42
      0
      Ну это известная проблема. Позже переделаю на выпадающий список
    Ксения
    25 июня 2018, 11:16
    0
    Получила вот такое
    Fatal error: Call to a member function newObject() on a non-object
    Если без этой
    $queue_email->newObject('EmailQueueItem');
    строки то
    Fatal error: Call to a member function getChunk() on a non-object
      Александр
      25 июня 2018, 11:26
      0
      Блин поторопился. Конечно
      $queue_email=$modx->newObject('EmailQueueItem');
      Александр
      25 июня 2018, 11:32
      0
      Fatal error: Call to a member function getChunk() on a non-object
      У вас в начале скрипта сам $modx подключен?
      что то вроде такого
      require_once dirname(dirname(dirname(dirname(dirname(__FILE__))))) . '/config.core.php';
      require_once MODX_CORE_PATH . 'config/' . MODX_CONFIG_KEY . '.inc.php';
      require_once MODX_CONNECTORS_PATH . 'index.php';
        Ксения
        25 июня 2018, 11:38
        0
        Нет. Сейчас буду разбираться.
        Ксения
        25 июня 2018, 11:53
        0
        Подключила
        require_once
        , изменила
        $queue_email=$modx->newObject('EmailQueueItem');
        и всё равно выход то же
        Fatal error: Call to a member function newObject() on a non-object
          Александр
          25 июня 2018, 12:04
          0
          Полностью код можно? И путь до файла
            Ксения
            25 июня 2018, 12:16
            0
            <?php
            error_reporting(E_ALL | E_STRICT);
            ini_set('display_errors', 1);
            require_once dirname(dirname(dirname(dirname(dirname(dirname(__FILE__)))))) . '/config.core.php';
            require_once MODX_CORE_PATH . 'config/' . MODX_CONFIG_KEY . '.inc.php';
            require_once MODX_CONNECTORS_PATH . 'index.php';
            
            
            if (!$EmailQueue = $modx->getService('emailqueue', 'EmailQueue', $modx->getOption('emailqueue_core_path', null,
            	$modx->getOption('core_path') . 'components/emailqueue/') . 'model/emailqueue/', array())) {
            	return;
            }
            users()
                ->members('ru')
                ->profile()
                ->select('email,dob')
                ->where('modUser.active = 1 AND DAYOFMONTH(FROM_UNIXTIME(Profile.dob)) = DAYOFMONTH(now()) AND MONTH(FROM_UNIXTIME(Profile.dob)) = MONTH(now())')
                ->each(function($user, $idx) {
                    if (is_email($user['email'])) {
                       $queue_email = $modx->newObject('EmailQueueItem');
            		$data1 = array(
            			'sender_package'=>'UsersSend', //дополнение отправитель письма
            			'to'=>$user['email'], //емаил
            			'from_name'=>'Тест',
            			'subject'=>'Поздравляем с Днём рождения!', // тема письма
            			'body'=>$modx->getChunk('email_ru', $user), // тело письма
            			'date'=>date("Y-m-d H:i:s"), // дата в очереди. Нужно чтоб письма удалялись по истечении срока хранения.
            		);
            		$queue_email->fromArray($data1);
            		if(!$queue_email->save())
            			$modx->log(1,"Письмо для ".$user['email']." не удалось добавить в очередь писем!");
                    }
                });
            Про путь до файла не поняла. Делаю стандартный сниппет в админке, запускаю по Cron Manager.
              Александр
              25 июня 2018, 12:35
              0
              Не работал с Cron Manager. По идее в нем не надо $modx подключать. он уже должен быть подключен. Надо разбираться. Так ничего не понятно. Мой скайп touols. Можите доступ скинуть?
              странно что на $modx->getService не ругается
                Ксения
                25 июня 2018, 12:46
                0
                Подключать $modx не надо по моему тоже. Через 1-1.5 часа сделаю копию на Modhost и если там не заработает пришлю доступы.
                  Александр
                  25 июня 2018, 12:59
                  0
                  Поставил Cron Manager. Запустил такой код. И все успешно.
                  <?php
                  error_reporting(E_ALL | E_STRICT);
                  ini_set('display_errors', 1);
                  
                  
                  if (!$EmailQueue = $modx->getService('emailqueue', 'EmailQueue', $modx->getOption('emailqueue_core_path', null,
                  	$modx->getOption('core_path') . 'components/emailqueue/') . 'model/emailqueue/', array())) {
                  	return;
                  }
                  $user['email'] = 'a@b.ru';
                  $queue_email = $modx->newObject('EmailQueueItem');
                  $data1 = array(
                  	'sender_package'=>'UsersSend', //дополнение отправитель письма
                  	'to'=>$user['email'], //емаил
                  	'from_name'=>'Тест',
                  	'subject'=>'Поздравляем с Днём рождения!', // тема письма
                  	'body'=>$modx->getChunk('email_ru', $user), // тело письма
                  	'date'=>date("Y-m-d H:i:s"), // дата в очереди. Нужно чтоб письма удалялись по истечении срока хранения.
                  );
                  $queue_email->fromArray($data1);
                  if(!$queue_email->save())
                  	$modx->log(1,"Письмо для ".$user['email']." не удалось добавить в очередь писем!");
                  Александр
                  25 июня 2018, 13:04
                  +1
                  Похоже я понял в чем косяк. $modx внутри функции вызывается. А внутри функции его нет. Попробуйте перед if (is_email($user['email'])) { вставить global $modx;

                  ->each(function($user, $idx) {
                         global $modx;
                  	 if (is_email($user['email'])) {
                             $queue_email = $modx->newObject('EmailQueueItem');
                    Ксения
                    25 июня 2018, 13:18
                    0
                    Да, так ругается уже на последнюю строку
                    $queue_email->fromArray($data1);
                    Fatal error: Call to a member function fromArray() on a non-object
                    Если вызвать
                    global $modx;
                    ещё и в начале сниппета, тогда работает. Только глаз режет :-)
                    Спасибо!