From 7102d01bcc7a30dac87ca86e0dc3df75eea8cbf6 Mon Sep 17 00:00:00 2001 From: nexxo Date: Tue, 28 Jul 2026 03:50:02 +0200 Subject: [PATCH] Keep the purpose mailer through the queue instead of losing it to the default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mail::to($x)->queue($mail) resolves the DEFAULT mailer, and Illuminate\Mail\Mailer::queue() (vendor Mailer.php:482) does `$view->mailer($this->name)->queue($this->queue)` — overwriting MaintenanceAnnouncementMail/MaintenanceCancelledMail's own 'cp_maintenance' with the default mailer's name ('array' in tests, 'log'/whatever MAIL_MAILER is in prod) before the job is even built. Both mails kept sending through the old .env account instead of MailboxTransport. CloudReady was never affected — MailChannel.php:66 preserves a notification's own mailer. Fix: name the mailer before queueing — Mail::mailer($mail->mailer)->to(...). $this->name on the resolved Mailer is then already $mail->mailer, so the vendor overwrite becomes a no-op instead of a silent downgrade. Confirmed by reading the vendor source, not assumed. SenderAddressTest's "names the purpose mailer..." test asserted $mail->mailer on a bare, never-queued mailable, which proves nothing about what survives queueing — the constructor sets it right regardless of what MaintenanceNotifier does with it. Replaced it with one that drives MaintenanceNotifier::deliver() under Queue::fake() and inspects the pushed SendQueuedMailable's mailable. Verified it fails against the pre-fix code first (the pushed job carried mailer = 'array'), then verified the fix makes it pass, then mutated the fix away and watched it fail again. --- .../Maintenance/MaintenanceNotifier.php | 9 +++++- tests/Feature/Mail/SenderAddressTest.php | 32 ++++++++++++++++--- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/app/Services/Maintenance/MaintenanceNotifier.php b/app/Services/Maintenance/MaintenanceNotifier.php index a6cfa22..5be9c42 100644 --- a/app/Services/Maintenance/MaintenanceNotifier.php +++ b/app/Services/Maintenance/MaintenanceNotifier.php @@ -80,7 +80,14 @@ class MaintenanceNotifier $mail->notificationId = $delivery->id; try { - Mail::to($customer->email) + // Mail::to() alone resolves the DEFAULT mailer. Illuminate\Mail\Mailer::queue() + // then runs `$view->mailer($this->name)->queue(...)` — that overwrites the + // mailable's own purpose mailer (set in its constructor, e.g. 'cp_maintenance') + // with the default's name before the job is even built. Naming the mailer here + // makes that overwrite a no-op: $this->name on the resolved Mailer is already + // $mail->mailer, so it writes back the same value. + Mail::mailer($mail->mailer) + ->to($customer->email) ->locale($customer->locale ?: config('app.locale')) ->queue($mail); } catch (\Throwable $e) { diff --git a/tests/Feature/Mail/SenderAddressTest.php b/tests/Feature/Mail/SenderAddressTest.php index 37426f5..1a8f6ac 100644 --- a/tests/Feature/Mail/SenderAddressTest.php +++ b/tests/Feature/Mail/SenderAddressTest.php @@ -8,8 +8,11 @@ use App\Models\Mailbox; use App\Models\MaintenanceWindow; use App\Notifications\CloudReady; use App\Services\Mail\MailPurpose; +use App\Services\Maintenance\MaintenanceNotifier; use App\Support\Settings; +use Illuminate\Mail\SendQueuedMailable; use Illuminate\Support\Facades\Crypt; +use Illuminate\Support\Facades\Queue; beforeEach(function () { config()->set('admin_access.secrets_key', 'base64:'.base64_encode(random_bytes(32))); @@ -61,12 +64,33 @@ it('sets Reply-To on an answerable mailbox, so a customer reply reaches a human' }); it('names the purpose mailer so the queue worker picks the right transport', function () { - $mail = new MaintenanceAnnouncementMail( - MaintenanceWindow::factory()->create(), - Customer::factory()->create(), + // A bare, never-queued mailable proves nothing here: the constructor sets + // $this->mailer correctly regardless of what MaintenanceNotifier does. + // Illuminate\Mail\Mailer::queue() (vendor Mailer.php) runs + // `$view->mailer($this->name)->queue(...)` — if MaintenanceNotifier queues + // through the bare `Mail` facade instead of `Mail::mailer($mail->mailer)`, + // that resolves the DEFAULT mailer ('array' in tests) and overwrites the + // mailable's own 'cp_maintenance' with it BEFORE the queued job is even + // built. The only way to see that is to actually queue it and inspect the + // job that comes out the other side. + Mailbox::factory()->create(['key' => 'no-reply', 'address' => 'no-reply@clupilot.com', 'no_reply' => true]); + Settings::set(MailPurpose::settingKey(MailPurpose::MAINTENANCE), 'no-reply'); + + Queue::fake(); + + $window = MaintenanceWindow::factory()->create(); + $customer = Customer::factory()->create(); + + app(MaintenanceNotifier::class)->deliver( + $window, + $customer, + 'announcement', + new MaintenanceAnnouncementMail($window, $customer), ); - expect($mail->mailer)->toBe('cp_maintenance'); + Queue::assertPushed(SendQueuedMailable::class, function (SendQueuedMailable $job) { + return $job->mailable->mailer === 'cp_maintenance'; + }); }); it('gives the cancellation mail the same sender as the announcement', function () {