From ea54387a5e6ee450b7bf1517a4eb8f81d36ef1c7 Mon Sep 17 00:00:00 2001 From: nexxo Date: Mon, 3 Aug 2026 21:13:36 +0200 Subject: [PATCH] Eine verschickte Mail hinterlaesst wieder eine Zeile, nicht zwei MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit App\Listeners\RecordSentMail hoert auf MessageSent, und Laravel meldet die Zuhoerer in app/Listeners von selbst an. Die zusaetzliche Anmeldung von Hand im AppServiceProvider wirkte deshalb nicht als Bekraeftigung, sondern als zweiter Zuhoerer: jeder Versand wurde zweimal verbucht. An einer einzelnen Mail faellt das niemandem auf — der Trockenlauf des Versandtakts schickte fuenfzig und fand hundert Zeilen. Alle zweiundzwanzig echten Zeilen im Register sind entsprechend Doppelungen; das sind Daten, ueber die der Betreiber entscheidet, und sie bleiben hier unangetastet. Die Handanmeldung ist weg, ihre Begruendung steht an ihrer Stelle. Ein Test in MailRegisterTest nagelt eine Mail auf genau eine Zeile fest, egal wie der Zuhoerer angemeldet ist. Co-Authored-By: Claude Opus 5 --- app/Providers/AppServiceProvider.php | 15 +++++++++------ tests/Feature/Admin/MailRegisterTest.php | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 3917aa0..5504271 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -6,7 +6,6 @@ use App\Http\Middleware\EnsureAdmin; use App\Http\Middleware\EnsureCustomerActive; use App\Http\Middleware\RestrictAdminHost; use App\Http\Middleware\RestrictConsoleNetwork; -use App\Listeners\RecordSentMail; use App\Listeners\RecordSignInDevice; use App\Mail\MaintenanceCancelledMail; use App\Mail\Transport\MailboxTransport; @@ -205,11 +204,15 @@ class AppServiceProvider extends ServiceProvider return null; }); - // The register of what this installation has sent, and to whom. One - // listener rather than a call in each mailable: a register that has to - // be remembered at every call site is a register with holes in it, and - // the holes are the mails nobody thought about. - Event::listen(MessageSent::class, RecordSentMail::class); + // Das Register steht hier bewusst NICHT. + // + // App\Listeners\RecordSentMail hört auf MessageSent, und Laravel meldet + // jeden Zuhörer in app/Listeners von selbst an. Eine zweite Anmeldung + // von Hand wirkt nicht wie eine Bekräftigung, sondern wie ein zweiter + // Zuhörer: der Auftritt zählte doppelt, und jede verschickte Mail + // hinterließ zwei Zeilen im Register. Aufgefallen ist es erst, als ein + // Trockenlauf mit fünfzig Mails hundert Zeilen schrieb — an einer + // einzelnen Mail sieht man es nicht. // Stamp a maintenance-notification ledger row as delivered only once the // mail is actually sent (the X-CP-Notification header carries the id). diff --git a/tests/Feature/Admin/MailRegisterTest.php b/tests/Feature/Admin/MailRegisterTest.php index 5752cc8..bfbf1dd 100644 --- a/tests/Feature/Admin/MailRegisterTest.php +++ b/tests/Feature/Admin/MailRegisterTest.php @@ -34,6 +34,24 @@ it('writes a row for every mail the application sends', function () { ->and($row->customer_id)->toBe($customer->id); }); +/** + * Eine Mail, eine Zeile. + * + * RecordSentMail liegt in app/Listeners, und Laravel meldet die Zuhörer dort + * von selbst an. Eine zweite Anmeldung von Hand im AppServiceProvider wirkte + * deshalb nicht wie eine Bekräftigung, sondern wie ein zweiter Zuhörer: jede + * Mail hinterließ zwei Zeilen. An einer einzelnen Mail fällt das niemandem auf + * — es fiel bei einem Trockenlauf auf, der fünfzig Mails verschickte und + * hundert Zeilen vorfand. + */ +it('writes exactly one row per recipient, however the listener is registered', function () { + $customer = Customer::factory()->create(); + + Mail::to($customer->email)->send(new OperatorMessageMail($customer, 'Betreff', 'Text')); + + expect(SentMail::query()->where('to', $customer->email)->count())->toBe(1); +}); + it('records the send, not the intention', function () { // On MessageSent, not MessageSending: a row here has to mean the transport // accepted it. Recording intentions would fill the register with mails that