key => label */ public function all(): array { return [ 'verify-email' => 'E-Mail bestätigen (Registrierung)', 'reset-password' => 'Passwort zurücksetzen', 'order-confirmation' => 'Bestellbestätigung', 'cloud-ready' => 'Cloud ist bereit (Zugangsdaten)', 'maintenance-announcement' => 'Wartungsfenster angekündigt', 'maintenance-cancelled' => 'Wartungsfenster abgesagt', 'invoice' => 'Rechnung', 'new-device' => 'Anmeldung von einem neuen Gerät', 'dormant-warning' => 'Konto ohne Paket wird gelöscht', 'operator-message' => 'Nachricht aus der Konsole', ]; } public function has(string $key): bool { return array_key_exists($key, $this->all()); } /** * One mailable, filled with sample data. * * Returns null for a key nobody knows rather than throwing: this is reached * from a URL, and a URL is a string somebody can retype. */ public function make(string $key): ?Mailable { if (! $this->has($key)) { return null; } $customer = $this->customer(); $user = $this->user(); return match ($key) { 'verify-email' => new VerifyEmailMail($user), 'reset-password' => new ResetPasswordMail($user, 'https://example.test/reset/beispiel-token', 60), 'order-confirmation' => new OrderConfirmationMail($this->order($customer), $customer->name), 'cloud-ready' => $this->cloudReady($customer), 'maintenance-announcement' => new MaintenanceAnnouncementMail($this->window(), $customer), 'maintenance-cancelled' => new MaintenanceCancelledMail($this->window(), $customer), 'invoice' => new InvoiceMail($this->invoice($customer), $customer->name), 'new-device' => new NewDeviceSignInMail($customer->name, $this->device($user), 'web'), 'dormant-warning' => new DormantAccountWarningMail($user, PruneDormantAccounts::WARN_DAYS_BEFORE), 'operator-message' => new OperatorMessageMail( $customer, 'Zur Datenübernahme', "Guten Tag Bea Berger,\n\nich habe mir Ihre Ausgangslage angesehen. Die Übernahme ist möglich.\n\nMit freundlichen Grüßen\nCluPilot", ), default => null, }; } // ---- The sample records. make(), never create(). ---- private function customer(): Customer { return Customer::make([ 'name' => 'Beispiel GmbH', 'contact_name' => 'Bea Berger', 'email' => 'bea@beispiel.test', 'locale' => 'de', ]); } private function user(): User { // The verification mail signs its URL from the model key, so the sample // needs one — 0 is enough for a link nobody is meant to follow. return User::make(['name' => 'Bea Berger', 'email' => 'bea@beispiel.test']) ->forceFill(['id' => 0, 'email_verified_at' => null]); } private function order(Customer $customer): Order { return Order::make([ 'plan' => 'start', 'amount_cents' => 4900, 'currency' => 'EUR', 'datacenter' => 'fsn', 'status' => 'paid', ])->forceFill(['id' => 0, 'created_at' => Carbon::now()]); } private function invoice(Customer $customer): Invoice { // The document renders from its own snapshot, so a preview only needs a // convincing snapshot — not a series, not a number drawn from one. return Invoice::make([ 'number' => 'RE-2026-0042', 'issued_on' => Carbon::now()->toDateString(), 'due_on' => Carbon::now()->addDays(14)->toDateString(), 'net_cents' => 4900, 'tax_cents' => 980, 'gross_cents' => 5880, 'currency' => 'EUR', 'snapshot' => ['meta' => ['number' => 'RE-2026-0042']], ])->forceFill(['id' => 0, 'uuid' => '00000000-0000-0000-0000-000000000000']); } private function window(): MaintenanceWindow { return MaintenanceWindow::make([ 'title' => 'Sicherheitsupdates Nextcloud', 'starts_at' => Carbon::now()->addDays(3)->setTime(2, 0), 'ends_at' => Carbon::now()->addDays(3)->setTime(4, 0), 'state' => 'scheduled', ])->forceFill(['id' => 0]); } private function device(User $user): UserDevice { return UserDevice::make([ 'guard' => 'web', 'name' => 'Firefox auf Linux', 'last_ip' => '203.0.113.42', 'last_seen_at' => Carbon::now(), ])->forceFill(['id' => 0]); } /** * The credentials mail is a notification rather than a Mailable. * * Built through its own toMail() so the preview shows the real thing, not a * second copy of the wording that would then drift. */ private function cloudReady(Customer $customer): Mailable { $mail = new class($customer) extends Mailable { use \App\Mail\Concerns\SendsFromMailbox; public function __construct(public Customer $customer) { // The same mailbox the real notification sends from. Without // this the preview took the framework default — an address no // mailbox owns, over whichever account mail.default logs in // with, which is the "553 Sender address rejected" every // properly configured mail server answers with. $this->mailer('cp_'.MailPurpose::PROVISIONING); } public function envelope(): \Illuminate\Mail\Mailables\Envelope { return $this->mailboxEnvelope(MailPurpose::PROVISIONING, __('provisioning.mail.ready_subject')); } public function content(): \Illuminate\Mail\Mailables\Content { // The same keys App\Notifications\CloudReady hands the view. A // preview that invented its own would render a mail nobody ever // receives, and drift from the real one without saying so. return new \Illuminate\Mail\Mailables\Content(view: 'mail.cloud-ready', with: [ 'name' => $this->customer->name, 'url' => 'https://beispiel.clupilot.cloud', 'plan' => 'start', 'storage' => __('provisioning.mail.value_storage', ['gb' => 100]), 'location' => 'Falkenstein, Deutschland', 'dashboardUrl' => route('dashboard'), ]); } }; return $mail; } }