Print

 

Sunday, February 27, 2021

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

}
?>

 


 

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

we are listening

pre-sales.ch said:

Thursday, July 28, 2021

Hello Thanks 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, 2021

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, 2021

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