mail: Anrede an den tatsächlichen Empfänger binden

Bei Einreichungen über die API geht die Veröffentlichungs- oder
Ablehnungsmail nicht an das API-Konto, sondern an den Pressekontakt der
Meldung oder ersatzweise an die Firmen-Adresse. Die Anrede stammte aber
weiterhin vom Konto — der Empfänger wurde also mit dem Namen des
Einreichers begrüßt, bei connektar mit dem der Agentur.

Adresse und Name werden jetzt gemeinsam bestimmt. Ohne hinterlegten Namen
bleibt er leer und das Template grüßt neutral, statt einen falschen zu raten.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-01 11:07:23 +00:00
parent e634af0330
commit f6934f54e1
6 changed files with 61 additions and 9 deletions

View file

@ -26,10 +26,18 @@ class PressReleasePublished extends Mailable
*/
public array $portalLinks;
/**
* Name für die Anrede — bei API-Einlieferungen der Pressekontakt bzw.
* die Firma, nie der Kontoname des API-Einlieferers.
*/
public string $recipientName;
public function __construct(
public readonly User $user,
public readonly PressRelease $pressRelease,
?string $recipientName = null,
) {
$this->recipientName = $recipientName ?? $user->name;
$this->showUrl = $user->canAccessAdmin()
? route('admin.press-releases.show', $pressRelease->id)
: route('me.press-releases.show', $pressRelease->id);

View file

@ -18,11 +18,19 @@ class PressReleaseRejected extends Mailable
public string $editUrl;
/**
* Name für die Anrede — bei API-Einlieferungen der Pressekontakt bzw.
* die Firma, nie der Kontoname des API-Einlieferers.
*/
public string $recipientName;
public function __construct(
public readonly User $user,
public readonly PressRelease $pressRelease,
public readonly ?string $reason = null,
?string $recipientName = null,
) {
$this->recipientName = $recipientName ?? $user->name;
$this->editUrl = $user->canAccessAdmin()
? route('admin.press-releases.edit', $pressRelease->id)
: route('me.press-releases.edit', $pressRelease->id);

View file

@ -669,6 +669,7 @@ class PressReleaseService
// ist die Quelle des letzten Review-Uebergangs im Status-Log — der
// Kontotyp ist nicht verlaesslich (connektar ist z. B. 'agency').
$recipient = $user->email;
$recipientName = $user->name;
$isApiSubmitter = $pressRelease->statusLogs()
->where('to_status', PressReleaseStatus::Review->value)
->orderByDesc('id')
@ -676,10 +677,28 @@ class PressReleaseService
if ($isApiSubmitter) {
// Scope-frei auflösen: der Mail-Versand läuft auch aus Jobs/
// Kontexten ohne passenden Portal-Host (PortalScope).
$recipient = $pressRelease->contacts()->withoutGlobalScopes()->whereNotNull('email')->value('email')
?? Company::withoutGlobalScopes()->find($pressRelease->company_id)?->email
?: $user->email;
// Kontexten ohne passenden Portal-Host (PortalScope). Anrede und
// Adresse gehören zusammen — sobald die Mail an Pressekontakt
// oder Firma geht, darf nie der Name des API-Kontos gegrüßt
// werden. Ohne hinterlegten Namen bleibt $recipientName leer,
// das Template grüßt dann neutral.
$contact = $pressRelease->contacts()
->withoutGlobalScopes()
->whereNotNull('contacts.email')
->where('contacts.email', '!=', '')
->orderBy('contacts.id')
->first();
$company = Company::withoutGlobalScopes()->find($pressRelease->company_id);
$companyName = trim((string) $company?->name);
if ($contact) {
$recipient = $contact->email;
$recipientName = trim($contact->first_name.' '.$contact->last_name) ?: $companyName;
} elseif ($company && filled($company->email)) {
$recipient = $company->email;
$recipientName = $companyName;
}
}
// Opt-out-Gate: Benachrichtigungs-Mails sind je Typ abbestellbar
@ -701,8 +720,8 @@ class PressReleaseService
}
$mailable = match ($event) {
'published' => new PressReleasePublished($user, $pressRelease),
'rejected' => new PressReleaseRejected($user, $pressRelease, $reason),
'published' => new PressReleasePublished($user, $pressRelease, $recipientName),
'rejected' => new PressReleaseRejected($user, $pressRelease, $reason, $recipientName),
default => null,
};