In Laravel, you can use the MAIL_ENCRYPTION environment variable to select what type of encryption you want your email connections to use. The default config files use this to set the encryption option on the smtp mail transport.

In previous versions of Laravel this encryption option is given verbatim to the Swift Mailer library used for SMTP emails 1:

if (! empty($config['encryption'])) {
    $transport->setEncryption($config['encryption']);
}

With $transport a Swift_SmtpTransport instance. This works exactly as intended.

However, Swift Mailer is not maintained anymore and Laravel 9 now uses Symphony Mailer. With this, the use of the encryption setting is now different 2:

$transport = $factory->create(new Dsn(
    ! empty($config['encryption']) && $config['encryption'] === 'tls' ? (($config['port'] == 465) ? 'smtps' : 'smtp') : '',
    ...
));

With $factory an EsmtpTransportFactory instance. The encryption setting is no longer passed verbatim and instead used to set the scheme to one of three possible values. Thus, we must look at how the factory uses this scheme 3:

$tls = 'smtps' === $dsn->getScheme() ? true : null;
...
$transport = new EsmtpTransport($host, $port, $tls, $this->dispatcher, $this->logger);

Combining this information, we can determine that $tls is set to true if and only if the encryption setting is tls and the port setting is 465. Otherwise $tls is set to null. Before this value is used, however, it is further modified in the EsmtpTransport constructor 4:

if (null === $tls) {
    if (465 === $port) {
        $tls = true;
    } else {
        $tls = \defined('OPENSSL_VERSION_NUMBER') && 0 === $port && 'localhost' !== $host;
    }
}

Here, if the port setting is 465 but this $tls is null it will be set to true anyway regardless of the encryption setting. We will ignore the other part for now, as it’s a mostly unrelated edge case. Next, we need to investigate how this $tls is used 5:

if (!$tls) {
    $stream->disableTls();
}

Thus, when $tls is true implicit TLS remains enabled, when $tls is null implicit TLS is disabled instead.

But what about STARTTLS? There is one final detail that needs to be covered 6:

if (!$stream->isTLS() && \defined('OPENSSL_VERSION_NUMBER') && \array_key_exists('STARTTLS', $this->capabilities)) {
    $this->executeCommand("STARTTLS\r\n", [220]);
    ...
}

According to this code, when implicit TLS is not enabled and the server advertises STARTTLS, Symphony Mailer always tries to enable it. There is no possibility to enforce or disable this.

Conclusion

To put all this information together, when the MAIL_PORT environment variable is set to 465 then Symphony Mailer will end up using implicit TLS. Otherwise, Symphony Mailer will try to use STARTTLS if the server supports it. There is no way for a Laravel user to change this behaviour. The MAIL_ENCRYPTION environment variable ends up not affecting this process. This leads to the following problems:

  • You cannot enable implicit TLS for any non-standard port, nor can you disable it on the standard port.
  • You cannot disable STARTTLS even when it is optional. This prevents you from connecting to your mailserver through localhost.
  • You cannot enforce STARTTLS when you know a server supports it. This makes Symphony Mailer vulnerable to even trivial downgrade attacks.

As for the edge case skipped earlier, when your MAIL_PORT is undefined or set to 0 it is modified. When MAI