mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 17:01:16 +00:00
Update emails
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Enums\IncidentImpactEnum;
|
||||
use App\Enums\IncidentStatusEnum;
|
||||
use App\Mail\AccountChange;
|
||||
use App\Mail\AccountDeleted;
|
||||
use App\Mail\AccountExpired;
|
||||
use App\Mail\AccountWillExpire;
|
||||
use App\Mail\ContactUs;
|
||||
use App\Mail\ForgottenPassword;
|
||||
use App\Mail\IncidentDetected;
|
||||
use App\Mail\InvitationMail;
|
||||
use App\Mail\NewAccountCreatedEmail;
|
||||
use App\Mail\PasswordReset;
|
||||
use App\Mail\WelcomeEmail;
|
||||
use App\Models\Invitation;
|
||||
use App\Models\ServiceIncident;
|
||||
use App\Models\User;
|
||||
use App\Notifications\VerifyEmailBranded;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Markdown;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* Generates static HTML previews for every transactional and notification email
|
||||
* the application can send. Useful for spot-checking the themed templates
|
||||
* across mail clients without needing to wire up Mailpit or trigger real
|
||||
* application events. Output lands in `storage/app/mail-previews/*.html`.
|
||||
*/
|
||||
class MailPreview extends Command
|
||||
{
|
||||
protected $signature = 'mail:preview
|
||||
{--out= : Override the output directory (defaults to storage/app/mail-previews)}';
|
||||
|
||||
protected $description = 'Render every mailable to static HTML files for visual QA across email clients.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$directory = (string) ($this->option('out') ?: storage_path('app/mail-previews'));
|
||||
if (! is_dir($directory) && ! mkdir($directory, 0o755, true) && ! is_dir($directory)) {
|
||||
$this->components->error("Could not create output directory: {$directory}");
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$user = $this->fakeUser();
|
||||
$invitation = $this->fakeInvitation();
|
||||
[$activeIncident, $resolvedIncident] = $this->fakeIncidents();
|
||||
|
||||
/** @var array<string, callable(): Mailable> $mailables */
|
||||
$mailables = [
|
||||
'welcome' => fn (): Mailable => new WelcomeEmail($user),
|
||||
'forgotten_password' => fn (): Mailable => new ForgottenPassword('https://example.test/reset/abc123'),
|
||||
'password_reset' => fn (): Mailable => new PasswordReset($user, 'TempPass!2026'),
|
||||
'new_account_created' => fn (): Mailable => new NewAccountCreatedEmail($user),
|
||||
'account_change' => fn (): Mailable => new AccountChange($user),
|
||||
'account_will_expire' => fn (): Mailable => new AccountWillExpire($user, 5),
|
||||
'account_expired' => fn (): Mailable => new AccountExpired($user),
|
||||
'account_deleted' => fn (): Mailable => new AccountDeleted($user),
|
||||
'contact_us' => fn (): Mailable => new ContactUs(
|
||||
'admin@example.test',
|
||||
'visitor@example.test',
|
||||
"Hello!\nQuestion inside.",
|
||||
),
|
||||
'invitation' => fn (): Mailable => new InvitationMail($invitation),
|
||||
'incident_detected' => fn (): Mailable => new IncidentDetected($activeIncident, false),
|
||||
'incident_resolved' => fn (): Mailable => new IncidentDetected($resolvedIncident, true),
|
||||
];
|
||||
|
||||
foreach ($mailables as $name => $factory) {
|
||||
$html = $factory()->render();
|
||||
file_put_contents("{$directory}/{$name}.html", $html);
|
||||
$this->components->info("Wrote {$name}.html (".number_format(strlen($html)).' bytes)');
|
||||
}
|
||||
|
||||
$this->writeVerifyPreview($directory);
|
||||
|
||||
$this->newLine();
|
||||
$this->components->info("All previews written to: {$directory}");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function writeVerifyPreview(string $directory): void
|
||||
{
|
||||
$notifiable = new class
|
||||
{
|
||||
public int $id = 7;
|
||||
|
||||
public string $email = 'tester@example.test';
|
||||
|
||||
public string $username = 'tester';
|
||||
|
||||
public function getKey(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getEmailForVerification(): string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
};
|
||||
|
||||
$message = (new VerifyEmailBranded)->toMail($notifiable);
|
||||
$markdown = app(Markdown::class);
|
||||
$html = (string) $markdown->render($message->markdown, $message->data());
|
||||
file_put_contents("{$directory}/verify_email.html", $html);
|
||||
$this->components->info('Wrote verify_email.html ('.number_format(strlen($html)).' bytes)');
|
||||
}
|
||||
|
||||
private function fakeUser(): User
|
||||
{
|
||||
$user = new User;
|
||||
$user->id = 99;
|
||||
$user->username = 'tester';
|
||||
$user->email = 'tester@example.test';
|
||||
$user->setRelation('role', new class
|
||||
{
|
||||
public string $name = 'Silver';
|
||||
});
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
private function fakeInvitation(): Invitation
|
||||
{
|
||||
$inviter = new User;
|
||||
$inviter->id = 1;
|
||||
$inviter->username = 'mod_alice';
|
||||
|
||||
$invitation = new Invitation;
|
||||
$invitation->token = 'preview-token-abc123';
|
||||
$invitation->email = 'newuser@example.test';
|
||||
$invitation->expires_at = Carbon::now()->addDays(7);
|
||||
$invitation->setRelation('invitedBy', $inviter);
|
||||
|
||||
return $invitation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: ServiceIncident, 1: ServiceIncident}
|
||||
*/
|
||||
private function fakeIncidents(): array
|
||||
{
|
||||
$services = collect([
|
||||
(object) ['name' => 'NZB Search'],
|
||||
(object) ['name' => 'API'],
|
||||
]);
|
||||
|
||||
$active = new ServiceIncident;
|
||||
$active->title = 'API latency spike';
|
||||
$active->description = "p99 latency exceeded thresholds.\nFollow status page for updates.";
|
||||
$active->status = IncidentStatusEnum::Identified;
|
||||
$active->impact = IncidentImpactEnum::Critical;
|
||||
$active->started_at = Carbon::now()->subMinutes(15);
|
||||
$active->setRelation('services', $services);
|
||||
|
||||
$resolved = clone $active;
|
||||
$resolved->status = IncidentStatusEnum::Resolved;
|
||||
$resolved->resolved_at = Carbon::now();
|
||||
|
||||
return [$active, $resolved];
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Mail\SendInvite;
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SendInviteEmail implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
private string $email;
|
||||
|
||||
private string $url;
|
||||
|
||||
private User $user;
|
||||
|
||||
/**
|
||||
* SendInviteEmail constructor.
|
||||
*/
|
||||
public function __construct(string $email, User $user, string $url)
|
||||
{
|
||||
$this->email = $email;
|
||||
$this->user = $user;
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
Mail::to($this->email)->send(new SendInvite($this->user, $this->url));
|
||||
}
|
||||
}
|
||||
+22
-23
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Mail\Concerns\HasBrandedSubject;
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
@@ -11,41 +12,39 @@ use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class AccountChange extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
use HasBrandedSubject, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
public $user;
|
||||
public string $username;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $siteEmail;
|
||||
public string $account;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $siteTitle;
|
||||
public string $site;
|
||||
|
||||
public ?string $preheader;
|
||||
|
||||
private string $siteEmail;
|
||||
|
||||
/**
|
||||
* AccountChange constructor.
|
||||
*/
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->siteEmail = config('mail.from.address');
|
||||
$this->siteTitle = config('app.name');
|
||||
$this->username = (string) $user->username;
|
||||
$this->account = (string) ($user->role->name ?? 'User');
|
||||
$this->siteEmail = (string) config('mail.from.address');
|
||||
$this->site = (string) config('app.name');
|
||||
$this->preheader = "Your {$this->site} account level was updated to {$this->account}.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function build(): static
|
||||
{
|
||||
return $this->from($this->siteEmail)->subject('Account Changed')->view('emails.accountChange')->with(['account' => $this->user->role->name, 'username' => $this->user->username, 'site' => $this->siteTitle]);
|
||||
return $this->from($this->siteEmail)
|
||||
->brandedSubject('Account level changed')
|
||||
->markdown('emails.markdown.accountChange', [
|
||||
'username' => $this->username,
|
||||
'account' => $this->account,
|
||||
'site' => $this->site,
|
||||
'preheader' => $this->preheader,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
+18
-21
@@ -4,38 +4,29 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Mail\Concerns\HasBrandedSubject;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class AccountDeleted extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
use HasBrandedSubject, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* @var Model|null|object|static
|
||||
*/
|
||||
private $user;
|
||||
public string $username;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $siteEmail;
|
||||
public string $site;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $siteTitle;
|
||||
public ?string $preheader;
|
||||
|
||||
private string $siteEmail;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct(mixed $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->siteEmail = config('mail.from.address');
|
||||
$this->siteTitle = config('app.name');
|
||||
$this->username = (string) $user->username;
|
||||
$this->siteEmail = (string) config('mail.from.address');
|
||||
$this->site = (string) config('app.name');
|
||||
$this->preheader = "User {$this->username} deleted their {$this->site} account.";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,6 +34,12 @@ class AccountDeleted extends Mailable
|
||||
*/
|
||||
public function build(): static
|
||||
{
|
||||
return $this->from($this->siteEmail)->subject('User Account Deleted')->view('emails.accountDelete')->with(['username' => $this->user->username, 'site' => $this->siteTitle]);
|
||||
return $this->from($this->siteEmail)
|
||||
->brandedSubject('User account deleted')
|
||||
->markdown('emails.markdown.accountDeleted', [
|
||||
'username' => $this->username,
|
||||
'site' => $this->site,
|
||||
'preheader' => $this->preheader,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
+22
-14
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Mail\Concerns\HasBrandedSubject;
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
@@ -11,32 +12,39 @@ use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class AccountExpired extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
use HasBrandedSubject, Queueable, SerializesModels;
|
||||
|
||||
public User $user;
|
||||
public string $username;
|
||||
|
||||
private mixed $siteEmail;
|
||||
public string $account;
|
||||
|
||||
private mixed $siteTitle;
|
||||
public string $site;
|
||||
|
||||
public ?string $preheader;
|
||||
|
||||
private string $siteEmail;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->siteEmail = config('mail.from.address');
|
||||
$this->siteTitle = config('app.name');
|
||||
$this->username = (string) $user->username;
|
||||
$this->account = (string) ($user->role->name ?? 'User');
|
||||
$this->siteEmail = (string) config('mail.from.address');
|
||||
$this->site = (string) config('app.name');
|
||||
$this->preheader = "Your {$this->site} subscription has expired — account downgraded to {$this->account}.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function build(): static
|
||||
{
|
||||
return $this->from($this->siteEmail)->subject('Account expired')->view('emails.accountExpired')->with(['account' => $this->user->role->name, 'username' => $this->user->username, 'site' => $this->siteTitle]);
|
||||
return $this->from($this->siteEmail)
|
||||
->brandedSubject('Your account has expired')
|
||||
->markdown('emails.markdown.accountExpired', [
|
||||
'username' => $this->username,
|
||||
'account' => $this->account,
|
||||
'site' => $this->site,
|
||||
'preheader' => $this->preheader,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Mail\Concerns\HasBrandedSubject;
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
@@ -11,32 +12,40 @@ use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class AccountWillExpire extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
use HasBrandedSubject, Queueable, SerializesModels;
|
||||
|
||||
private int $days;
|
||||
public int $days;
|
||||
|
||||
private User $user;
|
||||
public string $username;
|
||||
|
||||
private mixed $siteEmail;
|
||||
public string $account;
|
||||
|
||||
private mixed $siteTitle;
|
||||
public string $site;
|
||||
|
||||
public ?string $preheader;
|
||||
|
||||
private string $siteEmail;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct(User $user, int $days)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->days = $days;
|
||||
$this->siteEmail = config('mail.from.address');
|
||||
$this->siteTitle = config('app.name');
|
||||
$this->username = (string) $user->username;
|
||||
$this->account = (string) ($user->role->name ?? 'User');
|
||||
$this->siteEmail = (string) config('mail.from.address');
|
||||
$this->site = (string) config('app.name');
|
||||
$this->preheader = "Your {$this->account} role expires in {$this->days} day(s).";
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*/
|
||||
public function build(): static
|
||||
{
|
||||
return $this->from($this->siteEmail)->subject('Account about to expire')->view('emails.accountAboutToExpire')->with(['account' => $this->user->role->name, 'username' => $this->user->username, 'site' => $this->siteTitle, 'days' => $this->days]);
|
||||
return $this->from($this->siteEmail)
|
||||
->brandedSubject('Your account is about to expire')
|
||||
->markdown('emails.markdown.accountWillExpire', [
|
||||
'username' => $this->username,
|
||||
'account' => $this->account,
|
||||
'days' => $this->days,
|
||||
'site' => $this->site,
|
||||
'preheader' => $this->preheader,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mail\Concerns;
|
||||
|
||||
/**
|
||||
* Adds a configurable, site-wide subject prefix (default "[App Name] ") to
|
||||
* mailables, so transactional emails read consistently in users' inboxes.
|
||||
*
|
||||
* Concrete mailables call `$this->brandedSubject('Welcome')` instead of
|
||||
* `$this->subject('Welcome')`. The prefix can be overridden globally via
|
||||
* `MAIL_SUBJECT_PREFIX` (see `config/mail.php` -> `brand.subject_prefix`)
|
||||
* or disabled per call by passing `prefix: false`.
|
||||
*/
|
||||
trait HasBrandedSubject
|
||||
{
|
||||
protected function brandedSubject(string $subject, bool $prefix = true): static
|
||||
{
|
||||
$prefixValue = $prefix
|
||||
? (string) config('mail.brand.subject_prefix', '')
|
||||
: '';
|
||||
|
||||
return $this->subject($prefixValue.$subject);
|
||||
}
|
||||
}
|
||||
+20
-11
@@ -4,13 +4,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Mail\Concerns\HasBrandedSubject;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ContactUs extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
use HasBrandedSubject, Queueable, SerializesModels;
|
||||
|
||||
public string $mailFrom;
|
||||
|
||||
@@ -18,24 +19,32 @@ class ContactUs extends Mailable
|
||||
|
||||
public string $mailTo;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public string $site;
|
||||
|
||||
public ?string $preheader;
|
||||
|
||||
public function __construct(mixed $mailTo, mixed $mailFrom, mixed $mailBody)
|
||||
{
|
||||
$this->mailTo = $mailTo;
|
||||
$this->mailFrom = $mailFrom;
|
||||
$this->mailBody = $mailBody;
|
||||
$this->mailTo = (string) $mailTo;
|
||||
$this->mailFrom = (string) $mailFrom;
|
||||
$this->mailBody = (string) $mailBody;
|
||||
$this->site = (string) config('app.name');
|
||||
$this->preheader = "New contact form submission from {$this->mailFrom}.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function build(): static
|
||||
{
|
||||
return $this->from($this->mailTo)->subject('Contact form submitted')->replyTo($this->mailFrom)->view('emails.contactUs')->with(['mailBody' => $this->mailBody]);
|
||||
return $this->from($this->mailTo)
|
||||
->replyTo($this->mailFrom)
|
||||
->brandedSubject('Contact form submitted')
|
||||
->markdown('emails.markdown.contactUs', [
|
||||
'mailBody' => $this->mailBody,
|
||||
'mailFrom' => $this->mailFrom,
|
||||
'site' => $this->site,
|
||||
'preheader' => $this->preheader,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,41 +4,43 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Mail\Concerns\HasBrandedSubject;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ForgottenPassword extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public mixed $user;
|
||||
use HasBrandedSubject, Queueable, SerializesModels;
|
||||
|
||||
public string $resetLink;
|
||||
|
||||
private mixed $siteEmail;
|
||||
public string $site;
|
||||
|
||||
private mixed $siteTitle;
|
||||
public ?string $preheader;
|
||||
|
||||
private string $siteEmail;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct(string $resetLink)
|
||||
{
|
||||
$this->resetLink = $resetLink;
|
||||
$this->siteEmail = config('mail.from.address');
|
||||
$this->siteEmail = (string) config('mail.from.address');
|
||||
$siteName = config('app.name');
|
||||
$this->siteTitle = is_array($siteName) ? (string) ($siteName[0] ?? '') : (string) ($siteName ?? '');
|
||||
$this->site = is_array($siteName) ? (string) ($siteName[0] ?? '') : (string) ($siteName ?? '');
|
||||
$this->preheader = 'Reset your password using the secure link inside.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function build(): static
|
||||
{
|
||||
return $this->from($this->siteEmail)->subject('Forgotten password reset')->view('emails.forgottenPassword')->with(['resetLink' => $this->resetLink, 'site' => $this->siteTitle]);
|
||||
return $this->from($this->siteEmail)
|
||||
->brandedSubject('Reset your password')
|
||||
->markdown('emails.markdown.forgottenPassword', [
|
||||
'resetLink' => $this->resetLink,
|
||||
'site' => $this->site,
|
||||
'preheader' => $this->preheader,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,34 +4,46 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Mail\Concerns\HasBrandedSubject;
|
||||
use App\Models\ServiceIncident;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class IncidentDetected extends Mailable
|
||||
class IncidentDetected extends Mailable implements ShouldQueue
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
use HasBrandedSubject, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
private readonly ServiceIncident $incident,
|
||||
private readonly bool $resolved = false
|
||||
) {}
|
||||
public readonly ServiceIncident $incident,
|
||||
public readonly bool $resolved = false,
|
||||
) {
|
||||
// Incident notifications take priority over routine user mail so they
|
||||
// are routed onto a dedicated high-priority queue (overridable via env).
|
||||
$this->onQueue((string) config('mail.brand.incident_queue', 'incidents'));
|
||||
}
|
||||
|
||||
public function build(): static
|
||||
{
|
||||
$site = config('app.name');
|
||||
$site = (string) config('app.name');
|
||||
$status = $this->resolved ? 'Resolved' : 'Detected';
|
||||
$services = $this->incident->services->pluck('name')->join(', ');
|
||||
$services = (string) $this->incident->services->pluck('name')->join(', ');
|
||||
$impact = ucfirst($this->incident->impact->value);
|
||||
$preheader = $this->resolved
|
||||
? "{$impact} incident on {$services} has been resolved."
|
||||
: "{$impact} impact incident detected on {$services}.";
|
||||
|
||||
return $this->from(config('mail.from.address'))
|
||||
->subject("[{$site}] Service Incident {$status}: {$services}")
|
||||
return $this->from((string) config('mail.from.address'))
|
||||
->brandedSubject("Service incident {$status}: {$services}")
|
||||
->view('emails.incidentDetected')
|
||||
->text('emails.text.incidentDetected')
|
||||
->with([
|
||||
'incident' => $this->incident,
|
||||
'services' => $services,
|
||||
'resolved' => $this->resolved,
|
||||
'site' => $site,
|
||||
'preheader' => $preheader,
|
||||
'statusUrl' => url('/admin/status'),
|
||||
]);
|
||||
}
|
||||
|
||||
+14
-15
@@ -4,58 +4,57 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Mail\Concerns\HasBrandedSubject;
|
||||
use App\Models\Invitation;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Attachment;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class InvitationMail extends Mailable
|
||||
class InvitationMail extends Mailable implements ShouldQueue
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
use HasBrandedSubject, Queueable, SerializesModels;
|
||||
|
||||
public Invitation $invitation;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct(Invitation $invitation)
|
||||
{
|
||||
$this->invitation = $invitation;
|
||||
$this->onQueue((string) config('mail.brand.queue', 'emails'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(
|
||||
subject: 'You\'re invited to join '.config('app.name'),
|
||||
from: (string) config('mail.from.address'),
|
||||
subject: (string) config('mail.brand.subject_prefix', '').'You\'re invited to join '.config('app.name'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
$siteName = (string) config('app.name');
|
||||
$invitedByName = (string) ($this->invitation->invitedBy->username ?? 'A member');
|
||||
|
||||
return new Content(
|
||||
view: 'emails.invitation',
|
||||
text: 'emails.text.invitation',
|
||||
with: [
|
||||
'invitation' => $this->invitation,
|
||||
'invitedBy' => $this->invitation->invitedBy,
|
||||
'invitedByName' => $invitedByName,
|
||||
'registerUrl' => route('register', ['token' => $this->invitation->token]),
|
||||
'expiresAt' => $this->invitation->expires_at,
|
||||
'siteName' => config('app.name'),
|
||||
'siteName' => $siteName,
|
||||
'preheader' => "{$invitedByName} has invited you to join {$siteName}.",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array<int, Attachment>
|
||||
*/
|
||||
public function attachments(): array
|
||||
|
||||
@@ -4,47 +4,39 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Mail\Concerns\HasBrandedSubject;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class NewAccountCreatedEmail extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
use HasBrandedSubject, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
public string $username;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $siteEmail;
|
||||
public string $site;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $siteTitle;
|
||||
public ?string $preheader;
|
||||
|
||||
private string $siteEmail;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(mixed $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->siteEmail = config('mail.from.address');
|
||||
$this->siteTitle = config('app.name');
|
||||
$this->username = (string) $user->username;
|
||||
$this->siteEmail = (string) config('mail.from.address');
|
||||
$this->site = (string) config('app.name');
|
||||
$this->preheader = "New user registered on {$this->site}.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*/
|
||||
public function build(): static
|
||||
{
|
||||
return $this->from($this->siteEmail)->subject('New account registered')->view('emails.newAccountCreated')->with(['username' => $this->user->username, 'site' => $this->siteTitle]);
|
||||
return $this->from($this->siteEmail)
|
||||
->brandedSubject('New account registered')
|
||||
->markdown('emails.markdown.newAccountCreated', [
|
||||
'username' => $this->username,
|
||||
'site' => $this->site,
|
||||
'preheader' => $this->preheader,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
+21
-28
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Mail\Concerns\HasBrandedSubject;
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
@@ -11,47 +12,39 @@ use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class PasswordReset extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
use HasBrandedSubject, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
public $user;
|
||||
public string $username;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $newPass;
|
||||
public string $newPass;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $siteEmail;
|
||||
public string $site;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $siteTitle;
|
||||
public ?string $preheader;
|
||||
|
||||
/**
|
||||
* PasswordReset constructor.
|
||||
*/
|
||||
public function __construct(User $user, mixed $newPass)
|
||||
private string $siteEmail;
|
||||
|
||||
public function __construct(User $user, string $newPass)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->username = (string) $user->username;
|
||||
$this->newPass = $newPass;
|
||||
$this->siteEmail = config('mail.from.address');
|
||||
$this->siteTitle = config('app.name');
|
||||
$this->siteEmail = (string) config('mail.from.address');
|
||||
$this->site = (string) config('app.name');
|
||||
$this->preheader = 'Your password has been reset — temporary credentials inside.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function build(): static
|
||||
{
|
||||
return $this->from($this->siteEmail)->subject('Password reset')->view('emails.passwordReset')->with(['newPass' => $this->newPass, 'userName' => $this->user->username, 'site' => $this->siteTitle]);
|
||||
return $this->from($this->siteEmail)
|
||||
->brandedSubject('Your password has been reset')
|
||||
->markdown('emails.markdown.passwordReset', [
|
||||
'username' => $this->username,
|
||||
'newPass' => $this->newPass,
|
||||
'site' => $this->site,
|
||||
'preheader' => $this->preheader,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SendInvite extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $invite;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $siteEmail;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $siteTitle;
|
||||
|
||||
/**
|
||||
* SendInvite constructor.
|
||||
*/
|
||||
public function __construct(User $user, mixed $invite)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->invite = $invite;
|
||||
$this->siteEmail = config('mail.from.address');
|
||||
$this->siteTitle = config('app.name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function build(): static
|
||||
{
|
||||
return $this->from($this->siteEmail)->subject('Invite received')->view('emails.sendinvite')->with(['invite' => $this->invite, 'username' => $this->user['username'], 'site' => $this->siteTitle, 'email' => $this->user['email']]);
|
||||
}
|
||||
}
|
||||
+18
-23
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Mail\Concerns\HasBrandedSubject;
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
@@ -11,38 +12,32 @@ use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class WelcomeEmail extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
use HasBrandedSubject, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
public string $username;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $siteEmail;
|
||||
public string $site;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $siteTitle;
|
||||
public ?string $preheader;
|
||||
|
||||
private string $siteEmail;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->siteEmail = config('mail.from.address');
|
||||
$this->siteTitle = config('app.name');
|
||||
$this->username = (string) $user->username;
|
||||
$this->site = (string) config('app.name');
|
||||
$this->siteEmail = (string) config('mail.from.address');
|
||||
$this->preheader = "Welcome to {$this->site} — your account is ready.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*/
|
||||
public function build(): static
|
||||
{
|
||||
return $this->from($this->siteEmail)->subject('Welcome to '.$this->siteTitle)->view('emails.welcome')->with(['username' => $this->user->username, 'site' => $this->siteTitle]);
|
||||
return $this->from($this->siteEmail)
|
||||
->brandedSubject('Welcome to '.$this->site)
|
||||
->markdown('emails.markdown.welcome', [
|
||||
'username' => $this->username,
|
||||
'site' => $this->site,
|
||||
'preheader' => $this->preheader,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-3
@@ -8,11 +8,11 @@ use App\Enums\SignupError;
|
||||
use App\Enums\UserRole;
|
||||
use App\Jobs\SendAccountExpiredEmail;
|
||||
use App\Jobs\SendAccountWillExpireEmail;
|
||||
use App\Notifications\VerifyEmailBranded;
|
||||
use App\Rules\ValidEmailDomain;
|
||||
use App\Services\InvitationService;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Auth\MustVerifyEmail as MustVerifyEmailTrait;
|
||||
use Illuminate\Auth\Notifications\VerifyEmail;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail as MustVerifyEmailContract;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
@@ -402,11 +402,15 @@ final class User extends Authenticatable implements HasPasskeys, MustVerifyEmail
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the standard Laravel email verification notification.
|
||||
* Send the branded email verification notification.
|
||||
*
|
||||
* Uses {@see VerifyEmailBranded} so the verification email renders with
|
||||
* the same themed Markdown components as the rest of the site's
|
||||
* transactional emails (instead of Laravel's default notification look).
|
||||
*/
|
||||
public function sendEmailVerificationNotification(): void
|
||||
{
|
||||
$this->notify(new VerifyEmail);
|
||||
$this->notify(new VerifyEmailBranded);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use Illuminate\Auth\Notifications\VerifyEmail;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
/**
|
||||
* Branded replacement for Laravel's framework `VerifyEmail` notification.
|
||||
*
|
||||
* Renders through our themed Markdown mail components (see
|
||||
* `resources/views/vendor/mail/*` and `resources/views/emails/markdown/verify.blade.php`)
|
||||
* so verification emails match the rest of the transactional email branding
|
||||
* instead of falling back to the default Laravel notification look.
|
||||
*/
|
||||
class VerifyEmailBranded extends VerifyEmail implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->onQueue((string) config('mail.brand.queue', 'emails'));
|
||||
}
|
||||
|
||||
public function toMail($notifiable): MailMessage
|
||||
{
|
||||
$url = $this->verificationUrl($notifiable);
|
||||
$site = (string) config('app.name');
|
||||
$prefix = (string) config('mail.brand.subject_prefix', '');
|
||||
$username = (string) ($notifiable->username ?? $notifiable->name ?? '');
|
||||
|
||||
return (new MailMessage)
|
||||
->subject($prefix.'Verify your email address')
|
||||
->markdown('emails.markdown.verify', [
|
||||
'url' => $url,
|
||||
'site' => $site,
|
||||
'username' => $username,
|
||||
'preheader' => "Confirm your email address to activate your {$site} account.",
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user