From 67016ed549ff160126ed8aa73e28a37a74642f10 Mon Sep 17 00:00:00 2001
From: Kevin Adametz
Date: Wed, 14 Oct 2026 10:30:04 +0000
Subject: [PATCH] =?UTF-8?q?mail:=20Pr=C3=BCfmail=20f=C3=BChrt=20Pressekont?=
=?UTF-8?q?akte=20per=20Anmeldelink=20zur=20Meldung?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Die Mail „Ihre Pressemitteilung wird persönlich geprüft“ verlinkte auch
bei API-Meldungen in den Kundenbereich. Empfänger ohne Konto landeten auf
der Passwort-Anmeldung. Wie in der Veröffentlichungs- und Ablehnungsmail
schickt jetzt ein Klick einen Anmeldelink an die Adresse der Mail; die
Zugangsseite übernimmt die Herkunft mail_review für den Funnel.
Changelog: Verbessert
Public: Auch die Mail zur persönlichen Prüfung führt Pressekontakte ohne Passwort zu ihrer Meldung.
Co-Authored-By: Claude Opus 5.5
---
app/Mail/PressReleaseHeldForReview.php | 13 +++++++++
app/Services/Auth/MailAccessLink.php | 2 +-
.../press-release/held-for-review.blade.php | 16 ++++++++++-
.../views/livewire/auth/mail-access.blade.php | 6 ++---
tests/Feature/Auth/MailAccessTest.php | 27 +++++++++++++++++++
5 files changed, 59 insertions(+), 5 deletions(-)
diff --git a/app/Mail/PressReleaseHeldForReview.php b/app/Mail/PressReleaseHeldForReview.php
index 385fa538..27e2cf8d 100644
--- a/app/Mail/PressReleaseHeldForReview.php
+++ b/app/Mail/PressReleaseHeldForReview.php
@@ -7,6 +7,7 @@ use App\Enums\EmailNotificationType;
use App\Mail\Concerns\Unsubscribable;
use App\Models\PressRelease;
use App\Models\User;
+use App\Services\Auth\MailAccessLink;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
@@ -35,6 +36,16 @@ class PressReleaseHeldForReview extends Mailable
public int $maxAttempts;
+ /**
+ * Empfänger ohne Konto (API-Meldung, Roadmap 6.17): Adresse dieser Mail
+ * und Link, der einen Anmeldelink an sie schickt. Der Link zum Kundenbereich
+ * führte sonst auf eine Anmeldeseite für ein Konto, das der Empfänger
+ * nicht kennt.
+ */
+ public ?string $accessEmail = null;
+
+ public ?string $accessUrl = null;
+
public function __construct(
public readonly User $user,
public readonly PressRelease $pressRelease,
@@ -55,6 +66,8 @@ class PressReleaseHeldForReview extends Mailable
]);
if ($recipientEmail !== null && mb_strtolower(trim($recipientEmail)) !== mb_strtolower((string) $user->email)) {
+ $this->accessEmail = trim($recipientEmail);
+ $this->accessUrl = app(MailAccessLink::class)->url($this->accessEmail, $pressRelease, ConversionSource::MailReview);
$this->configureAddressUnsubscribe($recipientEmail, EmailNotificationType::PressReleaseRejected);
} else {
$this->configureUnsubscribe($user, EmailNotificationType::PressReleaseRejected);
diff --git a/app/Services/Auth/MailAccessLink.php b/app/Services/Auth/MailAccessLink.php
index b4d816d4..b2eb012a 100644
--- a/app/Services/Auth/MailAccessLink.php
+++ b/app/Services/Auth/MailAccessLink.php
@@ -11,7 +11,7 @@ use Illuminate\Support\Facades\URL;
/**
* „Anmeldelink an diese Adresse“ aus einer Kundenmail (Roadmap 6.17).
*
- * Veröffentlichungs- und Ablehnungsmails zu API-Meldungen gehen an den
+ * Veröffentlichungs-, Ablehnungs- und Prüfmails zu API-Meldungen gehen an den
* Pressekontakt oder die Firmen-E-Mail — Empfänger, die aus ihrer Sicht kein
* Konto haben. Der Link führt auf eine Seite, die mit einem Klick einen
* Anmeldelink an genau die Adresse schickt, an die die Mail ging, und nach
diff --git a/resources/views/emails/press-release/held-for-review.blade.php b/resources/views/emails/press-release/held-for-review.blade.php
index 564e6c43..e2463c8e 100644
--- a/resources/views/emails/press-release/held-for-review.blade.php
+++ b/resources/views/emails/press-release/held-for-review.blade.php
@@ -24,7 +24,21 @@
- Stand der Prüfung ansehen
+ @if ($accessUrl)
+ {{-- Empfänger ohne Konto (API-Meldung, Roadmap 6.17): ein Klick
+ schickt einen Anmeldelink an genau diese Adresse. --}}
+
+ @else
+ Stand der Prüfung ansehen
+ @endif
Bei Fragen erreichen Sie uns über den Hilfe-Bereich auf pressekonto.
diff --git a/resources/views/livewire/auth/mail-access.blade.php b/resources/views/livewire/auth/mail-access.blade.php
index e22fc878..bf566bf7 100644
--- a/resources/views/livewire/auth/mail-access.blade.php
+++ b/resources/views/livewire/auth/mail-access.blade.php
@@ -17,8 +17,8 @@ use Livewire\Component;
/**
* Anmeldelink an die Adresse, an die eine Kundenmail ging (Roadmap 6.17).
*
- * Erreichbar nur über den signierten Link aus der Veröffentlichungs- oder
- * Ablehnungsmail (MailAccessLink). Ein Klick schickt den Anmeldelink; nach
+ * Erreichbar nur über den signierten Link aus der Veröffentlichungs-,
+ * Ablehnungs- oder Prüfmail (MailAccessLink). Ein Klick schickt den Anmeldelink; nach
* der Anmeldung landet der Empfänger auf der Meldung. Die Seite zeigt die
* Adresse nur maskiert.
*/
@@ -98,7 +98,7 @@ new #[Layout('components.layouts.auth.pressekonto', ['heading' => 'Ohne Passwort
{
$source = ConversionSource::parse((string) request()->query('src'));
- return in_array($source, [ConversionSource::MailPublish, ConversionSource::MailReject], true) ? $source : ConversionSource::Organic;
+ return in_array($source, [ConversionSource::MailPublish, ConversionSource::MailReject, ConversionSource::MailReview], true) ? $source : ConversionSource::Organic;
}
private function pressRelease(int $id): ?PressRelease
diff --git a/tests/Feature/Auth/MailAccessTest.php b/tests/Feature/Auth/MailAccessTest.php
index 9cd709ea..81f8d590 100644
--- a/tests/Feature/Auth/MailAccessTest.php
+++ b/tests/Feature/Auth/MailAccessTest.php
@@ -5,6 +5,7 @@ use App\Enums\ConversionSource;
use App\Enums\Portal;
use App\Enums\PressReleaseStatus;
use App\Mail\MagicLoginLink;
+use App\Mail\PressReleaseHeldForReview;
use App\Mail\PressReleasePublished;
use App\Mail\PressReleaseRejected;
use App\Models\Company;
@@ -93,6 +94,32 @@ test('the rejection mail tells recipients without an account that they can revis
->and($own)->not->toContain('Anmeldelink anfordern');
});
+test('the held-for-review mail lets recipients without an account see the status via a sign-in link', function () {
+ /** @var TestCase $this */
+ $release = apiRelease(['status' => PressReleaseStatus::Review->value]);
+ $mail = new PressReleaseHeldForReview($release->user, $release, false, 'Frau Muster', 'presse@firma.de');
+ $html = $mail->render();
+
+ expect($mail->accessUrl)->toContain('/zugang?')
+ ->and(accessQuery($mail->accessUrl)['src'])->toBe(ConversionSource::MailReview->value)
+ ->and($html)->toContain('Sie müssen nichts tun')
+ ->and($html)->toContain('Den Stand der Prüfung sehen?')
+ ->and($html)->toContain('weder ein Konto noch ein Passwort')
+ ->and($html)->toContain('presse@firma.de')
+ ->and($html)->toContain('Anmeldelink anfordern')
+ ->and($html)->not->toContain('Stand der Prüfung ansehen');
+
+ // Die Zugangsseite übernimmt die Herkunft aus der Prüfmail.
+ $this->get($mail->accessUrl)->assertOk();
+ expect(ConversionEvent::query()->where('type', ConversionEventType::ManagePageOpened->value)->sole()->source)
+ ->toBe(ConversionSource::MailReview);
+
+ // Wer selbst eingereicht hat, öffnet die Meldung wie bisher im Konto.
+ $own = (new PressReleaseHeldForReview($release->user, $release, false))->render();
+ expect($own)->toContain('Stand der Prüfung ansehen')
+ ->and($own)->not->toContain('Anmeldelink anfordern');
+});
+
// --------------------------------------------------------- Zugangsseite
test('the access page only opens with a valid signature and shows the address masked', function () {