79 lines
3.2 KiB
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];
|
|
}
|
|
}
|