|null */ public function config(): ?array { return Addon::where('key', 'smtp')->where('installed', true)->first()?->config; } public function eventEnabled(string $event): bool { return (bool) data_get($this->config(), "events.{$event}", false); } /** Send only if the SMTP add-on is set up AND this event type is enabled. */ public function notify(string $event, string $subject, string $body): bool { if (! $this->eventEnabled($event)) { return false; } return $this->send($subject, $body); } /** Send unconditionally (used by the "send test" button). Returns success, never throws. */ public function send(string $subject, string $body): bool { $cfg = $this->config(); if ($cfg === null || blank($cfg['to_email'] ?? null) || blank($cfg['host'] ?? null)) { return false; } $this->configureMailer($cfg); try { Mail::mailer('homeos_smtp')->raw($body, function ($message) use ($cfg) { $message->to($cfg['to_email']) ->subject('[HomeOS] '.$subject) ->from($cfg['from_email'] ?: 'homeos@localhost', $cfg['from_name'] ?: 'HomeOS'); }); } catch (\Throwable $e) { // A transient SMTP failure must not abort the caller (a sweep over many devices, an // automation run). Log and report failure so the loop keeps going. report($e); return false; } return true; } /** @param array $cfg */ private function configureMailer(array $cfg): void { config(['mail.mailers.homeos_smtp' => [ 'transport' => 'smtp', 'host' => $cfg['host'], 'port' => (int) ($cfg['port'] ?? 587), 'encryption' => ($cfg['encryption'] ?? '') ?: null, 'username' => ($cfg['username'] ?? '') ?: null, 'password' => ($cfg['password'] ?? '') ?: null, 'timeout' => 10, ]]); // MailManager caches the resolved mailer by name; purge it so a long-running worker // (Horizon) picks up config changes instead of reusing the first mailer it built. Mail::purge('homeos_smtp'); } }