Search code examples
phpmailer

How to handle transient/intermittent PHPMailer SMTP connect errors


I have searched StackOverflow and the PHPMailer issues without success. There’s plenty on permanent PHPMailer SMTP connect errors but none that I could see on transient or intermittent ones.

I am using PHPMailer (6.2) and PHP 8.0 to send email to smtp.office365.com (Microsoft Exchange Online service) from my remote web server. Most of the time it works fine.

Intermittently, I suspect due to congestion at the network level or the SMTP server level, I see the following messages:

SMTP ERROR: Failed to connect to server: Connection refused (111)

SMTP ERROR: Failed to connect to server: Connection timeout

and then: SMTP connect() failed.

As the initial connection to the SMTP server is remote from the web server hosting the PHPMailer script, this means I can’t rely on the SMTP server handling email queuing and retries.

I have put in some basic code that retries the $email-> send command but so far this has never been successful in getting through when the SMTP Connect fails.

I’m seeking advice on how to handle (in PHPMailer) these transient errors ?

Many thanks in advance.

My server PHP script (extract):

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\SMTP;

require ($_SERVER['DOCUMENT_ROOT'] . '/vendor/phpmailer/phpmailer/src/Exception.php');
require ($_SERVER['DOCUMENT_ROOT'] . '/vendor/phpmailer/phpmailer/src/PHPMailer.php');
require ($_SERVER['DOCUMENT_ROOT'] . '/vendor/phpmailer/phpmailer/src/SMTP.php');      

// Load Composer's autoloader
require $_SERVER['DOCUMENT_ROOT'] . '/vendor/autoload.php';  
$email = new PHPMailer(true);//True means Throw errors

try {
    //Server settings
    $email->SMTPDebug = SMTP::DEBUG_CONNECTION;
    $debug = '';
    $email->Debugoutput = function($str, $level) {
        $GLOBALS['debug'] .= "$level: $str\n";
    };

    $email->isSMTP(); // Set mailer to use SMTP
    $email->CharSet = 'UTF-8';  //not important
    $email->Host = 'smtp.office365.com'; // Specify main and backup SMTP servers
    $email->SMTPAuth = true; // Enable SMTP authentication
    $email->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; 
    $email->Port = 587; // TCP port to connect to
    //Authentication
    $email->Username = ‘Myusername’; // SMTP username
    $email->Password = 'mypassword'; // SMTP password - must be App Password from Microsoftt


     //Recipients
      $email->SetFrom(‘MySenderemail’);
      $email->addReplyto(‘Myreplytoemail’, 'No-Reply');
     $email->addAddress( ‘MyTargetemailaddress’);

    // Content
    $email->isHTML(true); 
    $email->Subject   = $mailsubject;
    $email->Body      = $messagebody;
    $email->AltBody = 'This email can only be viewed in HTML. Please contact the SSN IT Coordinator.';

    //Send the email
    $email->send();
    elog($ModuleName . '|' . $authentic . ': ' . 'Mail Sent Successfully ' . ' ' . $mailsubject);
    echo($ModuleName . '|' . $authentic . ': ' . 'Mail Sent Successfully ' . ' ' . $mailsubject);
    return true;

} catch (Exception $e) {
    error_log($email->ErrorInfo);
    error_log($debug);
    if (strpos($email->ErrorInfo, 'SMTP connect() failed') !== false) {
        // Retry sending the email in case it was a transient error
        error_log ($ModuleName . '|' . $authentic . ': ' . 'First attempt at email failed, retrying: ' .  $email->ErrorInfo . ' ' . $mailsubject);
        try {$email->send();} catch (exception $e){
            //Failed second time so send error
            http_response_code(500);
            if (!headers_sent()) {header('HTTP/1.1 500 Internal Server Error');}
            elog($ModuleName . '|' . $authentic . ': ' . 'Retry Mail not sent ' . $mailsubject . $authentic . $e->getMessage());
           die($ModuleName . '|' . $authentic . ': ' . 'Retry Mail not sent ' . $mailsubject . $authentic . $e->getMessage());
        }
    } else {
        //Some error other than SMTP Connect failed so return it.
        http_response_code(500);
        if (!headers_sent()) {header('HTTP/1.1 500 Internal Server Error');}
        error_log ($ModuleName . '|' . $authentic . ': ' . 'Mail NOT SENT ' . $mailsubject . $authentic . $e->getMessage());
        die($ModuleName . '|' . $authentic . ': ' . 'Mail NOT SENT ' . $mailsubject . $authentic . $e->getMessage());
    }
}

Solution

  • It looks like there is a time element in your connection difficulties, so an immediate retry is unlikely to fare any better than the first one. The approach that I would recommend is also what is recommended by the PHPMailer docs: install a local mail server and relay through it. It will manage queuing, retries, exponential back off, and bounces, and your sending scripts will run much faster. This will be far more effective and reliable than trying to write a mail server in PHP, which is what you’re trying to do.

    Side note: since you’re using composer, you can delete those require lines for the PHPMailer classes; composer’s autoloader will load those classes for you automatically.