MyBB Mail Handler Code (First pass)
Paste ID: svzPon2Z
Created: 2026-02-19 22:12:40
Expires: Never
Views: 47
Size: 8.74 KB
== INSTRUCTIONS ==
composer require symfony/mailer
then backup smtp.php in inc/mailhandlers/
=== BEGIN PHP CODE ===
<?php
/**
* MyBB 1.9
* Copyright 2014 MyBB Group, All Rights Reserved
* Copyright 2026 Setsuna Software L.C. and Kazuo Kuroi
*
* Website: http://www.mybb.com
* License: http://www.mybb.com/about/license
*
* Symfony Mailer integration is licensed under the MIT License.
* See: https://symfony.com/doc/current/mailer.html
*/
if(!defined("IN_MYBB"))
{
die("Direct initialization of this file is not allowed.<br /><br />Please make sure IN_MYBB is defined.");
}
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
use Symfony\Component\Mime\Email;
use Symfony\Component\Mime\Address;
if(!defined('MYBB_SSL'))
{
define('MYBB_SSL', 1);
}
if(!defined('MYBB_TLS'))
{
define('MYBB_TLS', 2);
}
class SmtpMail extends MailHandler
{
/**
* SMTP host.
*
* @var string
*/
public $host = '';
/**
* SMTP port.
*
* @var int
*/
public $port = 25;
/**
* SMTP username.
*
* @var string
*/
public $username = '';
/**
* SMTP password.
*
* @var string
*/
public $password = '';
/**
* Whether to use implicit SSL (port 465).
*
* @var bool
*/
public $use_ssl = false;
/**
* Whether to use STARTTLS (port 587).
*
* @var bool
*/
public $use_tls = false;
/**
* Connection timeout in seconds.
*
* @var int
*/
public $timeout = 5;
/**
* The last error message.
*
* @var string
*/
public $last_error = '';
/**
* Plain text message body, stored separately so Symfony can
* build multipart messages natively rather than consuming
* MailHandler's raw MIME string.
*
* @var string
*/
public $message_text = '';
function __construct()
{
global $mybb;
if(empty($mybb->settings['smtp_host']))
{
$this->host = @ini_get('SMTP') ?: 'localhost';
}
else
{
$this->host = $mybb->settings['smtp_host'];
}
switch((int)$mybb->settings['secure_smtp'])
{
case MYBB_SSL:
$this->use_ssl = true;
$this->port = 465;
break;
case MYBB_TLS:
$this->use_tls = true;
$this->port = 587;
break;
default:
$this->port = 25;
break;
}
// Explicit port setting overrides the protocol default
if(!empty($mybb->settings['smtp_port']))
{
$this->port = (int)$mybb->settings['smtp_port'];
}
elseif(empty($mybb->settings['smtp_port']) && @ini_get('smtp_port'))
{
$this->port = (int)@ini_get('smtp_port');
}
$this->username = $mybb->settings['smtp_user'] ?? '';
$this->password = $mybb->settings['smtp_pass'] ?? '';
}
/**
* Override MailHandler's raw MIME multipart construction.
* Symfony Mailer handles multipart natively, so we store the
* HTML and plain text parts separately and skip the raw boundary
* building that MailHandler would otherwise do.
*
* @param string $message HTML message body
* @param string $message_text Plain text alternative
*/
function set_html_headers($message, $message_text = '')
{
$this->message = $message;
$this->message_text = $message_text ?: strip_tags($message);
// Intentionally do not call parent — we do not want the raw
// MIME boundary construction; Symfony handles this for us.
}
/**
* Build and return a configured EsmtpTransport instance.
*
* @return EsmtpTransport
*/
protected function build_transport()
{
$transport = new EsmtpTransport(
$this->host,
$this->port,
$this->use_ssl // true = implicit SSL (port 465), false = plain or STARTTLS
);
if($this->use_tls)
{
$transport->getStream()->setStreamOptions([
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'allow_self_signed' => false,
],
]);
}
if(!empty($this->username))
{
$transport->setUsername($this->username);
}
if(!empty($this->password))
{
$transport->setPassword($this->password);
}
return $transport;
}
/**
* Sends the email via Symfony Mailer.
*
* @return bool True on success, false on failure.
*/
function send()
{
try
{
$transport = $this->build_transport();
$mailer = new Mailer($transport);
$email = new Email();
// From — from_named is already formatted as "Name" <email> by MailHandler
if(!empty($this->from_named) && $this->from_named !== $this->from)
{
$email->from(Address::create($this->from_named));
}
else
{
$email->from($this->from);
}
// Reply-To / Return-Path
if(!empty($this->return_email))
{
$email->replyTo($this->return_email);
}
// To — MailHandler stores comma-separated addresses in $this->to
$recipients = array_map('trim', explode(',', $this->to));
foreach($recipients as $recipient)
{
if(!empty($recipient))
{
$email->addTo($recipient);
}
}
$email->subject($this->orig_subject ?: $this->subject);
// Body — use parse_format set by MailHandler::build_message()
if($this->parse_format === 'html' || $this->parse_format === 'both')
{
$email->html($this->message);
if(!empty($this->message_text))
{
$email->text($this->message_text);
}
}
else
{
$email->text($this->message);
}
// Additional headers — skip anything Symfony sets itself
if(!empty($this->headers))
{
$skip = [
'from',
'reply-to',
'return-path',
'message-id',
'content-type',
'content-transfer-encoding',
'mime-version',
'x-priority',
'x-mailer',
];
$raw_headers = explode("\n", trim($this->headers));
foreach($raw_headers as $header_line)
{
$header_line = trim($header_line);
if(empty($header_line))
{
continue;
}
$parts = explode(':', $header_line, 2);
if(count($parts) !== 2)
{
continue;
}
$name = trim($parts[0]);
$value = trim($parts[1]);
if(in_array(strtolower($name), $skip))
{
continue;
}
$email->getHeaders()->addTextHeader($name, $value);
}
}
$mailer->send($email);
return true;
}
catch(\Symfony\Component\Mailer\Exception\TransportExceptionInterface $e)
{
$this->set_error($e->getMessage());
$this->fatal_error("Symfony Mailer transport error: " . $e->getMessage());
return false;
}
catch(\Exception $e)
{
$this->set_error($e->getMessage());
$this->fatal_error("Mailer error: " . $e->getMessage());
return false;
}
}
/**
* Get the last error message.
*
* @return string
*/
function get_error()
{
return $this->last_error ?: 'N/A';
}
/**
* Set the last error message.
*
* @param string $error
*/
function set_error($error)
{
$this->last_error = $error;
}
}