new

Sendex

Компонент для работы с рассылками.
Бесплатно
Загрузите дополнение из админки вашего сайта.
Как загрузить?
Автор дополнения
MODX.pro
Пакетов
14
Закачек
134 501
Автор дополнения
Пакетов
14
Закачек
134 501
Версия 2.0.0-pl
Дата выпуска 25.07.2026
Загрузки 3 689
Просмотры 23 242
Компонент хранит три сущности в отдельных таблицах:

  • sxNewsletter — рассылка: тема, отправитель, шаблон письма и остальные параметры.
  • sxSubscriber — подписчик: email и id рассылки обязательны; id пользователя MODX сохраняется, если он был авторизован при подписке.
  • sxQueue — письмо в очереди на отправку.

В админке можно пройти весь цикл вручную: создать рассылку, добавить подписчиков, сгенерировать очередь и отправить письма. Часть шагов автоматизируется через сниппет, cron и события плагина.

Создаём рассылку:



Укажите шаблон письма (один идёт в комплекте). В шаблоне можно вызывать сниппеты.

Подписываем пользователя:



Вручную из менеджера можно подписать только пользователя сайта. Гости подписываются через сниппет на фронте. Можно добавить всю группу пользователей MODX (только активные и не заблокированные). Гостевые записи с тем же email сливаются с аккаунтом при активации или сохранении пользователя.

Генерируем письма:



Рассылка обходит подписчиков и кладёт письма в очередь. Новые строки хранят пустое тело: HTML собирается из шаблона рассылки в момент отправки. Старые строки с сохранённым HTML отправляются как есть.

Отправляем:



В сетке рассылок есть действие «Отправить подписчикам»: одним кликом вызываются addQueues и отправка очереди. Отправку можно запускать по одному письму, пакетом из админки или по cron.

Сниппет Sendex

Вызов на странице:

[[!Sendex? &id=`1`]]

Сниппет показывает форму подписки и отписки. Авторизованный пользователь нажимает кнопку. Гость подтверждает email по ссылке из письма, если включено подтверждение (&confirmEmail=1 или системная настройка sendex_confirm_email). С &confirmEmail=0 гость подписывается сразу.

По умолчанию формы работают через AJAX: assets/components/sendex/js/web/sendex.js отдаёт JSON {success, message, html} и обновляет виджет без перезагрузки страницы. Несколько виджетов на одной странице разводите через &widgetKey=.

Уже подписанному пользователю показывается кнопка отписки. Гость отписывается по ссылке из письма (code + newsletter_id в URL).

Сниппет — готовый пример. Логику подписки можно перенести в свой код через методы sxNewsletter.

Основные методы

Объект sxNewsletter

  • addQueues — добавляет письма в очередь для всех подписчиков рассылки
  • sendToSubscribersaddQueues + отправка очереди этой рассылки (то же, что кнопка в админке)
  • subscribe — подписка по id пользователя или email
  • subscribeGuest — гостевая подписка с подтверждением или без
  • subscribeGroup — массовая подписка группы пользователей
  • checkEmail — отправка проверочной ссылки гостю
  • confirmEmail — подтверждение email по hash из ссылки
  • unSubscribe — отписка по полю code из sxSubscriber
  • isSubscribed — проверка по user_id или email; возвращает id sxSubscriber или 0

Генерация очереди через API:

$modx->addPackage('sendex', MODX_CORE_PATH . 'components/sendex/model/');
require_once MODX_CORE_PATH . 'components/sendex/bootstrap.php';
sendexBootstrap($modx);

/** @var sxNewsletter $newsletter */
if ($newsletter = $modx->getObject('sxNewsletter', 1)) {
	$response = $newsletter->addQueues();
	if ($response !== true) {
		echo $response; die;
	}
}


Метод вернёт true или текст ошибки (в том числе если ни одной строки очереди не создано).

Объект sxSubscriber

Запись в таблице и есть подписка. При отписке строка удаляется. Поле code — уникальный код для ссылки отписки. Уникальность в рамках рассылки: пара (newsletter_id, email).

Объект sxQueue

  • send — отправка одного письма через sxQueueSender::sendOne

Перед отправкой строка «захватывается» (claim): параллельные cron-воркеры не отправят одно письмо дважды. При ошибке почты строка возвращается в очередь для повторной попытки. После успешной отправки строка удаляется.

