How to handle transient/intermittent PHPMailer SMTP connect errors - phpmailer

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());
}
}

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.

Related

PHPMailer not working when function is within and included file

Something is mighty odd. I created a basic test file with the code from PHPMailer.
# Import the PHPMailer class into the global namespace
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
require 'includes/PHPMailer/PHPMailer.php';
require 'includes/PHPMailer/SMTP.php';
require 'includes/PHPMailer/Exception.php';
function sendCampaignEmail ($email, $firstname, $lastname)
{
$mail = new PHPMailer(true); // Create a new PHPMailer instance. Passing `true` enables exceptions.
try {
//Server settings
# $mail->SMTPDebug = SMTP::DEBUG_SERVER; // Enable verbose debug output. SMTP::DEBUG_SERVER = client and server messages
# $mail->SMTPDebug = SMTP::DEBUG_CLIENT; // SMTP::DEBUG_CLIENT = client messages
$mail->SMTPDebug = SMTP::DEBUG_OFF; // SMTP::DEBUG_OFF = off (for production use)
$mail->isSMTP(); // Send using SMTP
$mail->Host = 'myhostxxxx.co.uk'; // Set the SMTP server to send through
$mail->SMTPAuth = true; // Enable SMTP authentication
$mail->Username = 'oobaclub#xxxxxxxxx.co.uk'; // SMTP username
$mail->Password = 'xxxxxmyEmailPasswordxxxxx'; // SMTP password
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; // Enable TLS encryption; `PHPMailer::ENCRYPTION_SMTPS` encouraged
$mail->Port = 465; // TCP port to connect to, use 465 for `PHPMailer::ENCRYPTION_SMTPS` above
//Recipients
$mail->setFrom('myfromemail.co.uk', 'My Name');
$mail->addAddress($email, $firstname . ' ' . $lastname); // Add a recipient
$mail->addReplyTo('myfromemail.co.uk', 'My Name');
// Content
$mail->isHTML(true); // Set email format to HTML
$mail->Subject = 'Here is the subject';
$mail->Body = 'Dear ' . $firstname . ' ' . $lastname . '<br/><br/>This is the HTML message body <b>in bold!</b>';
$mail->AltBody = 'This is the body in plain text for non-HTML mail clients';
$mail->send();
echo '<br/>Message has been sent to ' . $email . ' (' . $firstname . ' ' . $lastname . ')';
} catch (Exception $e) {
echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
}
}
$names= array();
$id=0;
$names[$id]['email'] = "atestemail#gmail.com";
$names[$id]['first_name'] = "Bob";
$names[$id]['last_name'] = "gMail"; $id++;
$names[$id]['email'] = "anothertest#testingmyemail.co.uk";
$names[$id]['first_name'] = "Sid";
$names[$id]['last_name'] = "Smith"; $id++;
$count=0;
while ($count < count($names))
{
sendCampaignEmail ($names[$count]['email'], $names[$count]['first_name'], $names[$count]['last_name']);
$count++;
}
THIS CODE ABOVE WORKS FINE.
So... Then, I took the function and put it into an Included file (where all my functions are: "globalfunctions.php").... And now it says "Fatal error: Uncaught Error: Class 'PHPMailer' not found"
So, now, at the top of my index.php, I have:
## PHP MAILER - # Import the PHPMailer class into the global namespace
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
require_once 'includes/PHPMailer/PHPMailer.php';
require_once 'includes/PHPMailer/SMTP.php';
require_once 'includes/PHPMailer/Exception.php';
require_once 'includes/connect.php';
require_once 'includes/globalfunctions.php';
I am confused as everything else works. All my other functions work. I thought namespaces were global... But, I tried adding the "use" code into the function... but, as expected, that didn't work either....
I am stumped.
PHP use declarations are local aliases that apply only to the file they appear in. You have to add a use statement in every file that uses that class; you can't put all your declares in one file and then include it from somewhere else.
Now might be a good time to learn how to use composer as it takes care of a lot of this.

Feedback form hangs due to PHPMailer

I have a feedback form, this script is filtering and validating data and writing data to the database. At the very bottom, through include, I connected a script with PHPMailer, which sends the text of the feedback form to my mail via gmail smtp.
If I comment out the script connections, then the form is submitted either immediately or after 1 second. With him, he can wait 2-3 seconds.
I submit form data via XMLHttpRequest, remotely. Upon successful submission, the form is reset to zero and the submit button becomes inactive, and via pop-up notifications I display the server's response. So it happened that I managed to press the send button 2-3 times until the script worked and, accordingly, several records were created and several letters were sent to the mail. Is this how it should be or have I configured PHPMailer wrong?
Let me know what data I need to attach. This is my PHPMailer script:
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
require $_SERVER['DOCUMENT_ROOT'] . '/form/PHPMailer/PHPMailer.php';
require $_SERVER['DOCUMENT_ROOT'] . '/form/PHPMailer/Exception.php';
require $_SERVER['DOCUMENT_ROOT'] . '/form/PHPMailer/SMTP.php';
$mail = new PHPMailer;
$mail->CharSet = 'UTF-8';
$mail->isSMTP();
$mail->SMTPDebug = 0; // 0 = off (for production use) - 1 = client messages - 2 = client and server messages
$mail->Host = gethostbyname("smtp.gmail.com");; // use $mail->Host = gethostbyname('smtp.gmail.com'); // if your network does not support SMTP over IPv6
$mail->Port = 587; // TLS only
$mail->SMTPSecure = 'tls'; // ssl is deprecated
$mail->SMTPAuth = true;
$mail->Username = 'mymail#gmail.com'; // email
$mail->Password = 'mypassword'; // password
$mail->setFrom($email, $name); // From email and name
$mail->addAddress('mymail#gmail.com', 'Admin'); // to email and name
$mail->Subject = $subject;
$mail->msgHTML("Message from: \n"."<h3>".$email."</h3>\n"."<h1>".$message."</h1>"); //$mail->msgHTML(file_get_contents('contents.html'), __DIR__); //Read an HTML message body from an external file, convert referenced images to embedded,
$mail->AltBody = 'HTML messaging not supported'; // If html emails is not supported by the receiver, show this body
// $mail->addAttachment('images/phpmailer_mini.png'); //Attach an image file
$mail->SMTPOptions = array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
)
);
$mail->send();
// if(!$mail->send()){
// echo "Mailer Error: " . $mail->ErrorInfo;
// }else{
// echo "Message sent!";
// }
?>
You're doing some inadvisable things here.
$mail->Host = gethostbyname("smtp.gmail.com");
This sets Host to a literal IP address, and that in turn means that you will never have it match a TLS certificate name. As a result you're having to disable TLS verification, which is never a good thing. If you understand exactly why you're doing this, and what the consequences are, that's fine, but if not, you shouldn't be doing it.
There isn't any error checking in this script. I suggest starting again using the gmail example provided with PHPMailer which is much more careful.
You've discovered why sending to remote mail servers with SMTP during web form processing generally a bad idea: it's too slow, partly by design. The best way to work around this is to install a local mail server (postfix is good) and configure it as a relay to your gmail account – if you search for that you'll find plenty of examples. When that's done, you can submit messages to localhost, it will be more or less instant, and it will take care of queuing, throttling, bounces etc.

AddStringAttachment giving unusual results

I am sending attachments (CSV) which I have been sending for years using mail() but decided to migrate to SMTP for better reliability.
Code 1 (CSV attachment)
$attachment = $this->CSVData; // "Date","Name","Dept" ... \n"2019-03-13","Dave" ...
$encoding = 'base64';
$contentType = 'text/csv';
$filename = $this->createFileName(); //Get FileDate and Name
$recipient = $delivery_email; // xxxxxxx#gmail.com
$subject = $this->emailHeader['subject'] . " CSV Data";
$message = 'Daily Data File';
$mail = new PHPMailer\PHPMailer\PHPMailer(true); // Passing `true` enables exceptions
try {
//Server settings
$mail->SMTPDebug = 0; // Enable verbose debug output
$mail->isSMTP(); // Set mailer to use SMTP
$mail->Host = SMTP_HOST; // Specify main and backup SMTP servers
$mail->SMTPAuth = true; // Enable SMTP authentication
$mail->Username = SMTP_USER; // SMTP username
$mail->Password = SMTP_PASS; // SMTP password
$mail->SMTPSecure = SMTP_AUTH; // Enable TLS encryption, `ssl` also accepted
$mail->Port = SMTP_PORT; // TCP port to connect to
//Recipients
$mail->setFrom($this->fromEmail, $this->fromEmailName); // Add a FROM
$addresses = explode(',', $recipient);
foreach ($addresses as $address) {
$mail->AddAddress(trim($address)); // Add a recipient(s)
}
if ( !empty($this->emailCC) ) $mail->addCC($this->emailCC); // Add a CC
//13-03-2019: Add the attachment to the email
$mail->AddStringAttachment($attachment, $filename, $encoding, $contentType);
//Content
$mail->isHTML(true); // Set email format to HTML
$mail->Subject = $subject;
$mail->Body = $message;
$mail->AltBody = 'This email is formatted in HTML';
$mail->send();
$this->fo->printStatus('Email successfully sent to: '. $recipient );
return true;
} catch (Exception $e) {
$this->fo->printStatus( basename(__FILE__) .' '. __LINE__ . ': Message could not be sent. Mailer Error: '. $mail->ErrorInfo );
return false;
}
The email gets delivered to me BUT ...
Problems:
When viewing in Gmail browser - I get message: "
Gmail virus scanners are temporarily unavailable – The attached files haven't been scanned for viruses. Download these files at your own risk."
When viewing in Gmail browser - I cant save/download the file? (clicking the download button does nothing)
When clicking attachment to view in browser, I now get error: "Whoops. There was a problem while previewing this document"
I try "Show Original" and it takes 30+ seconds for the email to load which just shows the base64 encoded data
I tried to open in inbox in Outlook and after 5 minutes of the emails not loading I gave up (thinking the emails are not encoded properly or something causing outlook to get stuck)
It looks like it is working (i.e. the file looks legit based on the gmail icon preview) but I cant do anything else with it and I don't know if it is a Gmail issue or File Issue.
Any advice?
Turns out that Gmail was having issues all yesterday afternoon with attachments. It was a Gmail issue - The timing is unbelievable
https://www.theguardian.com/technology/2019/mar/13/googles-gmail-and-drive-suffer-global-outages

