Sunday, February 27, 2011

Simple SMTP Class for PHP

Easily send email via PHP with advanced features

PHP has a very simple mail function which is used very often for very basic text email messages. However, the standard mail function has only limited standard capabilities.  When it  comes to sending out more complicated emails, or using a more secure server to sent emails, we often fall back to PHPMailer. I use PHPMailer too. But for some requirements it is just too much power and too big. Because my needs fall somewhere in the two, I have created my own KM _Mailer for PHP.

There are a lot of reasons to use your own SMTP transport for sending e-mail messages from a web application. Some of them are:

  • Many shared hosting providers doesn’t allow to use the PHP mail() function for security reasons
  • Your web application is more flexible if you use SMTP
  • Your e-mail function is not limited to the servers port or e-mail configuration anymore
  • SMTP is much more powerful and secure (using SSL or TLS)

Just because I like to do this kind of stuff, I created my own simple, lightweight SMTP class to send email messages. It features:

  • Relaying message delivery to a pre-defined SMTP server.
  • Supports AUTH login using TLS or SSL
  • Send messages a plain text, html or a combination of both
  • Use extended email addresses (ie: MyName <myname@mydomain.com>)
  • Validate that server is connected and logged in
  • Validate that email message has been sent
  • Automatically generates plain text message when sending multipart messages
  • Specify additional headers if needed
  • Send multiple messages without losing the server connection
  • Enable debugging to monitor server response

New version 1.5 offers the ability to include multiple recipients, CC and BCC, and attachments.

Using the class is very simple. See examples below.

<?php

include('km_smtp_class.php');

/* $mail = new KM_Mailer(server, port, username, password, secure); */
/* secure can be: null, tls or ssl */
$mail = new KM_Mailer("smtp.gmail.com", "587", "username@gmail.com", "password", "tls");

if($mail->isLogin) {
  /* for localhost server no login is required: */
  /* $mail = new KM_Mailer('localhost', '25'); */

  /* $mail->send(from, to, subject, body, headers = optional) */
  $mail->send('username@mydomain.com', 'recipent@somedomain.com', 'test email 1', 'This is a <b>multipart email</b>!');

  /* more emails can be sent on the same connection: */
  $mail->send('UserName <username@mydomain.com>', 'Recipient <recipent@somedomain.com>', 'test email 2', 'This is a <b>multipart email</b>!');

  /* add more recipients */
  $mail->addRecipient('New Recipient <newrecipient@somedomain.com>');

  /* add CC recipient */
  $mail->addCC('CC Recipient <ccrecipient@somedomain.com>');

  /* add BCC recipient */
  $mail->addBCC('CC Recipient <bccrecipient@somedomain.com>');

  /* add attachment */
  $mail->addAttachment('pathToFileToAttach');

  /* send multipart email with different plain text part */
  $mail->altBody = "This is an alternate body for multiipart emails.";
  $mail->send('UserName <username@mydomain.com>', 'Recipient <recipent@somedomain.com>', 'test email 3', 'This is a multipart email with a <b>different plain text part</b>!');

  /* send just a plain text email and test if it was sent successfully */
  $mail->contentType = "text/plain";
  if(!$mail->send('username@mydomain.com', 'recipent@somedomain.com', 'test email 4', 'This is a plain text email.')) {
    echo "Failed to send email";
  } else {
    echo "Email sent successfully";
  }

  /* clear recipients and attachments */
  $mail->clearRecipients();
  $mail->clearCC();
  $mail->clearBCC();
  $mail->clearAttachments();
} else {
  echo "Login failed <br>";
}
?>

 For those of you that are interested, the entire class script is provided below. 

Note: if you are planning to use TLS/SSL then be sure to enable the extension called php_openssl in php.ini.

<?php
include("strip_html_tags.php");  /* this script provided by David R. Nadeau, NadeauSoftware.com */

class KM_Mailer {
  public $server;
  public $port;
  public $username;
  public $password;
  public $secure;    /* can be tls, ssl, or none */

