CluPilotCloud/app/Services/Mail/MailboxTester.php

79 lines
3.2 KiB
PHP

<?php
namespace App\Services\Mail;
use App\Models\Mailbox;
use App\Services\Secrets\SecretCipher;
use App\Support\Settings;
use Illuminate\Support\Facades\Mail;
use Throwable;
/**
* Sends one real message with one mailbox's credentials.
*
* Deliberately built with Mail::build() rather than going through the
* configured mailers: on a development machine the default is the log, and a
* button that honoured that would report success while writing to a file. A
* check that quietly examines something else is worse than no check — the
* same mistake the timezone tests made by computing the expected value with
* the implementation they were testing.
*
* @return array{ok: bool, error: ?string}
*/
final class MailboxTester
{
public function __construct(private readonly SecretCipher $cipher) {}
public function run(Mailbox $box, string $recipient): array
{
// Guarded BEFORE anything reads $box->password: that accessor
// decrypts under SECRETS_KEY, and it is not set on this installation
// at all (the mail settings page's own banner says so). Reading it
// first would let a RuntimeException out of a Livewire action —
// Task 7's EditMailbox::save() shipped a Critical fix for exactly
// this shape of bug. The tester touches the same cipher and gets the
// same guard, checked here rather than left for the property access
// below to throw through.
if (! $this->cipher->isUsable()) {
return ['ok' => false, 'error' => __('mail_settings.no_key')];
}
if ($box->password === null) {
return ['ok' => false, 'error' => __('mail_settings.test_no_password')];
}
$port = (int) Settings::get('mail.port', 587);
try {
Mail::build([
'transport' => 'smtp',
'host' => (string) Settings::get('mail.host', ''),
'port' => $port,
// 'scheme', NOT 'encryption'. Laravel 13's MailManager reads
// scheme only (MailManager.php:196) — an 'encryption' key is
// silently ignored, and the connection would go out in the
// clear while the console reported TLS.
'scheme' => (Settings::get('mail.encryption') === 'ssl' || $port === 465) ? 'smtps' : 'smtp',
'username' => $box->smtpUsername(),
'password' => $box->password,
])->raw(
__('mail_settings.test_body', ['key' => $box->key]),
function ($message) use ($box, $recipient) {
$message->to($recipient)
->from($box->address, $box->display_name ?: null)
->subject(__('mail_settings.test_subject'));
},
);
} catch (Throwable $e) {
// Verbatim. With wrong credentials, or a server that rejects the
// message outright, the server's own sentence IS the entire
// diagnosis — "sending failed" would throw that away.
return ['ok' => false, 'error' => $e->getMessage()];
}
$box->forceFill(['last_verified_at' => now()])->save();
return ['ok' => true, 'error' => null];
}
}