Пакетная отправка (так работает cron):

$modx->addPackage('sendex', MODX_CORE_PATH . 'components/sendex/model/');
require_once MODX_CORE_PATH . 'components/sendex/bootstrap.php';
sendexBootstrap($modx);

sxQueueSender::flush($modx, array(
	'limit'     => $modx->getOption('sendex_queue_limit', null, 100, true),
	'logErrors' => true,
));


Cron-скрипт: /core/components/sendex/cron/send.php. Запуск из корня сайта:

php core/components/sendex/cron/send.php


События плагина

Подписка и отписка: sxOnBeforeSubscribe, sxOnSubscribe, sxOnBeforeUnsubscribe, sxOnUnsubscribe.
Очередь: sxOnBeforeAddQueues, sxOnAddQueues, sxOnBeforeQueueSend, sxOnQueueSend, sxOnQueueSendFailed, sxOnQueueFlushComplete.
События «Before» можно отменить через $modx->event->output('сообщение').

Обновление с 1.x

Установка и апгрейд прогоняют Phinx-миграции (core/components/sendex/migrations/). Таблицы переводятся на InnoDB и utf8mb4. В очереди поле subscriber_id ссылается на sxSubscriber.id, а не на modUser.id. После апгрейда существующие строки пересчитываются миграцией.

Что сделать на сайте

  1. Оформить шаблон письма, создать рассылку и указать шаблон в настройках.
  2. Подписать пользователей вручную, группой, через сниппет или API.
  3. По событию или по расписанию вызвать addQueues или sendToSubscribers.
  4. Отправить очередь из админки или через cron.

Документация: docs.modx.pro/komponentyi/sendex. Исходники и issue tracker: GitHub.

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[2.0.0-pl] - 2026-07-25