  public $charset = ""iso-8859
-1""; /* included double quotes on purpose */
  public $contentType = "multipart/mixed";  /* can be set to: text/plain, text/html, multipart/mixed */
  public $transferEncodeing = "quoted-printable"; /* or 8-bit  */
  public $altBody = "";
  public $isLogin = false;
  public $recipients = array();
  public $cc = array();
  public $bcc = array();
  public $attachments = array();

  private $conn;
  private $newline = "rn";
  private $localhost = 'localhost';
  private $timeout = '60';
  private $debug = false;

  public function __construct($server, $port, $username=null, $password=null, $secure=null) {
    $this->server = $server;
    $this->port = $port;
    $this->username = $username;
    $this->password = $password;
    $this->secure = $secure;

    if(!$this->connect()) return;
    if(!$this->auth()) return;
    $this->isLogin = true;
    return;
  }

  /* Connect to the server */
  private function connect() {
    if(strtolower(trim($this->secure)) == 'ssl') {
      $this->server = 'ssl://' . $this->server;
    }
    $this->conn = fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);
    if (substr($this->getServerResponse(),0,3)!='220') { return false; }
    return true;
  }

  /* sign in / authenicate */
  private function auth() {
    fputs($this->conn, 'HELO ' . $this->localhost . $this->newline);
    $this->getServerResponse();
    if(strtolower(trim($this->secure)) == 'tls') {
      fputs($this->conn, 'STARTTLS' . $this->newline);
      if (substr($this->getServerResponse(),0,3)!='220') { return false; }
      stream_socket_enable_crypto($this->conn, true,STREAM_CRYPTO_METHOD_TLS_CLIENT);
      fputs($this->conn, 'HELO ' . $this->localhost . $this->newline);
      if (substr($this->getServerResponse(),0,3)!='250') { return false; }
    }
    if($this->server != 'localhost') {
      fputs($this->conn, 'AUTH LOGIN' . $this->newline);
      if (substr($this->getServerResponse(),0,3)!='334') { return false; }
      fputs($this->conn, base64_encode($this->username) . $this->newline);
      if (substr($this->getServerResponse(),0,3)!='334') { return false; }
      fputs($this->conn, base64_encode($this->password) . $this->newline);
      if (substr($this->getServerResponse(),0,3)!='235') { return false; }
    }
    return true;
  }

  /* send the email message */
  public function send($from, $to, $subject, $message, $headers=null) {
    /* set up the headers and message body with attachments if necessary */
    $email  = "Date: " . date("D, j M Y G:i:s") . " -0500" . $this->newline;
    $email .= "From: $from" . $this->newline;
    $email .= "Reply-To: $from" . $this->newline;
    $email .= $this->setRecipients($to);

    if ($headers != null) { $email .= $headers . $this->newline; }

    $email .= "Subject: $subject" . $this->newline;
    $email .= "MIME-Version: 1.0" . $this->newline;
    if($this->contentType == "multipart/mixed") {
      $boundary = $this->generateBoundary();
      $message = $this->multipartMessage($message,$boundary);
      $email .= "Content-Type: $this->contentType;" . $this->newline;
      $email .= "    boundary="$boundary"";
    } else {
      $email .= "Content-Type: $this->contentType; charset=$this->charset";
    }
    $email .= $this->newline . $this->newline . $message . $this->newline;
    $email .= "." . $this->newline;

    /* set up the server commands and send */
    fputs($this->conn, 'MAIL FROM: <'. $this->getMailAddr($from) .'>'. $this->newline);
    $this->getServerResponse();

    if(!$to=='') {
      fputs($this->conn, 'RCPT TO: <'. $this->getMailAddr($to) .'>' . $this->newline);
      $this->getServerResponse();
    }
    $this->sendRecipients($this->recipients);
    $this->sendRecipients($this->cc);
    $this->sendRecipients($this->bcc);

    fputs($this->conn, 'DATA'. $this->newline);
    $this->getServerResponse();
    fputs($this->conn, $email);  /* transmit the entire email here */
    if (substr($this->getServerResponse(),0,3)!='250') { return false; }
    return true;
  }

  private function setRecipients($to) { /* assumes there is at least one recipient */
    $r = 'To: ';
    if(!($to=='')) { $r .= $to . ','; }
    if(count($this->recipients)>0) {
      for($i=0;$i<count($this->recipients);$i++) {
        $r .= $this->recipients[$i] . ',';
      }
    }
    $r = substr($r,0,-1) . $this->newline;  /* strip last comma */;
    if(count($this->cc)>0) { /* now add in any CCs */
      $r .= 'CC: ';
      for($i=0;$i<count($this->cc);$i++) {
        $r .= $this->cc[$i] . ',';
      }
      $r = substr($r,0,-1) . $this->newline;  /* strip last comma */
    }
    return $r;
  }

  private function sendRecipients($r) {
    if(empty($r)) { return; }
    for($i=0;$i<count($r);$i++) {
      fputs($this->conn, 'RCPT TO: <'. $this->getMailAddr($r[$i]) .'>'. $this->newline);
      $this->getServerResponse();
    }
  }

  public function addRecipient($recipient) {
    $this->recipients[] = $recipient;
  }

  public function clearRecipients() {
    unset($this->recipients);
    $this->recipients = array();
  }

  public function addCC($c) {
    $this->cc[] = $c;
  }

  public function clearCC() {
    unset($this->cc);
    $this->cc = array();
  }

  public function addBCC($bc) {
    $this->bcc[] = $bc;
  }

  public function clearBCC() {
    unset($this->bcc);
    $this->bcc = array();
  }

  public function addAttachment($filePath) {
    $this->attachments[] = $filePath;
  }

  public function clearAttachments() {
    unset($this->attachments);
    $this->attachments = array();
  }

  /* Quit and disconnect */
  function __destruct() {
    fputs($this->conn, 'QUIT' . $this->newline);
    $this->getServerResponse();
    fclose($this->conn);
  }

  /* private functions used internally */
  private function getServerResponse() {
    $data="";
    while($str = fgets($this->conn,4096)) {
      $data .= $str;
      if(substr($str,3,1) == " ") { break; }
    }
    if($this->debug) echo $data . "<br>";
    return $data;
  }

  private function getMailAddr($emailaddr) {
     $addr = $emailaddr;
     $strSpace = strrpos($emailaddr,' ');
     if($strSpace > 0) {
       $addr= substr($emailaddr,$strSpace+1);
       $addr = str_replace("<","",$addr);
       $addr = str_replace(">","",$addr);
     }
     return $addr;
  }

  private function randID($len) {
    $index = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $out = "";
    for ($t=0; $t<$len;$t++) {
      $r = rand(0,61);
      $out = $out . substr($index,$r,1);
    }
    return $out;
  }

  private function generateBoundary() {
    $boundary = "--=_NextPart_000_";
    $boundary .= $this->randID(4) . "_";
    $boundary .= $this->randID(8) . ".";
    $boundary .= $this->randID(8);
    return $boundary;
  }

  private function multipartMessage($htmlpart,$boundary) {
    if($this->altBody == "") { $this->altBody = strip_html_tags($htmlpart); }
    $altBoundary = $this->generateBoundary();
    ob_start();//Turn on output buffering
    $parts  = "This is a multi-part message in MIME format." . $this->newline . $this->newline;
    $parts .= "--" . $boundary . $this->newline;

    $parts .= "Content-Type: multipart/alternative;" . $this->newline;
    $parts .= "    boundary="$altBoundary"" . $this->newline . $this->newline;

    $parts .= "--" . $altBoundary . $this->newline;
    $parts .= "Content-Type: text/plain; charset=$this->charset" . $this->newline;
    $parts .= "Content-Transfer-Encoding: $this->transferEncodeing" . $this->newline . $this->newline;
    $parts .= $this->altBody . $this->newline . $this->newline;

    $parts .= "--" . $altBoundary . $this->newline;
    $parts .= "Content-Type: text/html; charset=$this->charset" . $this->newline;
    $parts .= "Content-Transfer-Encoding: $this->transferEncodeing" . $this->newline . $this->newline;
    $parts .= $htmlpart . $this->newline . $this->newline;

    $parts .= "--" . $altBoundary . "--" . $this->newline . $this->newline;

    if(count($this->attachments) > 0) {
      for($i=0;$i<count($this->attachments);$i++) {
        $attachment = chunk_split(base64_encode(file_get_contents($this->attachments[$i])));
        $filename = basename($this->attachments[$i]);
        $ext = pathinfo($filename,PATHINFO_EXTENSION);
        $parts .= "--" . $boundary . $this->newline;
        $parts .= "Content-Type: application/$ext; name="$filename"" . $this->newline;
        $parts .= "Content-Transfer-Encoding: base64" . $this->newline;
        $parts .= "Content-Disposition: attachment; filename="$filename"" . $this->newline . $this->newline;
        $parts .=  $attachment . $this->newline;
      }
    }

    $parts .= "--" . $boundary . "--";

    $message = ob_get_clean();//Turn off output buffering
    return $parts;
  }

}
?>

 

