You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[#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.
[#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_id → subscriber_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.
[#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.
[#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.