Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/components/sendex/docs/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#111] Mgr row-action and menu icons no longer force `font-family: "Font Awesome 5 Free"` (Sendex does not load FA5); icons inherit the mgr icon font on MODX 2.3+/3.x or bundled FA4 on older MODX.
- [#25666] Transport package built on MODX 3.x stored vehicle class as `xPDO\Transport\xPDOObjectVehicle`, which MODX 2.8.8/2.8.9 cannot load (install fails with ~30 "Could not load class" errors). Release build now runs on MODX 2.x so the manifest uses the legacy vehicle format that installs on both MODX 2.8+ and 3.x.
- Queue claim columns (`claimed_at`, `attempts`, `expires_at`) are ensured on Sendex bootstrap when Phinx did not apply `#105` migration, avoiding `Unknown column sxQueue.claimed_at` on live upgrades.
- [#123] Queue send resolves From/Reply headers from the linked newsletter at delivery time, so correcting `email_from` in mgr applies to pending queue rows instead of the stale snapshot from `addQueues`.

## [2.0.0-pl] - 2026-07-25

Expand Down
18 changes: 14 additions & 4 deletions core/components/sendex/model/sendex/sxnewslettermailer.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
* Post-#62 mail paths (issue originally named sxNewsletter::send/checkEmail):
* - activation: snippet → Sendex::sendEmail → buildActivationMessage
* - queue build: sxNewsletterQueueBuilder::addQueues → compact row (headers only)
* - queue send: sxQueueSender::deliverMail → messageFromQueue (+ sxQueueBodyRenderer when body empty)
* - queue send: sxQueueSender::deliverMail → messageFromQueue (+ sxQueueBodyRenderer when body empty).
* From/Reply headers come from the linked newsletter at send time (#123), not the queue snapshot.
*/
class sxNewsletterMailer
{
Expand Down Expand Up @@ -107,13 +108,22 @@ public static function messageFromQueue($queue)
$body = $rendered;
}

$headers = self::resolveHeaders($queue, $queue->xpdo);
$newsletterId = (int) self::readField($queue, 'newsletter_id');
if ($newsletterId > 0) {
$newsletter = $queue->xpdo->getObject('sxNewsletter', $newsletterId);
if ($newsletter) {
$headers = self::resolveHeaders($newsletter, $queue->xpdo);
}
}

return array(
'email_to' => self::sanitizeHeader(self::readField($queue, 'email_to')),
'email_body' => $body,
'email_from' => self::sanitizeHeader(self::readField($queue, 'email_from')),
'email_from_name' => self::sanitizeHeader(self::readField($queue, 'email_from_name')),
'email_from' => $headers['email_from'],
'email_from_name' => $headers['email_from_name'],
'email_subject' => self::readField($queue, 'email_subject'),
'email_reply' => self::sanitizeHeader(self::readField($queue, 'email_reply')),
'email_reply' => $headers['email_reply'],
);
}

Expand Down
30 changes: 30 additions & 0 deletions tests/Unit/NewsletterMailerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,36 @@ public function testMessageFromQueueSanitizesHeaderFields()
$this->assertSame('reply@example.com Bcc:bad@example.com', $message['email_reply']);
}

public function testMessageFromQueueUsesNewsletterHeadersWhenLinked()
{
$newsletter = new TestableNewsletter($this->modx);
$newsletter->set('id', 10);
$newsletter->set('email_from', 'fixed@example.com');
$newsletter->set('email_from_name', 'Fixed Sender');
$newsletter->set('email_reply', 'reply-fixed@example.com');
$this->modx->newsletters[10] = $newsletter;

$queue = new sxQueue($this->modx);
$queue->fromArray(array(
'id' => 1,
'newsletter_id' => 10,
'subscriber_id' => 1,
'email_to' => 'to@example.com',
'email_subject' => 'Subject',
'email_body' => 'Body',
'email_from' => 'bad-address',
'email_from_name' => 'Old Name',
'email_reply' => 'bad-reply',
));

$message = sxNewsletterMailer::messageFromQueue($queue);

$this->assertSame('fixed@example.com', $message['email_from']);
$this->assertSame('Fixed Sender', $message['email_from_name']);
$this->assertSame('reply-fixed@example.com', $message['email_reply']);
$this->assertSame('Subject', $message['email_subject']);
}

public function testSanitizeHeaderStripsControlCharacters()
{
$this->assertSame('from@example.comBcc:bad@example.com', sxNewsletterMailer::sanitizeHeader("from@example.com\x00Bcc:bad@example.com"));
Expand Down
Loading