Download SMTP Script

delicious digg facebook stumble twitter myspace linkedin technorati reddit google springpad blogger | addthis Share More...

we are listening

Renato BR said:

Thursday, April 18, 2013

very useful script!

kidmoses said:

Thursday, April 18, 2013

Sounds like you do not have permission to send emails from the account you are trying to access. May be related to credentials or authentication? A Google search on the error may give you some clues. Some people have reported this error with an exchange server. I haven't tested the script on an exchange server so can't answer to that.

Pedro R said:

Thursday, April 18, 2013

Hi. I have a problem. The form has some fields and one input type file to be attached to the email. I set the debug to true and I got this message " 550 5.7.1 Client does not have permissions to send as this senderFailed to send email".What does this means about the permissions as this sender.? How can I solve this ??Thanks

pedro said:

Friday, April 5, 2013

Hi. Is that any way to set the "From" user email address to be the same as the "Answer to" email address ?? Because when I recieve the email the value that in From it is what I defined in $mail = new KM_Mailer(...). Thanks

ciprianmp said:

Thursday, January 31, 2013

Hi kidmoses. Nice class.Before I try it out deeper, I want to know if it's open-source/GPL or can be used in such an app.Also, can "utf-8" encoding be used?Which php version is this compatible with? I need both 4 and 5 (if not also 6)...Thank you.PS: can we subscribe for a new version release notification, so we can keep it up-to-date?

