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

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

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

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

В сетке рассылок есть действие «Отправить подписчикам»: одним кликом вызываются addQueues и отправка очереди. Отправку можно запускать по одному письму, пакетом из админки или по cron.
Сниппет Sendex
Вызов на странице:
Сниппет показывает форму подписки и отписки. Авторизованный пользователь нажимает кнопку. Гость подтверждает 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
Генерация очереди через API:
Метод вернёт true или текст ошибки (в том числе если ни одной строки очереди не создано).
Объект sxSubscriber
Запись в таблице и есть подписка. При отписке строка удаляется. Поле code — уникальный код для ссылки отписки. Уникальность в рамках рассылки: пара (newsletter_id, email).
Объект sxQueue
Перед отправкой строка «захватывается» (claim): параллельные cron-воркеры не отправят одно письмо дважды. При ошибке почты строка возвращается в очередь для повторной попытки. После успешной отправки строка удаляется.
Пакетная отправка (так работает cron):
Cron-скрипт: /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. После апгрейда существующие строки пересчитываются миграцией.
Что сделать на сайте
- 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 — добавляет письма в очередь для всех подписчиков рассылки
- sendToSubscribers — addQueues + отправка очереди этой рассылки (то же, что кнопка в админке)
- 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. После апгрейда существующие строки пересчитываются миграцией.
Что сделать на сайте
- Оформить шаблон письма, создать рассылку и указать шаблон в настройках.
- Подписать пользователей вручную, группой, через сниппет или API.
- По событию или по расписанию вызвать addQueues или sendToSubscribers.
- Отправить очередь из админки или через 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 requiresedit_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
srcand 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;0disables).
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 vianewsletter_idand optionalwidgetKey. - [#38] Guest subscribe can skip email confirmation: snippet
&confirmEmail=0or system settingsendex_confirm_email; domain helpersubscribeGuest()keeps one subscribe path. - [#29] Mgr newsletter «Send to subscribers»: one action runs
addQueues+sxQueueSender::flushfor 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(onlyOnUserActivate/OnUserSave), so a cancelled activation cannot attach guests to an inactive user. - [#40] MODX 3 package install:
registerNamespacesetsassets_path; build script aliasesmodPackageBuilder, skips model regen on MODX 3 (preserves globalsx*maps); mgr menu withoutmodAction; Phinx migration property no longer conflicts withAbstractMigration::$tableson PHP 8.4. - [#74] MODX 3 mgr:
SendexIndexManagerControlleraliases menu actionindex(no duplicate menu remap). - [#74] MODX 3 bootstrap:
bootstrap.phpfor connector/mgr/cron; processor autoload +modProcessoraliases;sxModxCompat(mail/parser/registry) andsxUserProfile(user/profile placeholders); ExtJS mgr icon/menu polish on MODX 3. - [#42] Multi-widget AJAX: authenticated subscribe no longer breaks request scoping (
$idshadowing); confirm link keepsnewsletter_id; unsubscribe widget keepswidget_key; email links withoutsendex_widget_keyroute to the default snippet instance (emptywidgetKey). - [#60] Mgr grids: empty checkbox selection no longer sends
ids:''; alert viaSendex.utils.requireSelectedIds, row action falls back tomenu.record. - [#59] Deleting a newsletter removes its
sxQueuerows via xPDO compositeQueues; upgrade migration purges queue rows left orphaned by earlier deletes. - [#57]
addQueuesreturns an error when 0 queue rows were created (all subscribers skipped); mgrqueue/addreports 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 = ?withrowCount()fallback-safe behavior for single-owner delivery. - [#105] Queue claim switches to
UPDATE ... SET claimed_at, attempts = attempts + 1 ... WHERE claimed_at IS NULLwith legacy delete fallback. - [#56] Unsubscribe from email: snippet resolves newsletter by subscriber
codewhen snippet&iddiffers; default letter link includesnewsletter_id(not MODX resourceid). - [#67] / [#52] Schema and upgrade: Sendex tables use InnoDB; queue index renamed
user_id→subscriber_id;addQueuesstoressxSubscriber.id(notuser_id); Phinx backfill remaps legacy queue rows (by user_id, guests by email). - [#54]
isSubscribedmatches byuser_idORemailwithin a newsletter; unique key is(newsletter_id, email); guest rows attachuser_idwhen the same email confirms as a user. - [#39] Guest rows merge onto
modUseronOnUserActivate/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]
confirmEmailrestores registry hash whensubscribe()fails so the confirm link stays usable; remaining TTL kept via_expires;checkEmailsharessxSubscribeRegistry::store. - [#61]
sxSubscriber::savekeeps existingcode(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: castgroup_idandnewsletter_idto int; require user Profile (innerJoin).confirmEmailreturns thesubscribe()result when confirming a hash for another newsletter.- Mgr
newsletter/updateno longer clearsactivewhen the field is omitted from the request (partial saves, QA/API). - PHP 7.4–8.4: dynamic properties, null Profile in
addQueues, PHPMailerErrorInfo. - [#28]
sxProcessorInput::parseIdsacceptsidsas array or CSV; queue send from snippet/code matches ExtJSrunProcessorinput.
Changed
- [#68] ExtJS mgr grids share
Sendex.grid.SelectionMixin(getSelectedIds,confirmWithSelection,ajaxWithSelection) instead of per-grid_getSelectedIdscopy-paste. - [#73] Newsletter mgr
getlist: COUNT runs on filters only; JOIN/COUNT(Subscribers)/GROUP BY moved toprepareQueryAfterCountviasxNewsletterListQuery. - [#103] Group subscribe loads only relevant existing rows (
user_id/emailsubset) instead of all newsletter subscribers. - [#103] Subscriber schema adds
user_idindex via Phinx migration20260725170000_subscriber_user_id_index.php. - [#105] Queue schema adds claim/retry fields (
claimed_at,attempts,expires_at) and normalizessubscriber_idtoNOT NULL DEFAULT 0. - [#105] Sendex tables are converted to
utf8mb4_unicode_ci; subscriber email is backfilled to lowercase and uses case-insensitiveutf8mb4collation. - [#103] Mgr controller ACL now checks
view_sendexorview_document; transport menu declares explicitview_documentpermission. - [#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
sxSubscriberlookup (findSubscriber) instead of SELECT inisSubscribedplus a second SELECT inattachUserToSubscriber. - [#70]
add_groupbulk-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]
addQueuesbatch-loadsmodUser+ Profile (id:IN) once instead of N+1 per subscriber. - [#66]
sxNewsletterMailercentralizes From/Reply-To/HTML setup for activation mail and queue delivery; activation reply-to usesemail_replylike queue build. - [#69] Mgr processors share
sxSendexProcessor(edit_document+requireIds/parseIds); get-list processors declareview_document. - [#62] Split
sxNewsletterintosxNewsletterSubscription,sxNewsletterQueueBuilder,sxSendexEvent; model keeps a thin public API for processors/snippet. - Breaking (DB): existing
sendex_*tables are converted to InnoDB on upgrade; queuesubscriber_idmeaning issxSubscriber.idafter backfill (join Queue→Subscriber for guests works). Soft FK to coremodUsertables are not added. add_groupprocessor requiresedit_documentpermission.- 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.lockin git (removed from.gitignore) for reproducible CI installs. - Package schema install/upgrade runs via Phinx instead of ad-hoc
resolve.tablesManager 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
&msgClassfor placeholder[[+class]]. - [#31] Frontend messages.
Changed
- [#30] Schema:
phptypetext→string.
[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
modUserProfilewhen 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]
$_GETflagssubscribed,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