Security

  • [#51] Subscriber export goes through the authenticated manager connector (no public CSV under assets/); processor requires edit_document.
  • CSV cells that look like spreadsheet formulas are prefixed so Excel/LibreOffice do not execute them.
  • [#103] Mail header fields (email_from, email_reply, email_to) are sanitized to strip CR/LF/control characters before PHPMailer configuration.
  • [#103] Mgr newsletters grid escapes image src and fixes broken boolean renderer span tags to prevent stored XSS via crafted image values.
  • [#103] Frontend subscribe/unsubscribe adds optional CSRF token validation (sendex_csrf_protect).
  • [#103] Guest confirm flow adds per-email rate limiting (sendex_confirm_rate_limit, seconds; 0 disables).

Added

  • [#42] Frontend AJAX subscribe/unsubscribe: JSON {success, message, html} from snippet, default chunks + assets/components/sendex/js/web/sendex.js. Multi-widget pages scope POST via newsletter_id and optional widgetKey.
  • [#38] Guest subscribe can skip email confirmation: snippet &confirmEmail=0 or system setting sendex_confirm_email; domain helper subscribeGuest() keeps one subscribe path.
  • [#29] Mgr newsletter «Send to subscribers»: one action runs addQueues + sxQueueSender::flush for the newsletter (mgr/newsletter/send); button in grid row actions and update window.
  • [#46] Search subscribers and queue by email or username.
  • [#44] Plugin events for subscribe/unsubscribe (sxOnBeforeSubscribe, sxOnSubscribe, sxOnBeforeUnsubscribe, sxOnUnsubscribe).
  • PHPUnit coverage for subscribe/unsubscribe events (stubs, no MODX install).
  • [#72] Focused PHPUnit coverage for queue claim (#55), plus regression contracts for #52 / #58 / #61.
  • Phinx migrations: core/components/sendex/phinx.php, migrations/, install/upgrade resolver; metadata table {prefix}sendex_migrations.
  • [#104] Queue lifecycle plugin events: sxOnBeforeAddQueues, sxOnAddQueues, sxOnBeforeQueueSend, sxOnQueueSend, sxOnQueueSendFailed, sxOnQueueFlushComplete.
  • [#104] Subscribe/unsubscribe events pass source (snippet|ajax|confirm|mgr|guest).

Fixed

  • [#110] Mgr queue newsletter combo keeps the selected newsletter after click; MODX 3 tab/grid spacing aligned; manager menu uses envelope icon on MODX 3.
  • [#104] Guest merge no longer runs on OnBeforeUserActivate (only OnUserActivate / OnUserSave), so a cancelled activation cannot attach guests to an inactive user.
  • [#40] MODX 3 package install: registerNamespace sets assets_path; build script aliases modPackageBuilder, skips model regen on MODX 3 (preserves global sx* maps); mgr menu without modAction; Phinx migration property no longer conflicts with AbstractMigration::$tables on PHP 8.4.
  • [#74] MODX 3 mgr: SendexIndexManagerController aliases menu action index (no duplicate menu remap).
  • [#74] MODX 3 bootstrap: bootstrap.php for connector/mgr/cron; processor autoload + modProcessor aliases; sxModxCompat (mail/parser/registry) and sxUserProfile (user/profile placeholders); ExtJS mgr icon/menu polish on MODX 3.
  • [#42] Multi-widget AJAX: authenticated subscribe no longer breaks request scoping ($id shadowing); confirm link keeps newsletter_id; unsubscribe widget keeps widget_key; email links without sendex_widget_key route to the default snippet instance (empty widgetKey).
  • [#60] Mgr grids: empty checkbox selection no longer sends ids:''; alert via Sendex.utils.requireSelectedIds, row action falls back to menu.record.
  • [#59] Deleting a newsletter removes its sxQueue rows via xPDO composite Queues; upgrade migration purges queue rows left orphaned by earlier deletes.
  • [#57] addQueues returns an error when 0 queue rows were created (all subscribers skipped); mgr queue/add reports the created count on success.
  • [#55] Queue send claims the row (remove-before-send) so parallel cron/mgr workers do not double-deliver; mail failure requeues and logs; cron logs non-true send() results.
  • [#103] Queue claim uses atomic DELETE ... WHERE id = ? with rowCount() fallback-safe behavior for single-owner delivery.
  • [#105] Queue claim switches to UPDATE ... SET claimed_at, attempts = attempts + 1 ... WHERE claimed_at IS NULL with legacy delete fallback.
  • [#56] Unsubscribe from email: snippet resolves newsletter by subscriber code when snippet &id differs; default letter link includes newsletter_id (not MODX resource id).
  • [#67] / [#52] Schema and upgrade: Sendex tables use InnoDB; queue index renamed user_idsubscriber_id; addQueues stores sxSubscriber.id (not user_id); Phinx backfill remaps legacy queue rows (by user_id, guests by email).
  • [#54] isSubscribed matches by user_id OR email within a newsletter; unique key is (newsletter_id, email); guest rows attach user_id when the same email confirms as a user.
  • [#39] Guest rows merge onto modUser on OnUserActivate / OnUserSave (sxSubscriberMerge); registration does not create a second subscriber row for the same email.
  • [#103] Guest merge query filters user_id=0 + email at SQL level (no full scan in PHP).
  • [#58] confirmEmail restores registry hash when subscribe() fails so the confirm link stays usable; remaining TTL kept via _expires; checkEmail shares sxSubscribeRegistry::store.
  • [#61] sxSubscriber::save keeps existing code (generate only when empty) so unsubscribe links stay valid.
  • [#53] Snippet: authenticated user without Profile no longer triggers TypeError on PHP 8; merge via sxUserPlaceholders.
  • [#47] Adding a user group to a newsletter subscribes only active and unblocked users.
  • add_group: cast group_id and newsletter_id to int; require user Profile (innerJoin).
  • confirmEmail returns the subscribe() result when confirming a hash for another newsletter.
  • Mgr newsletter/update no longer clears active when the field is omitted from the request (partial saves, QA/API).
  • PHP 7.4–8.4: dynamic properties, null Profile in addQueues, PHPMailer ErrorInfo.
  • [#28] sxProcessorInput::parseIds accepts ids as array or CSV; queue send from snippet/code matches ExtJS runProcessor input.

Changed

  • [#68] ExtJS mgr grids share Sendex.grid.SelectionMixin (getSelectedIds, confirmWithSelection, ajaxWithSelection) instead of per-grid _getSelectedIds copy-paste.
  • [#73] Newsletter mgr getlist: COUNT runs on filters only; JOIN/COUNT(Subscribers)/GROUP BY moved to prepareQueryAfterCount via sxNewsletterListQuery.
  • [#103] Group subscribe loads only relevant existing rows (user_id/email subset) instead of all newsletter subscribers.
  • [#103] Subscriber schema adds user_id index via Phinx migration 20260725170000_subscriber_user_id_index.php.
  • [#105] Queue schema adds claim/retry fields (claimed_at, attempts, expires_at) and normalizes subscriber_id to NOT NULL DEFAULT 0.
  • [#105] Sendex tables are converted to utf8mb4_unicode_ci; subscriber email is backfilled to lowercase and uses case-insensitive utf8mb4 collation.
  • [#103] Mgr controller ACL now checks view_sendex or view_document; transport menu declares explicit view_document permission.
  • [#103] Frontend i18n cleanup: lexicon-based request failure text, guest/anonymous labels, and email placeholder.
  • [#103] CI adds integration smoke matrix (MODX 2.8/3.x, allow-failure), Clover coverage artifact, and tag-driven release workflow.
  • [#71] Confirm/subscribe reuses one sxSubscriber lookup (findSubscriber) instead of SELECT in isSubscribed plus a second SELECT in attachUserToSubscriber.
  • [#70] add_group bulk-subscribes group members via one query + chunked multi-row INSERT; mgr sync path skips per-row subscribe events (#44).
  • [#64] New queue rows store empty email_body (compact mode); body is rendered at send from newsletter template; legacy rows with stored HTML still send as-is.
  • [#63] addQueues batch-loads modUser + Profile (id:IN) once instead of N+1 per subscriber.
  • [#66] sxNewsletterMailer centralizes From/Reply-To/HTML setup for activation mail and queue delivery; activation reply-to uses email_reply like queue build.
  • [#69] Mgr processors share sxSendexProcessor (edit_document + requireIds / parseIds); get-list processors declare view_document.
  • [#62] Split sxNewsletter into sxNewsletterSubscription, sxNewsletterQueueBuilder, sxSendexEvent; model keeps a thin public API for processors/snippet.
  • Breaking (DB): existing sendex_* tables are converted to InnoDB on upgrade; queue subscriber_id meaning is sxSubscriber.id after backfill (join Queue→Subscriber for guests works). Soft FK to core modUser tables are not added.
  • add_group processor requires edit_document permission.
  • Newsletter create processor class renamed to sxNewsletterCreateProcessor.
  • subscribe() method name normalized to camelCase (PHP method names are case-insensitive; Subscribe() calls still work).
  • Declared PHP 7.4–8.4 support; CI lint matrix.
  • Track composer.lock in git (removed from .gitignore) for reproducible CI installs.
  • Package schema install/upgrade runs via Phinx instead of ad-hoc resolve.tables Manager calls.

[1.1.4-pl] - 2022-12-23

Added

  • [#37] Export subscriber email addresses from the manager.

Fixed

  • CSS bug for the usergroup combo.

[1.1.3-pl] - 2019-08-24

Added

  • [#32], [#33] Snippet property &msgClass for placeholder [[+class]].
  • [#31] Frontend messages.

Changed

  • [#30] Schema: phptype textstring.

[1.1.2-pl] - 2015-10-23

Added

  • [#23] Button to remove all letters from the queue.

[1.1.1-pl] - 2015-09-10

Fixed

  • Loading modUserProfile when creating a subscriber.

[1.1.0-pl] - 2015-08-19

Changed

  • Installation script improved for MODX 2.4.

[1.1.0-rc] - 2014-08-19

Added

  • Add all users of a group to a newsletter.
  • Multiselect in all grids.
  • Subscriber count in the newsletters grid.
  • Grid buttons for touch devices.
  • Font Awesome icons for MODX < 2.3.
  • [#20] “Send all” button on the queues grid.

Changed

  • UI improvements.
  • [#21] Compatibility with MODX 2.3.

[1.0.0-pl] - 2014-03-27

Added

  • Multi-remove and multi-send.
  • [#11] $_GET flags subscribed, unsubscribed, confirmed.

Fixed

  • [#1] Image URLs.

[1.0.0-rc2] - 2014-03-07

Fixed

  • Various small fixes.

[1.0.0-rc1] - 2013-12-30

Fixed

  • Template caching.

[1.0.0-beta] - 2013-12-19

Added

  • Initial release.

Последние обсуждения в сообществе MODX.pro