kidmoses said:

Tuesday, July 3, 2012

To track errors, set debug to "true". Progress will be displayed on screen. If you want to log the progress in a file, edit the getServerResponse() function.

anonymous said:

Tuesday, July 3, 2012

hi, how can i catch the errors during the progress.

kidmoses said:

Monday, June 18, 2012

If the Content-Transfer-Encoding is set to "quoted-printable" this might happen if the "=" symbol ends up at the end of a line. Try using "8-bit" encoding instead.

Diogo said:

Monday, June 18, 2012

Hello kidmoses, for some reason it is stripping "=" symbol from my body. Example: i have a link www.site.com/?page=1 when I send the email it turns into: www.site.com/?page1Any idea?

pre-sales.ch said:

Thursday, July 28, 2011

HelloThanks for this. Could the Return-Path be modified..., how?Not the "Reply-to" but the "Return-path" to be able to manage bounces. Thanks.

kidmoses said:

Thursday, March 10, 2011

By default the script will process messages as HTML. Just pass the HTML body in the body part of the send() function. Sending images has not be implemented.

pib said:

Sunday, March 6, 2011

HelloYour script seems to be very interesting.How do you send in HTML format and with inline images?Thank you and congratulations!Pib

speak your mind

If you have something to say, we would love to hear from you.

Name:
Comments:
 
Speak
Disabled due to spam

 

categories

scripts

all
general
inbox marketing
web solutions
software
travel


Popular

newsletter

Get occasional email updates from kidmoses

donate

Donations of any size to this website are greatly appreciated.

Donate
copyright © 2004 to the present day | web design by KidMoses
privacy | terms
windsor home inspections