admin: internes Newsletter-Modul mit Filtern, Protokoll und Abmeldung
Neues Modul unter Admin ▸ Newsletter (nur users:manage): Kampagnen mit Betreff + leicht formatiertem Text (Purifier-Profil newsletter: fett/ kursiv, Listen, Links) im zentralen Mail-Layout. Empfänger-Filter auf der User-Datenbank: Portal, Legacy-Vertrag (Bestandsschutz), Login innerhalb/außerhalb X Monaten, Sortierung nach Login/Erstellung — mit Live-Zähler und Vorschau im Editor. Versand friert die Empfängerliste als Protokoll ein (newsletter_ campaign_recipients) und stellt pro Empfänger einen Queue-Job ein; der Opt-out wird im Worker erneut geprüft. Jede Mail trägt den signierten One-Click-Abmelde-Link (neuer EmailNotificationType:: Newsletter, bestehende Unsubscribe-Infrastruktur inkl. RFC 8058); die Abmeldung greift sofort für alle künftigen Auswahlen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
d411e6ce7f
commit
215cbcbdbf
19 changed files with 1560 additions and 5 deletions
71
app/Models/NewsletterCampaign.php
Normal file
71
app/Models/NewsletterCampaign.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\NewsletterCampaignStatus;
|
||||
use App\Services\Newsletter\CampaignRecipients;
|
||||
use Database\Factories\NewsletterCampaignFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Mews\Purifier\Facades\Purifier;
|
||||
|
||||
/**
|
||||
* Interne Newsletter-Kampagne (Admin ▸ Newsletter): Betreff + leicht
|
||||
* formatierter Text an gefilterte Bestands-User. `filters` hält die
|
||||
* Empfänger-Auswahl als Snapshot ({@see CampaignRecipients}),
|
||||
* die Empfänger-Zeilen bilden das Versand-Protokoll.
|
||||
*/
|
||||
class NewsletterCampaign extends Model
|
||||
{
|
||||
/** @use HasFactory<NewsletterCampaignFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'subject',
|
||||
'body',
|
||||
'filters',
|
||||
'status',
|
||||
'created_by_user_id',
|
||||
'recipients_total',
|
||||
'sent_count',
|
||||
'failed_count',
|
||||
'skipped_count',
|
||||
'sent_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'filters' => 'array',
|
||||
'status' => NewsletterCampaignStatus::class,
|
||||
'sent_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function createdBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by_user_id');
|
||||
}
|
||||
|
||||
public function recipients(): HasMany
|
||||
{
|
||||
return $this->hasMany(NewsletterCampaignRecipient::class);
|
||||
}
|
||||
|
||||
public function isDraft(): bool
|
||||
{
|
||||
return $this->status === NewsletterCampaignStatus::Draft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Versandfertiger, gesäuberter Inhalt: nur leichtes HTML laut
|
||||
* Purifier-Profil `newsletter` (fett/kursiv, Listen, Links).
|
||||
*/
|
||||
public function sanitizedBody(): HtmlString
|
||||
{
|
||||
return new HtmlString((string) Purifier::clean($this->body ?? '', 'newsletter'));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue