[Code Example] Sending SMTP Emails using fsockopen()
Noticed there was a few questions on here about sending mail using the mail() function and I had a few problems when I used it a while back, mainly its overly slow but also it doesn't give much control.
So I wrote my own mail function, its much easier with cURL but I didn't have that available on the server I was working on so I used fSockopen which is more or less standard on all hosting packages.
I welcome anyone who has suggestions to improve it and reuse if you like.
FUNCTION:
PHP Code:
function authSendEmail($from, $fromname, $to, $toname, $subject, $message)
{
//SMTP + SERVER DETAILS
/* * * * CONFIGURATION START * * * */
$smtpServer = "****.atlas.pipex.net";
$port = "25";
$timeout = "2";
$username = "no-reply@*******.co.uk";
$password = "*********";
$identify = "localhost"; // This is used at the start of the connection to identify yourself but value isn't checked
$newLine = "\r\n";
/* * * * CONFIGURATION END * * * * */
//Connect to the host on the specified port
$smtpConnect = @fsockopen($smtpServer, $port, $errno, $errstr, $timeout);
if(empty($smtpConnect)) {
$logArray['connection'] = "Failed to connect";
$logArray['SUCCESS'] = "FALSE";
return $logArray;
}
stream_set_timeout($smtpConnect, $timeout);
$smtpResponse = fgets($smtpConnect, 515);
$info = stream_get_meta_data($smtpConnect);
if ($info['timed_out']) {
$logArray['connection'] = "Timeout: $smtpResponse";
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
$logArray['connection'] = "Connected: $smtpResponse";
//--- Say hello to the server --------
fputs($smtpConnect,"EHLO ".$identify . $newLine);
$logArray['ehlo'] = fgets($smtpConnect, 515);
$info = stream_get_meta_data($smtpConnect);
$i=0;
while (!$info['timed_out']) {
$logArray['ehlo'] .= fgets($smtpConnect, 515);
$info = stream_get_meta_data($smtpConnect);
$i++;
}
if ($i=0) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Request Auth Login --------
fputs($smtpConnect,"AUTH LOGIN" . $newLine);
$logArray['authrequest'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['authrequest'],"334")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Send username --------
fputs($smtpConnect, base64_encode($username) . $newLine);
$logArray['authusername'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['authusername'],"334")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Send password --------
fputs($smtpConnect, base64_encode($password) . $newLine);
$logArray['authpassword'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['authpassword'],"235")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Email From --------
fputs($smtpConnect, "MAIL FROM: $from" . $newLine);
$logArray['mailfrom'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['mailfrom'],"250")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Email To --------
fputs($smtpConnect, "RCPT TO: $to" . $newLine);
$logArray['mailto'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['mailto'],"250")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- The Email --------
fputs($smtpConnect, "DATA" . $newLine);
$logArray['data1'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['data1'],"354")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Construct Headers --------
$headers = "MIME-Version: 1.0" . $newLine;
$headers .= "Content-type: text/html; charset=iso-8859-1" . $newLine;
$headers .= "To: ".$toname." <".$to.">". $newLine;
$headers .= "From: ".$fromname." <".$from.">" . $newLine;
$headers .= "Subject: ".$subject.$newLine;
$message = str_replace("\n.", "\n..",$message);
fputs($smtpConnect, $headers.$newLine.$message."\n.\n");
$logArray['data2'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['data2'],"250")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Say Bye to SMTP --------
fputs($smtpConnect,"QUIT" . $newLine);
$logArray['quit'] = fgets($smtpConnect, 515);
//fclose($smtpConnect);
$logArray['SUCCESS'] = "TRUE";
return $logArray;
}
USAGE:
PHP Code:
function SendEmail_Error($MSG)
{
// This is sent to the Operator when an error occours.
$subject = "An Error has occurred on Eden Photography";
$message = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
$message.= "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n";
$message.= "<head><style type=\"text/css\">\n";
$message.= ".ErrorMSG{font-family: Courier New, sans-serif; font-size:12pt; color:#222222;}\n";
$message.= ".ErrorSQL{font-family: Courier New, sans-serif; font-size:12pt; color:#222252; width:100%; border-style:solid; border-color:#222252; border-width:1px; background-color:#CCBBBB; padding: 10px 0px;}\n";
$message.= ".ErrorText{font-family: Tahoma, Arial, sans-serif; font-size:10pt; color:#222222;}";
$message.= ".ErrorCode{font-family: Courier New, sans-serif; font-size:12pt; color:#222252; width:100%; border-style:solid; border-color:#222252; border-width:1px; background-color:#BBCCBB; padding: 10px 0px;}";
$message.= ".ErrorTitle{font-family: Tahoma, Arial, sans-serif; font-size:11pt; color:#222222; text-decoration:underline; margin-bottom:10px;}";
$message.= "</style>";
$message.= "</head><body>";
$message.= "The error was as follows:<br/>\n<br/>\n".$MSG."\n</body></html>";
//echo "<div class=\"BrowseBox\">To: ".CONST_EMAIL_ERROR."<br/>\n<strong>".$subject."</strong><br/>\n".$message."<br/><br/></div>";
return authSendEmail(CONST_EMAIL_FROM_ADDRE,CONST_EMAIL_FROM_NAME,CONST_EMAIL_ERROR,"Error Handler", $subject, $message);
}
OUTPUT:
PHP Code:
Array
(
[connection] => Connected: 220 ****.atlas.pipex.net ESMTP Exim 4.71 - "ATLAS SMTP Service" Wed, 10 Aug 2011 23:31:01 +0100
[ehlo] => 250-****.atlas.pipex.net Hello #####.atlas.pipex.net [10.15.10.49]
250-SIZE 31457280
250-PIPELINING
250-AUTH PLAIN LOGIN
250 HELP
[authrequest] => 334 VXNlcm5hbWU6
[authusername] => 334 UGFzc3dvcmQ6
[authpassword] => 235 Authentication succeeded
[mailfrom] => 250 OK
[mailto] => 250 Accepted
[data1] => 354 Enter message, ending with "." on a line by itself
[data2] => 250 OK id=1QrHIl-00031o-Pe
[quit] => 221 ****.atlas.pipex.net closing connection
[SUCCESS] => TRUE
)
Re: [Code Example] Sending SMTP Emails using fsockopen()
Not that I don't appreciate the post, but this probably belongs in the PHP Codebank.
But anyway, I totally support the individual efforts of people creating their own solutions rather than just taking someone else's code (great way to learn). I personally prefer object-oriented solutions, though, and use the SwiftMailer library for sending SMTP mail, but many use PEAR's Mail class.
Re: [Code Example] Sending SMTP Emails using fsockopen()
Thread moved from the 'PHP' forum to the 'CodeBank - PHP' forum.
Re: [Code Example] Sending SMTP Emails using fsockopen()
Quote:
Originally Posted by
kows
Not that I don't appreciate the post, but this probably belongs in the
PHP Codebank.
But anyway, I totally support the individual efforts of people creating their own solutions rather than just taking someone else's code (great way to learn). I personally prefer object-oriented solutions, though, and use the
SwiftMailer library for sending SMTP mail, but many use PEAR's Mail class.
Good call on using the CodeBank, was just thinking thats where the questions are so figured. At least this way there is a trail here :)
I agree with the building your own solutions method but I find a good way to learn is altering someone elses code to suit your needs.
I made this from a simple example I found that didn't use authentication and couldn't cope with more than one line on each responses.
Yea the OOP thing I normally end up doing as a conversion at the end, yea bad habit I should get out of haha. Gives somebody something to do if they want to use it like that I guess.
Ow as for using extentions I would have done that but the person's site I did this for was hosted on a server with very limited setup and wasn't able to change that. Had to do a similar thing with the PayPal intergration.
Re: [Code Example] Sending SMTP Emails using fsockopen()
This looks to be a great piece of code.
I have been using Swiftmailer,
but I had problems and there is no one to ask
no forum or anything.
So I would rather use raw code and know what is happening :)
I have not got my head around OOP so this is great for me :)
I like all the error logging with the array.
A couple of QUESTIONS :
1) What do you do with array on completion?
Do you write it to a log file, or just write the errors to a log file ?
2) Where does the return email address go ?
Do I just add another header eg 'Reply-To: webmaster@example.com'
Re: [Code Example] Sending SMTP Emails using fsockopen()
I have a couple more questions :blush:
1) In: fputs($smtpConnect,"EHLO ".$identify . $newLine);
Why do you use "EHLO " and not "HELO" as I have seen in other scripts ?
2) In: $smtpConnect = @fsockopen($smtpServer, $port, $errno, $errstr, $timeout);
Whats happens to those $errno, $errstr ?
They do not seem to get used or logged ?
3) I want to database the results.
Is this a proper use of the function ?
Assuming the output of the function is $logArray.
Not sure if this is correct :
PHP Code:
if ( authSendEmail($fm_email, $fm_name, $to, $contact, $subject, $message)[SUCCESS] == FALSE ) {
// update failure database
$sql = "INSERT INTO `email_fail` (type,email,send_dt,connection,helo,authrequest,authusername,authpassword,mailfrom,mailto,data1,data2,quit) VALUES ( '$send_type', '$to','$today_time',$logArray[connection],$logArray[ehlo],$logArray[authrequest],$logArray[authusername],$logArray[authpassword],$logArray[mailfrom],$logArray[mailto],$logArray[data1],$logArray[data2],$logArray[quit] )";
mysql_query($sql) or die("Email Fail query problem". mysql_error());
echo "<br>$Rctr ) Failed to Send Campaign to $email... at $failures";
} // end if
Thanks for helping :)
.
Re: [Code Example] Sending SMTP Emails using fsockopen()
Quote:
Originally Posted by
Davvit
1) What do you do with array on completion?
Do you write it to a log file, or just write the errors to a log file ?
Depends on the usage, if you want you can just ignore it and only check the SUCCESS element to check it worked. In debug mode I would check if it was successful and if not print the array to the client (obviously not wise for live systems).
What I've done recently is use a database to queue up outgoing emails so when the cronjob runs the script it then converts the array to a string and stores it in the database as the result of the attempt. So if you realise your emails are failing all of a sudden you can check the Db and see why.
Quote:
Originally Posted by
Davvit
2) Where does the return email address go ?
Do I just add another header eg 'Reply-To:
webmaster@example.com'
Yea this should be added to the emails headers under //--- Construct Headers --------
Quote:
Originally Posted by
Davvit
1) In: fputs($smtpConnect,"EHLO ".$identify . $newLine);
Why do you use "EHLO " and not "HELO" as I have seen in other scripts ?
They basically mean the same thing apart from HELO is the basic and EHLO is the advanced. To make it unviversal you could try EHLO and then if it responds with an error drop down to HELO. The first mail server I was using worked with HELO but the one I use now only accepts EHLO.
www.gordano.com/kb.htm?q=316
Quote:
Originally Posted by
Davvit
2) In: $smtpConnect = @fsockopen($smtpServer, $port, $errno, $errstr, $timeout);
Whats happens to those $errno, $errstr ?
They do not seem to get used or logged ?
Your right I've not used them but you would use them to diagnose any issues you have just getting a connection to the server.
Quote:
Originally Posted by
Davvit
3) I want to database the results.
Is this a proper use of the function ?
Like I mentioned at the start of this post I recently used this function to send emails in batchs through a database. When I did it I just converted the array straight to a string rather than store each element in its own field. If you prefer that route then thats fine but my choice of this was that if I add a new element onto the array it automatically gets stored int he Db still.
Also with your code you need to store the return array of the function in a variable because your going to access it multiple times in your code.
I've tidied it up a little and stripped some bits out but the code I used was:
PHP Code:
<?php
require_once "inc-bin/global.inc.php";
$i=0;
$query = "SELECT `emailID`, `toName`, `toAddress`, `fromName`, `fromAddress`, `subject`, `body`, `html`, `attempts` ";
$query.= "FROM `email` ";
$query.= "WHERE `attempts` < '6' ";
$query.= "AND `sent` = '0' ";
$query.= "LIMIT 0, 3 ";
if (!(false === $MainResults = $DB1->SQL_Query_Supress($query))) {
while ($MainResult = $DB1->SQL_Fetch_Array($MainResults)) {
$query = "UPDATE `email` SET ";
$query.= "`attempts` = '".($MainResult['attempts']+1)."' ";
$query.= "WHERE `emailID` = '".$MainResult['emailID']."' ";
$DB1->SQL_Query_Supress($query);
$EMAIL_result = authSendEmail($MainResult['fromAddress'], $MainResult['fromName'], $MainResult['toAddress'], $MainResult['toName'], $MainResult['subject'], $MainResult['body'], $MainResult['html']);
$EMAIL_result['method'] = "authSendEmail()";
if ($EMAIL_result['SUCCESS'] == "TRUE") {
$query = "UPDATE `email` SET ";
$query.= "`sent` = '1' ";
$query.= "WHERE `emailID` = '".$MainResult['emailID']."' ";
$DB1->SQL_Query_Supress($query);
$query = "INSERT INTO `emailattempt` (`emailID`, `responses`, `datetime`, `success`) ";
$query.= "VALUES ('".$MainResult['emailID']."', '".print_r($EMAIL_result,true)."', '".date('Y-m-d H:i:00', time())."', '1') ";
$results = $DB1->SQL_Query_Supress($query);
}
else {
$query = "INSERT INTO `emailattempt` (`emailID`, `responses`, `datetime`, `success`) ";
$query.= "VALUES ('".$MainResult['emailID']."', '".print_r($EMAIL_result,true)."', '".date('Y-m-d H:i:00', time())."', '0') ";
$results = $DB1->SQL_Query_Supress($query);
}
$i++;
}
}
?>
Re: [Code Example] Sending SMTP Emails using fsockopen()
Firstly,
I REALLY appreciate your sharing this and helping :)
One thing that I don't understand:
If email can be successfully sent out using this method, why do people write libraries of code - like the Swiftmail class and PHPMailer class ?
Do those things do any more than what can be done with fsockopen()
Is their underlying php function still fsockopen() fget & fputs ?
For me, it seems FAR better to understand and write the code with php functions rather than mess around with classes ? The classes just confuse the situation and when something doesn't work, its difficult to find out why.
Anyway - thanks again for your example,
I am going to adapt it to my situation.
There is one area I am not clear about:
How would I capture those $errno, $errstr and put them into the $logArray ?
Thanks
.
Re: [Code Example] Sending SMTP Emails using fsockopen()
Hi again,
There is something else that is rather fundamental.
The PHP manaual syas this about the php mail() function:
It is worth noting that the mail() function is not suitable for larger volumes of email in a loop. This function opens and closes an SMTP socket for each email, which is not very efficient.
Well unless I have miss-read the code,
Are we not doing the same thing ?
Should we not be opening the connection and then sending many emails
before closing the connection ?
I don't know how many we can send.
Presumably we need to check before we send each one that the connection is still open.
And if one email fails, we should not close the connection, we should log the failure and move onto the next one, keeping the connection open.
Not sure how we adjust the scripting to accommodate this :o:o
.
Re: [Code Example] Sending SMTP Emails using fsockopen()
Quote:
Originally Posted by
Davvit
If email can be successfully sent out using this method, why do people write libraries of code - like the Swiftmail class and PHPMailer class ?
Do those things do any more than what can be done with fsockopen()
Is their underlying php function still fsockopen() fget & fputs ?
For me, it seems FAR better to understand and write the code with php functions rather than mess around with classes ? The classes just confuse the situation and when something doesn't work, its difficult to find out why.
Behind the scenes they’ll do more or less the same thing but the libraries will be able to cope with different situations this code wont. To use something like this is a lot better if you want to understand how the email scripts work, especially if you then modify it. If you were doing an official website however you’d probably want something reliable, been tested, and wont give you headaches when the mail server gets an upgrade.
For example if the mail server doesn’t support EHLO a library would drop down to HELO and still work. Obviously you can add that to this code and once you’ve built enough into it you can make it a class. Then you have a mail object that you can reuse in all your sites and you know it really well.
Quote:
Originally Posted by
Davvit
There is one area I am not clear about:
How would I capture those $errno, $errstr and put them into the $logArray ?
They are both byRef parameters, so after the fsockopen() call they will contain error information if it failed. Simplest way to use this is:
PHP Code:
// From
$logArray['connection'] = "Failed to connect";
// To
$logArray['connection'] = "Failed to connect. Error: ".$errstr." (". $errno.")" ;
Quote:
Originally Posted by
Davvit
The PHP manual says this about the php mail() function:
It is worth noting that the mail() function is not suitable for larger volumes of email in a loop. This function opens and closes an SMTP socket for each email, which is not very efficient.
Well unless I have miss-read the code,
Are we not doing the same thing ?
Should we not be opening the connection and then sending many emails
before closing the connection ?
I don't know how many we can send.
Presumably we need to check before we send each one that the connection is still open.
And if one email fails, we should not close the connection, we should log the failure and move onto the next one, keeping the connection open.
Not sure how we adjust the scripting to accommodate this :o:o
Your right and I did think about changing the code to allow multiple messages in one connection. Its not too difficult to do but you’d need to change how the function is called as well and I decided against it because it would also affect how I stored the log.
Probably a good thing to start off trying to play with, basically you would not call the QUIT command and instead send MAIL FROM: $from again. The response received just before the QUIT command is what tells you if the email sent. 250 means success.
Even though this code opens a new connection for each email it still works a lot faster than the mail() function. I’ve not tried it for a while but I was getting approx. a 30sec wait when using the mail() function, which is too long if you’re doing it while the user waits for the page to display. Then this method was taking literally a couple of seconds.
If you’re going to have a go changing it just set it to output the log on every call and you will see what’s being returned. Also you can use a telnet connection to your mail server and play about using different commands.
Re: [Code Example] Sending SMTP Emails using fsockopen()
I've actually just noticed the code I posted for the CronJob relies on a slightly newer version of the function, mainly that it passes in a flag which switches the MIME type between Plain or HTML. But when I went to post that I actually realised I'd already done that logging of the $errno & $errstr mentioned earlier.
Also the way it checks if the EHLO response is finsihed is slightly different. The first example waits for it to timeout (reason the timeout is reduced to 2 seconds at the start), but the newer one also checks if its the end of the stream.
PHP Code:
function authSendEmail($from, $fromname, $to, $toname, $subject, $message, $html_MINE)
{
//SMTP + SERVER DETAILS
/* * * * CONFIGURATION START * * * */
$smtpServer = CONST_EMAIL_SMTP;
$port = CONST_EMAIL_SMTP_PORT;
$timeout = CONST_EMAIL_SMTP_TIMEOUT;
$username = CONST_EMAIL_SMTP_USERNAME;
$password = CONST_EMAIL_SMTP_PASSWORD;
$identify = $_SERVER['REMOTE_ADDR'];;
$newLine = "\r\n";
/* * * * CONFIGURATION END * * * * */
//Connect to the host on the specified port
$smtpConnect = @fsockopen($smtpServer, $port, $errno, $errstr, $timeout);
if(empty($smtpConnect)) {
$logArray['errno'] = $errno;
$logArray['errstr'] = $errstr;
$logArray['connection'] = "Failed to connect";
$logArray['SUCCESS'] = "FALSE";
return $logArray;
}
stream_set_timeout($smtpConnect, $timeout);
$smtpResponse = fgets($smtpConnect, 515);
$info = stream_get_meta_data($smtpConnect);
if ($info['timed_out']) {
$logArray['connection'] = "Timeout: $smtpResponse";
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
$logArray['connection'] = "Connected: $smtpResponse";
//--- Say hello to the server --------
fputs($smtpConnect,"EHLO ".$identify . $newLine);
$logArray['ehlo'] = fgets($smtpConnect, 515);
$info = stream_get_meta_data($smtpConnect);
$i=0;
while (!($info['timed_out'] || $info['unread_bytes'] == 0)) {
$logArray['ehlo'] .= fgets($smtpConnect, 515);
$info = stream_get_meta_data($smtpConnect);
$i++;
}
if ($i=0) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Request Auth Login --------
fputs($smtpConnect,"AUTH LOGIN" . $newLine);
$logArray['authrequest'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['authrequest'],"334")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Send username --------
fputs($smtpConnect, base64_encode($username) . $newLine);
$logArray['authusername'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['authusername'],"334")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Send password --------
fputs($smtpConnect, base64_encode($password) . $newLine);
$logArray['authpassword'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['authpassword'],"235")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Email From --------
fputs($smtpConnect, "MAIL FROM: <".$from.">" . $newLine);
$logArray['mailfrom'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['mailfrom'],"250")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Email To --------
fputs($smtpConnect, "RCPT TO: <".$to.">" . $newLine);
$logArray['mailto'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['mailto'],"250")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- The Email --------
fputs($smtpConnect, "DATA" . $newLine);
$logArray['data1'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['data1'],"354")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Construct Headers --------
$headers = "MIME-Version: 1.0" . $newLine;
if ($html_MINE) {
$headers .= "Content-type: text/html; charset=iso-8859-1" . $newLine;
}
else {
$headers .= "Content-type: text/plain; charset=iso-8859-1" . $newLine;
}
$headers .= "To: ".$toname." <".$to.">". $newLine;
$headers .= "From: ".$fromname." <".$from.">" . $newLine;
$headers .= "Subject: ".$subject.$newLine;
$message = str_replace("\n.", "\n..",$message);
fputs($smtpConnect, $headers.$newLine.$message.$newLine.".".$newLine);
$logArray['data2'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['data2'],"250")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Say Bye to SMTP --------
fputs($smtpConnect,"QUIT" . $newLine);
$logArray['quit'] = fgets($smtpConnect, 515);
//fclose($smtpConnect);
$logArray['SUCCESS'] = "TRUE";
return $logArray;
}
Re: [Code Example] Sending SMTP Emails using fsockopen()
Hi Bertie,
Thanks for the update - that's great.
And I am glad that I am beginning to understanding things correctly. :)
By the way, I am running a dedicated server and have a good relationship with the
server provider who also my ISP - They know I am working on this project to send
out loads ( thousands ) of emails. So there is no problem with that.
So the mail server is on mail.mydomain.com
Like you, I prefer to understand the whole code rather than relying on other libraries
cos no matter how well they are tested, if something goes wrong, I wont be able
to know where to look.
You said:
Quote:
For example if the mail server doesn’t support EHLO a library
Is that the mail server library on my server ?
Or do you mean on the server that my server is sending the emails to ?
If it is my server , then I could just find out what is supported and use that.
However - having said that, if its only a few lines of extra code to drop down to HELO
- then we may as well include them ( just in case ).
This timeout - does that mean after a specified number of seconds and no
response comes, we kill the connection ?
Or does it mean total time connection can be open for ?
Multiple SMTP sending connections
Not sure how this works, but I have been doing some research:
Quote:
The software will rotate between each SMTP sending connection available
— this allows users to spread out the sending load among multiple SMTP servers,
which can offer considerable benefits with regards to performance and deliverability.
On my dedicated server, I have several websites. As far as I know each can have its
own has is own SMTP sending connection.
So if I have a 3 virtual servers set up as
"my-domain.com"
"my-second-domain.com"
"my-third-domain.com"
Then I could open an smpt connection on each one ?
I will have to ask my ISP if I have any restrictions - so far they have not said I do, and
because I was asking if my processor and bandwidth was enough, they would have said so.
Anyway I will check again.
But this is interesting:
Quote:
If you have more than one email sending connection set up, you can edit each
connection to change the “Emails Per Cycle” setting. (Note: you must have at least two
connections set up before you see this option.) Emails per cycle determines how many
emails to send with the given connection before moving on to the next connection. By
default, it is set to 50 emails per connection. This setting is very useful, as it allows
you to distribute more emails with a certain server than with another, if you wanted to do so.
For example, let us say that you have 5 SMTP servers, and each server can only send 600
emails per hour, but you want to send as quickly as possible using all 5 of these SMTP connections.
What you should do if you wanted to send as quickly as possible using all of your connections
would be to set the sending throttling settings to 3000 emails per hour, and set the Emails Per Cycle
to 600. This would send 600 emails with “Connection 1? at a rate of 3000/hour, which would take
about 12 minutes to do. Then it would move on to “Connection 2,” and send another 600 emails
within 12 minutes. It would then move on to “Connection 3,” etc. By the time the software
has rotated through all five connections, one hour will have passed, and approximately 3000
emails will have been sent out, but none of the SMTP connections will have gone over their limit.
So it looks to me like they send the "Cycle" number of emails, like 600 ,in one connection before
closing it.
Now, one thing about this I am not sure about.
If the software is only using one connection at a time,
then I do NOT see any advantage. Only if there is a limitation on the
SMTP servers sending rate is there an advantage.
But - can three SMPT servers be opened and used concurrently ?
If so, then that would give an advantage.
Although my IPS wrote this:
Quote:
I would say your biggest throttle wouldn't be your server or bandwidth but the servers you send to. Your server can send a large amount of mail very fast but the receiving ends all have limitations in place to prevent abuse on their networks. Some people have legitimate emails that need to go to a large portion of users on a domain. In these cases I would recommend contacting some of the big names such as gmail, hotmail and yahoo to see what their policy is and throttle your email according to their networks. Then for the less commonly known networks take the lowest throttle and make that the default until you start seeing issues with mail going through. This will of course take a decent amount of debugging on your part but the end result will be very good since all of your emails will be delivered successfully.
I also asked my IPS:
Is my current connection broad enough for me to ramp up the service ?
Their answer:
Quote:
Yes, we don't limit your bandwidth but please be careful as you have a 2000GB cap per a month. If you somehow exceed that in a month the overage charges are $1/GB, however most customers never even break 100GB in a month so I think you will be ok.
So, from what they have written, additional SMPT connections
may not be of any benefit ???
Now, if I should use 3 connections:-
I have a "queue" table which holds all the emails that need to be sent.
There could be 3000-5000 emails in the queue.
Here is an idea of how it could work:
1) Read in 100 emails from the "queue" table
2) Open 1st Connection
3) Check it opened successfully. If not ( what ? try again or try a different connection ?
4) Call the send function: Attempt to Send email with all the checking and log results in array.
4a) write the logarray to logfile.txt
5) Do (4) until end of 100 batch
5 a ) Update the queue and subscription tables using SQL query on logfile.txt
6) Read in next 100 emails from the "queue" table
7) Open 2nd connection
8) Check it opened successfully. If not ( what ? try again or try a different connection ?
9) Call the send function: Attempt to Send email with all the checking and log results in array.
9 a) write the logarray to logfile.txt
10) Do (9) until end of 100 batch
10 a ) Update the queue and subscription tables using SQL query on logfile.txt
11) Read in next 100 emails from the "queue" table
12) Open 2nd connection
13) Check it opened successfully. If not ( what ? try again or try a different connection ?
14) Call the send function: Attempt to Send email with all the checking and log results in array.
14 a) write the logarray to logfile.txt
15) Do (14) until end of 100 batch
15 a ) Update the queue and subscription tables using SQL query on logfile.txt
But because I only have 3 smpt connections, I need to loop
back and use the first connection again.
Note:
I am not sure if writing the logarray to a logfile.txt with fwrite()
is faster than a dbase query update or not.
Doing hundreds of SQL queries could slow down the process.
Because it seems that a simple fwrite() should be faster, I thought to write the txt file then
use it to update the queue and subscription tables with one SQL query after processing
each 100 email batch.
What do you think?
Have you used multiple smpt connections at all - do you know if it is a good idea ?
If I only use one connection it will make the programming easier :)
Can you see any other problems with this approach ?
Hopefully this discussion is useful to you as well.
(it is very helpful to me :) )
Thanks again.
.
Re: [Code Example] Sending SMTP Emails using fsockopen()
Quote:
Originally Posted by
Davvit
Is that the mail server library on my server ?
Or do you mean on the server that my server is sending the emails to ?
If it is my server , then I could just find out what is supported and use that.
However - having said that, if its only a few lines of extra code to drop down to HELO
- then we may as well include them ( just in case ).
Its would be the mailserver software running on mail.mydomian.com and its setup. If your confident this wouldn't be changing then yea you can just test and see what works. I can't remember staight off what the return is if EHLO isn't allowed but should be documented on the internet. You'd also need to check the other commands are all accepted using HELO as well, seem to rememeber I needed to use EHLO to allow authentication or maybe a secure connection.
Quote:
Originally Posted by
Davvit
This timeout - does that mean after a specified number of seconds and no
response comes, we kill the connection ?
Or does it mean total time connection can be open for ?
The reason for the timeout is becuase each time you call fgets() it fetchs the next bytes upto a newline character. If no data has arrived yet the function waits for it, hence the timeout becuase you dont want it to wait forever.
That's not normally a problem for the commands that expect one line as the response because we call the function and want to wait for it to response.
Where it is an issue is after calling HELO or EHLO becuase the response to that command is to list all the accepted commands on a seperate line. So you need to loop through using fgets() until there are no more lines in the buffer. But if we want to end the loop when the buffer is empty you'd of called fgets() and it would timeout because it would be waiting for another line but the server isn't going to send anymore. This is how it identified the end of response in the first example, but now it also checks $info['unread_bytes'] which should let us know the buffer is empty before calling fgets() and needing to wait for a timeout. I also left the timeout check in the loop too so that if the connection died you wouldn't be stuck in a loop.
Might be an idea to add timeout checks on the others but I didn't do that becuase the only drawback was that it wouldn't find the success code and would fail anyway.
As far as multiple connections go I'd say you're better going with just one and see if there is a problem. If all the mail servers are virtual servers on the same box you wont find any benefit because I'd expect that would be more useful to load balnce across different machines, or the sending limit you mentioned.
Do all these messages need to go out at the same time or can you space them out?
If you can space them out thats probably better, especially for the policies of the receiving networks. You could use a similar approch to what I did and have a cronjob run every hour and in the script it will limit how many it sends by putting a limit on the SQL statement.
It would be very important that the script finishes before the next cronjob fires the script again or you'd run into a spiral getting worse and worse. This could either be done by calculating the amount of emails you want to send will take approx 30min so set the cronjob every hour to be safe. Or have some sort of flag stored somewhere that the script sets at the start and clears at the end. This way when the script starts it would check the flag and end itself if it was already set.
If you just get your script to try and send all the emails queued you'll definitley need to have someway to make sure its only running once at anytime. If you only do that through time it means the cronjob would have a very long gap. The drawback from that is that an email could be sitting in the queue for a long time if its added just after a batch goes out.
To your comment about writing to a file or DB you'll probably have to test that on the environment to see what is best but I'd expect the DB to be faster as they're optimised to read/write very fast. Obviously you'd already have an open connection so it would be a simple SQL statement.
On the other hand rather than using fwrite() each time you could build a string and do one fwrite() which would probably be faster. Be aware of the maximum length of a string though.
Re: [Code Example] Sending SMTP Emails using fsockopen()
Hi,
Thanks again for your detailed answer, especially the explanation about the timeout and loop of gets() for EHLO.
This also makes a lot of sense:
Quote:
As far as multiple connections go I'd say you're better going with just one and see if there is a problem. If all the mail servers are virtual servers on the same box you wont find any benefit because I'd expect that would be more useful to load balnce across different machines, or the sending limit you mentioned.
I am going to have a play with the code tomorrow and see if I can come up with something to send a batch of 100 emails out in one connection.
BTW - do you know how I should determine whether to use HELO or EHLO ?
I guess maybe I should try the advanced one EHLO and if it doesn't work use HELO
All the best.
Re: [Code Example] Sending SMTP Emails using fsockopen()
Yea you would send the EHLO command and if the response doesn't start with 250 you'll then need to use HELO.
Re: [Code Example] Sending SMTP Emails using fsockopen()
Hi PBertie
Thanks for your help a couple of months ago with this emailing script.
I implemented it by first having a cron job build a queue table
then a different cron job to check the queue and send out emails.
If the emails fail then they stay on the queue for 6 attempts.
At present it opens a new socket each time it sends an email
which must be slowing it down.
I would like to adjust my script so that it opens the socket and
then sends maybe 50 emails in one go, then closes the socket,
pauses some seconds, then opens a another sock and sends again.
BUT _ ıa m not sure how to adjust my script. :confused::o
This is what I have:
First my send-mail function:
PHP Code:
<?php
/*
* send_email_func.php
*
*
*/
function send_email_fnc($fromname, $reply_cd, $to, $toname, $subject, $message) {
//SMTP + SERVER DETAILS
/* * * * CONFIGURATION START * * * */
$smtpServer = "mail.example.net";
$port = "25";
$timeout = "2";
$username = "myuser";
$password = "my-password";
$from = "admin@example.net";
$reply_ad = $reply_cd."@example.net";
$identify = "localhost"; // Used at the start of the connection to identify yourself but value isn't checked
$newLine = "\r\n";
/* * * * CONFIGURATION END * * * * */
//Connect to the host on the specified port
$smtpConnect = @fsockopen($smtpServer, $port, $errno, $errstr, $timeout);
if(empty($smtpConnect)) {
$logArray['errno'] = $errno;
$logArray['errstr'] = $errstr;
$logArray['connection'] = "Failed to connect";
$logArray['SUCCESS'] = "FALSE";
return $logArray;
}
stream_set_timeout($smtpConnect, $timeout);
$smtpResponse = fgets($smtpConnect, 515);
$info = stream_get_meta_data($smtpConnect);
if ($info['timed_out']) {
$logArray['connection'] = "Timeout: $smtpResponse";
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
$logArray['connection'] = "Connected: $smtpResponse";
//--- Say hello to the server --------
fputs($smtpConnect,"EHLO ".$identify . $newLine);
$logArray['ehlo'] = fgets($smtpConnect, 515);
$info = stream_get_meta_data($smtpConnect);
$i=0;
while (!$info['timed_out']) {
$logArray['ehlo'] .= fgets($smtpConnect, 515);
$info = stream_get_meta_data($smtpConnect);
$i++;
}
if ($i=0) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Request Auth Login --------
fputs($smtpConnect,"AUTH LOGIN" . $newLine);
$logArray['authrequest'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['authrequest'],"334")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Send username --------
fputs($smtpConnect, base64_encode($username) . $newLine);
$logArray['authusername'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['authusername'],"334")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Send password --------
fputs($smtpConnect, base64_encode($password) . $newLine);
$logArray['authpassword'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['authpassword'],"235")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Email From --------
fputs($smtpConnect, "MAIL FROM: $from" . $newLine);
$logArray['mailfrom'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['mailfrom'],"250")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Email To --------
fputs($smtpConnect, "RCPT TO: $to" . $newLine);
$logArray['mailto'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['mailto'],"250")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- The Email --------
fputs($smtpConnect, "DATA" . $newLine);
$logArray['data1'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['data1'],"354")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Construct Headers --------
$headers = "MIME-Version: 1.0" . $newLine;
$headers .= "Content-type: text/html; charset=iso-8859-1" . $newLine;
$headers .= "To: ".$toname." <".$to.">". $newLine;
$headers .= "From: ".$fromname." <".$from.">" . $newLine;
$headers .= "Subject: ".$subject.$newLine;
$headers .= "Reply-To: ".$reply_ad.$newLine;
$message = str_replace("\n.", "\n..",$message);
fputs($smtpConnect, $headers.$newLine.$message."\n.\n");
$logArray['data2'] = fgets($smtpConnect, 515);
if (false === strpos($logArray['data2'],"250")) {
$logArray['SUCCESS'] = "FALSE";
fclose($smtpConnect);
return $logArray;
}
//--- Say Bye to SMTP --------
fputs($smtpConnect,"QUIT" . $newLine);
$logArray['quit'] = fgets($smtpConnect, 515);
//fclose($smtpConnect);
$logArray['SUCCESS'] = "TRUE";
return $logArray;
}
Now the calling script which also updates my tables and writes a log.
PHP Code:
<?php
/*
* send_queue.php
*
* Sending Queued Messages
*/
require_once("my_functions.php");
require_once("bbcode_email.php");
require_once("send_email_func.php");
function write_log($content) {
global $handle;
$content = $content."\r\n";
fwrite($handle, $content);
} // End of Function
function write_error($content) {
global $handle;
fwrite($handle, $content);
exit;
} // End of Function
$re1 = array("/\n/",
"/\s{2,}/",
"/>\s+</",
"/<br.*>/",
"/<[^>]+>/");
$re2 = array(" ",
" ",
" ",
" \n",
"");
function wrap_it($str,$max){
$str = explode(" ", $str );
$out = "";
$line = "";
foreach($str as $wrd){
if(strlen($line." ".$wrd) < $max){
$s = " ";
}
else {
$s = "<br>";
}
if( $s===" " ) {
$line = $line.$s.$wrd;
}
else {
$line = $wrd;
}
$out .= $s.$wrd;
} // end foreach
return $out;
} // end function
$log_file = "/home/exp526d/public_html/a_log_SEND.txt";
$handle = fopen("$log_file", "a");
//$today = $today-10800;
$stamp = date('H:i:s l, j F Y', $today);
write_log("----------------------------------------------- \r\nNew record - Time Stamp: $stamp ");
write_log("This script: send_queue.php, This file: $log_file");
echo "<br>This file: $log_file<br>";
$sql = "SELECT * FROM emailqueue WHERE sent = 'n' AND attempts < 5 ";
$result = mysql_query($sql) or write_error("Could not do emailqueue Query 1 ".mysql_error()." \r\n");
$num = mysql_num_rows($result);
if ($num == 0 ) {
write_log("No Valid Emails in Queue");
exit;
} // end if
while($row = mysql_fetch_assoc($result)){
extract($row);
$content = wordwrap($mess, 70, "\n");
$content = nl2br($content);
$content = str_replace("<br />", "<br>", $content);
$content = eml_bbcode($content,$contact);
$message_html = $content."<br><br>
<b>This is an opt-in only e-course.</b><br>
If you wish to cancel this at any time,<br>
please click here: <a href=\"http://www.expressresponse.net/unsubscribe.php?a=$sub_no\">E-course Cancellation.</a>
<br><br>
<br><br>
Published for $fm_name, By ExpressResponse.Net Waterfront Drive, Road Town. British Virgin Islands. <br>
© 2012 $fm_name All rights reserved.";
$message_text = "text";
$message_text = preg_replace($re1,$re2,$message_html);
$reply_cd = "sub_".$sub_no;
write_log("Processing Email: $queue_id to: $to_mail\n");
$logArray = send_email_fnc($fm_name, $reply_cd, $to_mail, $contact, $subject, $message_html);
if ( $logArray['SUCCESS'] === "TRUE" ) {
$sql = "UPDATE emailqueue SET
sent = 'y',
attempts = attempts+1,
sent_date = '$today_time'
WHERE queue_id = '$queue_id' ";
mysql_query($sql) or write_error("UPDATE emailqueue query problem1".mysql_error()." \r\n");
write_log("Successfully Sent: $queue_id to: $to_mail\n");
} // end if
else {
$sql = "UPDATE emailqueue SET
sent = 'n',
attempts = attempts+1
WHERE queue_id = '$queue_id' ";
mysql_query($sql) or write_error("Email Fail query problem".mysql_error()." \r\n");
$sql = "INSERT INTO `email_fail` (type,queue_no,client_no,camp_no,sub_no,mess_no,to,send_dt,connection,helo,authrequest,authusername,authpassword,mailfrom,mailto,data1,data2,quit,errno,errstr) VALUES ( '$send_type','$to','$today_time',$logArray[connection],$logArray[ehlo],$logArray[authrequest],$logArray[authusername],$logArray[authpassword],$logArray[mailfrom],$logArray[mailto],$logArray[data1],$logArray[data2],$logArray[quit],$logArray[errno],$logArray[errstr])";
mysql_query($sql) or write_error("Email Fail query problem2".mysql_error()." \r\n");
write_log("Sent Failed: $queue_id to: $to_mail\r\n");
} // end else
} // end while
fclose($handle);
?>
Can you see how I can change this so that I can sent out batches of 50 from
my queue ?
Once I an happy the script is running well, I will probably stop the logging as this no doubt slows it down a lot.
Do you think the table update should also be written as a batch of 50 as well ?
Might be a bit difficult to write that ? :confused:
Thanks for any help.
.
Re: [Code Example] Sending SMTP Emails using fsockopen()
Hi PBertie,
I have tried your code and it works up to the last step. I get the following result:
array ( 'connection' => 'Connected: 220 mi23 ESMTP service ready ', 'ehlo' => '250-mi23 250-8BITMIME 250-SIZE 20480000 250-AUTH=PLAIN LOGIN 250-AUTH PLAIN LOGIN 250 STARTTLS ', 'authrequest' => '334 VXNlcm5hbWU6 ', 'authusername' => '334 UGFzc3dvcmQ6 ', 'authpassword' => '235 Authentication successful. ', 'mailfrom' => '250 Sender address accepted ', 'mailto' => '250 Recipient address accepted ', 'data1' => '354 Continue ', 'data2' => false, 'SUCCESS' => 'FALSE', )
as you can see 'data2' is false, but, i don't get any errors or anything. do you know why this is happening or how I can check what's going on.
Thank you so much in advance! and thank you for posting the code.
Best!
Re: [Code Example] Sending SMTP Emails using fsockopen()
Quote:
Originally Posted by
aleksczajka@gmail.co
Hi PBertie,
I have tried your code and it works up to the last step. I get the following result:
array ( 'connection' => 'Connected: 220 mi23 ESMTP service ready ', 'ehlo' => '250-mi23 250-8BITMIME 250-SIZE 20480000 250-AUTH=PLAIN LOGIN 250-AUTH PLAIN LOGIN 250 STARTTLS ', 'authrequest' => '334 VXNlcm5hbWU6 ', 'authusername' => '334 UGFzc3dvcmQ6 ', 'authpassword' => '235 Authentication successful. ', 'mailfrom' => '250 Sender address accepted ', 'mailto' => '250 Recipient address accepted ', 'data1' => '354 Continue ', 'data2' => false, 'SUCCESS' => 'FALSE', )
as you can see 'data2' is false, but, i don't get any errors or anything. do you know why this is happening or how I can check what's going on.
Thank you so much in advance! and thank you for posting the code.
Best!
Sorry for a late reply but if anyone else is having trouble like this the problem seems to be with the message content. It could be lots of things so without seeing what the message is I couldn't say. I suggest trying it with a very simple message to rule that out.
It could be something specific to the mail server you are using too so you could find out what it is and google it's protocol and see if there's anything different its expecting to end the message. If so could you post it here so the code can be improved.
Thanks,