SMTP connect() failed error after 70 successful sends

A number of people had shown similar question, using phpmailer with g-mail. This problem is slightly different and may need a different answer.
The phpmailer code is fairly standard (below). I have a loop that goes through some data in the table and builds customised messages for recipients, sending a message for each row in the table. When the message is sent, it successfully completes about 75 out of some 100 recipients, and for the last 25, it reports the SMTP connect() failed error. This doesn't happen every time, and occasionally, the script goes through the loop without errors.
<?php
$mail = new PHPMailer();
$mail->IsSMTP(); // telling the class to use SMTP
$mail->SMTPDebug = 0; // enables SMTP debug information (for testing)
$mail->SMTPsecure = "ssl";
$mail->SMTPAuth = true; // enable SMTP authentication
$mail->Host = "smtp.gmail.com"; // sets the SMTP server
$mail->Port = 587; // set the SMTP port for the GMAIL server
$mail->Username = "xx"; // SMTP account username
$mail->Password = "xx"; // SMTP account password
$mail->SetFrom("xx#gmail.com");
$mail->isHTML(true);
$mail->CharSet = "utf-8";
$mail->Subject = $msgsubject;
$mail->AltBody = "Invitation";
do {
$membername = $row_musicians['name'];
$membernumber = $row_musicians['number'];
$emailaddress = $row_musicians['email'];
$rhdate = $row_musicians['rhdate'];
$mail->clearAttachments();
$mail->Body = 'Dear '.$membername.',
<p>'.$maintext.'</p>
';
// some more text using custom variables from database
// closing part of the message
$mail->Body .= '<p>'.nl2br($closing, false).'</p>
</body>
</html>';
$mail->AddAddress($emailaddress, $membername); // sending each message
sleep(1); // wait one second between sends, to avoid spam filters
echo '<br />
Recipient: '.$membername.' ('.$emailaddress.') -- ';
if(!$mail->Send()) {
echo "Mailer Error: <div style='color:#009999'>" . $mail->ErrorInfo." </div>";
} else {
echo "Message sent!";
}
$mail->ClearAddresses();
} while ($row_musicians = mysql_fetch_assoc($musicians));
mysql_free_result($musicians);
?>
I recommend basing your code on the mailing list example provided with PHPMailer.
You're doing most things right - initialising PHPMailer first and setting properties that are common to all messages, but one key thing is missing:
$mail->SMTPKeepAlive = true;
Without this it means that it closes and reopens a connection for every message, and you're mostly likely running into connection rate limits.
you're also using the combination of Port= 587 and SMTPSecure = 'ssl' which will generally not work; switch to Port = 465.
I'd also recommend against the do/while loop - it will fail if your database query fails to find anything because it's bottom-tested and thus will always run once, even if there is no data. Use a while or foreach loop instead.
The mailing list example does all of these things already.
Final thing - if this is all your code, it looks like you're running an old version of PHPMailer, so get the latest.

swift mailer not working

i am using swift mailer to send emails to my company agents
here is my code
require_once 'Swift-5.0.1/lib/swift_required.php';
// Create the Transport
$transport = Swift_SmtpTransport::newInstance('test.co.uk', 25);
$mailer = Swift_Mailer::newInstance($transport);
// Create a message
$message = Swift_Message::newInstance('Wonderful Subject')
->setFrom(array('test#test.co.uk' => 'test'))
->setTo(array('test1247#test.com'=> 'A name'))
->setBody('Here is the message itself');
$numSent = $mailer->send($message);
printf("Sent %d messages\n", $numSent);
if (!$mailer->send($message, $failures)) {
echo "Failures:";
print_r($failures);
} else {
echo 'email sent successfully';
}
OUTPUT::
Sent 0 messages Failures:Array ( [0] => test1247#yahoo.com )
is there some thing wrong in my code ?
also want to know what are these 2 parameters in first line ?
$transport = Swift_SmtpTransport::newInstance('test.co.uk', 25);
is there should be the name of my webmail which will send the mail to the agents?
or domain has to me mention there?
changing 25 to other numbers throw error
Check that you can telnet into khyberexchange.co.uk on port 25. This will check if the port is open and a service is running.
You might also need to authenticate to your mail server to be able to send email. See here for more information (top box)

Resources