Merge remote-tracking branch 'origin/main' into next

This commit is contained in:
github-actions[bot]
2026-08-19 08:55:26 +00:00
5 changed files with 211 additions and 24 deletions
+2 -16
View File
@@ -4,13 +4,13 @@ namespace App\Notifications\Channels;
use App\Exceptions\NonReportableException;
use App\Models\Team;
use App\Support\SmtpTransportFactory;
use Exception;
use Illuminate\Notifications\Notification;
use Resend;
use Resend\Exceptions\ErrorException;
use Resend\Exceptions\TransporterException;
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
use Symfony\Component\Mime\Email;
class EmailChannel
@@ -82,21 +82,7 @@ class EmailChannel
'html' => (string) $mailMessage->render(),
]);
} elseif ($isSmtpEnabled) {
$encryption = match (strtolower($settings->smtp_encryption)) {
'starttls' => null,
'tls' => 'tls',
'none' => null,
default => null,
};
$transport = new EsmtpTransport(
$settings->smtp_host,
$settings->smtp_port,
$encryption
);
$transport->setUsername($settings->smtp_username ?? '');
$transport->setPassword($settings->smtp_password ?? '');
$transport = SmtpTransportFactory::fromSettings($settings);
$mailer = new Mailer($transport);
$email = (new Email)
+5 -8
View File
@@ -2,6 +2,7 @@
namespace App\Services;
use App\Support\SmtpTransportFactory;
use Illuminate\Config\Repository;
use Illuminate\Support\Facades\Mail;
@@ -27,25 +28,21 @@ class ConfigurationRepository
}
if ($settings->smtp_enabled) {
$encryption = match (strtolower($settings->smtp_encryption)) {
'starttls' => null,
'tls' => 'tls',
'none' => null,
default => null,
};
$mailerOptions = SmtpTransportFactory::mailerOptions($settings);
$this->config->set('mail.default', 'smtp');
$this->applyMailFrom($from);
$this->config->set('mail.mailers.smtp', [
'transport' => 'smtp',
'scheme' => $mailerOptions['scheme'],
'host' => $settings->smtp_host,
'port' => $settings->smtp_port,
'encryption' => $encryption,
'encryption' => $mailerOptions['encryption'],
'username' => $settings->smtp_username,
'password' => $settings->smtp_password,
'timeout' => $settings->smtp_timeout,
'local_domain' => null,
'auto_tls' => $settings->smtp_encryption === 'none' ? '0' : '',
'auto_tls' => $mailerOptions['auto_tls'],
]);
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
namespace App\Support;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream;
class SmtpTransportFactory
{
public static function fromSettings(object $settings): EsmtpTransport
{
$mode = self::encryptionMode($settings);
$transport = new EsmtpTransport(
$settings->smtp_host,
(int) $settings->smtp_port,
match ($mode) {
'none' => false,
'tls' => true,
default => null,
}
);
if ($mode === 'none') {
$transport->setAutoTls(false);
}
$transport->setUsername($settings->smtp_username ?? '');
$transport->setPassword($settings->smtp_password ?? '');
$stream = $transport->getStream();
if (isset($settings->smtp_timeout) && $stream instanceof SocketStream) {
$stream->setTimeout((float) $settings->smtp_timeout);
}
return $transport;
}
/**
* @return array{scheme: ?string, encryption: ?string, auto_tls: string}
*/
public static function mailerOptions(object $settings): array
{
$mode = self::encryptionMode($settings);
return [
'scheme' => match ($mode) {
'none', 'starttls' => 'smtp',
'tls' => 'smtps',
default => null,
},
'encryption' => $mode === 'tls' ? 'tls' : null,
'auto_tls' => $mode === 'none' ? '0' : '',
];
}
private static function encryptionMode(object $settings): ?string
{
return $settings->smtp_encryption === null
? null
: strtolower((string) $settings->smtp_encryption);
}
}
@@ -0,0 +1,29 @@
<?php
use App\Services\ConfigurationRepository;
use Illuminate\Config\Repository;
it('configures the laravel smtp scheme from encryption', function (string $mode, int $port, string $scheme, ?string $encryption, string $autoTls) {
$config = new Repository;
$repository = new ConfigurationRepository($config);
$repository->updateMailConfig((object) [
'resend_enabled' => false,
'smtp_enabled' => true,
'smtp_encryption' => $mode,
'smtp_host' => 'smtp.example.com',
'smtp_port' => $port,
'smtp_username' => 'user',
'smtp_password' => 'secret',
'smtp_timeout' => null,
'smtp_from_address' => 'from@example.com',
'smtp_from_name' => 'Coolify',
]);
expect($config->get('mail.mailers.smtp.scheme'))->toBe($scheme)
->and($config->get('mail.mailers.smtp.encryption'))->toBe($encryption)
->and($config->get('mail.mailers.smtp.auto_tls'))->toBe($autoTls);
})->with([
'none on port 465' => ['none', 465, 'smtp', null, '0'],
'tls on a non-465 port' => ['tls', 587, 'smtps', 'tls', ''],
]);
+112
View File
@@ -0,0 +1,112 @@
<?php
use App\Support\SmtpTransportFactory;
use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream;
function smtpSettings(array $overrides = []): object
{
return (object) array_merge([
'smtp_host' => 'smtp.example.com',
'smtp_port' => 25,
'smtp_encryption' => 'none',
'smtp_username' => 'user',
'smtp_password' => 'secret',
'smtp_timeout' => null,
], $overrides);
}
it('disables opportunistic STARTTLS when encryption is none', function () {
$transport = SmtpTransportFactory::fromSettings(smtpSettings([
'smtp_encryption' => 'none',
]));
expect($transport->isAutoTls())->toBeFalse()
->and($transport->getStream())->toBeInstanceOf(SocketStream::class)
->and($transport->getStream()->isTLS())->toBeFalse();
});
it('does not issue STARTTLS for the issue 5877 anonymous port 25 relay', function () {
$transport = SmtpTransportFactory::fromSettings(smtpSettings([
'smtp_encryption' => 'none',
'smtp_port' => 25,
'smtp_username' => '',
'smtp_password' => '',
]));
expect($transport->isAutoTls())->toBeFalse()
->and($transport->getStream()->isTLS())->toBeFalse()
->and($transport->getStream()->getPort())->toBe(25)
->and($transport->getUsername())->toBe('')
->and($transport->getPassword())->toBe('');
});
it('does not enable implicit TLS on port 465 when encryption is none', function () {
$transport = SmtpTransportFactory::fromSettings(smtpSettings([
'smtp_encryption' => 'none',
'smtp_port' => 465,
]));
expect($transport->isAutoTls())->toBeFalse()
->and($transport->getStream()->isTLS())->toBeFalse();
});
it('keeps opportunistic STARTTLS when encryption is starttls', function () {
$transport = SmtpTransportFactory::fromSettings(smtpSettings([
'smtp_encryption' => 'starttls',
]));
expect($transport->isAutoTls())->toBeTrue()
->and($transport->getStream()->isTLS())->toBeFalse();
});
it('uses implicit TLS when encryption is tls', function () {
$transport = SmtpTransportFactory::fromSettings(smtpSettings([
'smtp_encryption' => 'tls',
'smtp_port' => 465,
]));
expect($transport->isAutoTls())->toBeTrue()
->and($transport->getStream()->isTLS())->toBeTrue();
});
it('infers implicit TLS on port 465 when encryption is null', function () {
$transport = SmtpTransportFactory::fromSettings(smtpSettings([
'smtp_encryption' => null,
'smtp_port' => 465,
]));
expect($transport->getStream()->isTLS())->toBeTrue();
});
it('applies a configured SMTP timeout to the transport stream', function () {
$transport = SmtpTransportFactory::fromSettings(smtpSettings([
'smtp_timeout' => 15,
]));
expect($transport->getStream()->getTimeout())->toBe(15.0);
});
it('maps none encryption to laravel mailer options that disable auto tls', function () {
expect(SmtpTransportFactory::mailerOptions(smtpSettings([
'smtp_encryption' => 'none',
])))->toBe([
'scheme' => 'smtp',
'encryption' => null,
'auto_tls' => '0',
]);
});
it('maps encryption to laravel mailer options', function (?string $mode, ?string $scheme, ?string $encryption) {
expect(SmtpTransportFactory::mailerOptions(smtpSettings([
'smtp_encryption' => $mode,
])))->toBe([
'scheme' => $scheme,
'encryption' => $encryption,
'auto_tls' => $mode === 'none' ? '0' : '',
]);
})->with([
'none' => ['none', 'smtp', null],
'starttls' => ['starttls', 'smtp', null],
'tls' => ['tls', 'smtps', 'tls'],
'unset' => [null, null, null],
]);