resolve(); if ($resolved['oauth_mailer'] !== null && $resolved['oauth_ready'] && $this->capabilities->has(Capability::EmailTransportConfigure)) { Config::set('mail.default', $resolved['oauth_mailer']); // Delegated Graph/Gmail can only send as the mailbox that // consented, so the From address is pinned to it — a stored // from_address from an earlier SMTP setup must not survive // into a mode where the vendor would reject it (SendAsDenied). if ($resolved['oauth_account'] !== null) { Config::set('mail.from.address', $resolved['oauth_account']); } } elseif ($resolved['transport_configured'] && $this->capabilities->has(Capability::EmailTransportConfigure)) { Config::set('mail.default', 'smtp'); Config::set('mail.mailers.smtp.host', $resolved['host']); Config::set('mail.mailers.smtp.port', $resolved['port']); Config::set('mail.mailers.smtp.username', $resolved['username']); Config::set('mail.mailers.smtp.password', $this->password()); Config::set('mail.mailers.smtp.encryption', $resolved['encryption']); } if ($resolved['from_address'] !== null && ($resolved['oauth_mailer'] === null || ! $resolved['oauth_ready'])) { Config::set('mail.from.address', $resolved['from_address']); } if ($resolved['from_name'] !== null) { Config::set('mail.from.name', $resolved['from_name']); } } public function flush(): void { Cache::forget(self::CACHE_KEY); } /** * The SMTP password, read from the row rather than from the cache. * * The same rule MailOAuthConnection states for tokens — "must never * travel through the boot-config cache" — applied to the credential * this class configures itself. Reached only from the SMTP branch of * apply(), so an installation using OAuth, or one that has never * opened the Email screen, still boots without touching the table. * * Guarded like the cached read beside it: resolve() can hand back a * warm "transport_configured" from a database that has since stopped * answering, and booting must survive that. */ private function password(): ?string { return BootSettingsCache::read( fn (): ?string => MailProviderSettings::current()->password, ); } /** * @return array{transport_configured: bool, host: string|null, port: int|null, username: string|null, encryption: string|null, from_address: string|null, from_name: string|null, oauth_mailer: string|null, oauth_ready: bool, oauth_account: string|null} */ private function resolve(): array { $blank = [ 'transport_configured' => false, 'host' => null, 'port' => null, 'username' => null, 'encryption' => null, 'from_address' => null, 'from_name' => null, 'oauth_mailer' => null, 'oauth_ready' => false, 'oauth_account' => null, ]; // Through BootSettingsCache, not Cache directly: this runs on every // process boot, including the artisan commands that install the // application, and must survive a database that has no tables yet // (or none at all). See that class for the full story. return BootSettingsCache::rememberForever(self::CACHE_KEY, function () use ($blank): array { if (! Schema::hasTable('mail_provider_settings')) { return $blank; } $settings = MailProviderSettings::current(); $hasHost = $settings->host !== null && $settings->host !== ''; $oauthMailer = null; $oauthReady = false; $oauthAccount = null; // The table guard covers an install mid-upgrade, where this // migration has not run yet but the settings row already // names an OAuth provider (it can't, but a guard beats a // boot-killing query on the ordering assumption). if ($settings->provider->isOAuth() && Schema::hasTable('mail_oauth_connections')) { $connection = MailOAuthConnection::for($settings->provider); $oauthMailer = $settings->provider->oauthMailer(); $oauthReady = $connection->usable(); $oauthAccount = $connection->account_email; } return [ 'transport_configured' => $hasHost && ! $settings->provider->isOAuth(), 'host' => $settings->host, 'port' => $settings->port, 'username' => $settings->username, 'encryption' => $settings->encryption === 'none' ? null : $settings->encryption, 'from_address' => $settings->from_address, 'from_name' => $settings->from_name, 'oauth_mailer' => $oauthMailer, 'oauth_ready' => $oauthReady, 'oauth_account' => $oauthAccount, ]; }, $blank); } }