Using PHPMailer to send e-mail from a web site

Previously I presented a PHP script to use the host’s e-mail program to send e-mail. Since then, this has been rendered obsolete as e-mail providers are increasingly rejecting any e-mail that doesn’t come via an authenticated login (i.e. a login to an smtp server that requires encryption).

While this could be resolved by adding a PHP module for SMTP authentication, many hosting companies don’t allow you to add PHP modules.

Fortunately PHPMailer can be dropped into a web site directly – no need to install it. Simply download the package (from https://github.com/PHPMailer/PHPMailer), unzip it and copy the files to your web site.

Unfortunately PHPMailer uses a dramatically different model for e-mail than my previous code. As such, it’s not a plug-in replacement. It took me a couple days to come up with a working replacement that is missing the feature of splitting file uploads into manageable chunks.

I’ve also split the mailer code into two files. One file sets the login to the SMTP server. This is generally different for each web site, since many e-mail providers reject e-mail that doesn’t come “from” the same domain.

Here’s my mailer-credentials.php code:

<?php
// smtp server settings
$_mailDom = '<domain.tld>'; // only send mail to this domain
$_SMTPHost = 'mail.' . $_mailDom; //Set the SMTP server to send through
$_SMTPAuth = true; //Enable SMTP authentication
$_Username = 'noreply@' . $_mailDom; //SMTP username
$_Password = '<secure_password>; //SMTP password
$_Port = 465; //TCP port to connect to; use 587 if you have set 
              // $_SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS

Note that to send e-mail from a domain, you need a real e-mail address to send through. I’ve defaulted to using noreply@<domain.tld> for this since it suggests that the address is not monitored.

The code for the mailer.php program is as follows:

<?php
//Import PHPMailer classes into the global namespace
//These must be at the top of your script, not inside a function

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

require 'PHPMailer/src/Exception.php';
require 'PHPMailer/src/PHPMailer.php';
require 'PHPMailer/src/SMTP.php';

// REQUIRED to be set in calling success_page
// - _recipient - the name within the $_mailDom to send form data to
// - _subject - the email subject
// - realname - should be a form field for the real name of the _cc_sender
// - email - should be a form field for the sender's e-mail address

// OPTIONAL form fields
// - _cc_sender - if it exists, cc the sender on the e-mail
// - _datafile - the name of the file to append the form data to

// turn on error reporting
ini_set("display_errors",1);
ini_set("error_reporting",E_ALL);

// smtp server settings
require 'mailer-credentials.php';  // remove to another file so mailer can be shared among various sites
$_SMTPDebug = SMTP::DEBUG_OFF;              //turn off debugging for live script
// $_SMTPDebug = SMTP::DEBUG_SERVER;           //Enable verbose debug output
$_SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; //Enable implicit TLS encryption

// globals
$_upload_folder = './uploads/'; //<-- this folder must be writeable by the script
$_data_folder   = "./data/";    //<-- this folder must be writeable by the script
$_errors        = "";
$_next_page     = $_SERVER['HTTP_REFERER'];  //<-- can be overridden by _success_page or _fail_page

function reportError($message) {
    global $_errors;
    $_errors .= $message . "<br />";
}

function copyAndAddAttachment($from_path, $to_path, &$message) {
    global $_upload_folder;
    $to_file = $_upload_folder . $to_path;
    if (move_uploaded_file($from_path, $to_file)) {
        $message->addAttachment($to_file);
    } else {
        unlink($from_path);  // remove file that failed to copy - otherwise it clogs up the site
        reportError("error attaching " . $to_file . " to submission.");
    }
}

if (!isset($_POST['submit'])) {
    reportError("post submit not found");
} else {
//Create an instance; passing `true` enables exceptions
    $mail = new PHPMailer(true);

    Try {
// first we get all of the form information    
        $_name       = $_POST['realname'];
        $_from_email = $_POST['email'];
// allow people to send outside domain        
        if (strpos($_POST['_recipient'], '@') == false) {
            $_to     = $_POST['_recipient'] . '@' . $_mailDom;
        } else {
            $_to     = $_POST['_recipient'];
        }
        if (isset($_POST['_cc_sender'])) {
            $_cc_sender = $_POST['_cc_sender'];
        } else {
            $_cc_sender = "";
        }
        $_subject    = $_POST['_subject'];
        $_text       = $_name . " has sent you this information:\n";
        $_texttofile = "";
        $_echoText   = "";
        if (isset($_POST['_datafile'])) {
            $_saveto   = $_POST['_datafile'];
        }
        foreach ($_POST as $input_field=>$value) {
            if (substr($input_field, 0, 1) != "_") {    // skip preset values
                $_text .= $input_field . ": " . $value . "\n";
                $_echoText .= "<b>" . $input_field . ": </b>" . $value . "<br />";
                if (empty($_texttofile)) {
                    $_texttofile .= '"' . $value . '"';
                } else {
                    $_texttofile .= ',"' . $value . '"';
                }
            }
        }
//Server settings
        $mail->SMTPDebug = $_SMTPDebug; 
        $mail->isSMTP(); //Send using SMTP
        $mail->Host     = $_SMTPHost; 
        $mail->SMTPAuth = $_SMTPAuth; 
        $mail->Username = $_Username; 
        $mail->Password = $_Password;
        $mail->SMTPSecure = $_SMTPSecure;
        $mail->Port     = $_Port;
        $mail->setFrom($_Username);
        $mail->addAddress($_to); //Add a recipient
        $mail->addReplyTo($_from_email);
        if (!empty($_cc_sender)) {
            $mail->addCC($_from_email);
        }

// attachments may be optional. Only add one if a file is uploaded.
// note that you cannot check isset($_FILES) because that will be true even if no file is selected
   
        foreach ($_FILES as &$userfile) {
            if (is_array($userfile['name'])) {
                foreach ($userfile['name'] as $currentfile=>$name) {
                    if (!empty($name)) {
                        $tmp_path = $userfile["tmp_name"][$currentfile];
                        $_texttofile .= ',"' . $name .'"';
                        copyAndAddAttachment($tmp_path, $name, $mail);
                    }
                }
            } else {
                $name     = $userfile['name'];
                if (!empty($name)) {
                    $tmp_path = $userfile["tmp_name"];
                    $_texttofile .= ',"' . $name .'"';
                    copyAndAddAttachment($tmp_path, $name, $mail);
                }
            }
        }
// append form data to a file if requested
        if (!empty($_saveto)) {
            $_texttofile .= "\n";
            $fp = fopen($_data_folder . $_saveto, 'a');
            fwrite($fp, $_texttofile);
            fclose($fp);
        }

// add content and send message
        $mail->isHTML(false); //Set email format to plain text
        $mail->Subject = $_subject;
        $mail->Body = $_text;
        $mail->send();

// remove any uploaded files
        foreach ($_FILES as &$userfile) {
            if (is_array($userfile['name'])) {
                foreach ($userfile['name'] as $currentfile=>$name) {
                    if (!empty($name)) {
                        unlink($_upload_folder . $name);
                    }
                }
            } else {
                $name = $userfile['name'];
                if (!empty($name)) {
                    unlink($_upload_folder . $name);
                }
            }
        }
        echo "<h3>this information has been sent:</h3>";
        echo  $_echoText;
        if (isset($_POST['_success_page'])) {
            $_next_page = $_POST['_success_page'];
        }
    } catch (Exception $e) {
        echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
        if (isset($_POST['_fail_page'])) {
            $_next_page = $_POST['_fail_page'];
        }
    }
    echo '<p>click ' . '<a href="' . $_next_page . '"><b>here</b></a> to continue</p>';
}
?>

The first bit simply lets you use PHPMailer. The section

// smtp server settings
require 'mailer-credentials.php';  // remove to another file so mailer can be shared among various sites
$_SMTPDebug = SMTP::DEBUG_OFF;              //turn off debugging for live script
// $_SMTPDebug = SMTP::DEBUG_SERVER;           //Enable verbose debug output

imports the mail-credentials.php and sets up the debugging. It’s useful to turn debugging on during testing.

// globals
$_upload_folder = './uploads/';  //<-- this folder must be writeable by the script
$_data_folder   = "./data/";     //<-- this folder must be writeable by the script
$_errors        = "";
$_next_page     = $_SERVER['HTTP_REFERER'];  //<-- can be overridden by _success_page or _fail_page

The globals is largely the same as the previous scripts, except that I’ve added the ability to save the form fields to a .csv file.

The latter ability has been added to the main body of the program, I’ve also provided a “report” to the sender that shows what they sent. The former is collected in the string $_texttofile while the latter is in $_echotext. Note that $_echotext contains HTML formatting.

Finally, note that $_nextpage is initially set to be the referring (the form) page. However the form page can override that with _success_page and _fail_page. After reading the report, the user can click on a link to go to whichever page has been set as the $_next_page.

I was forced to do things this way to get around error messages because PHPMailer seems to be writing to a page itself. I can (and do) add to the page but I couldn’t just replace it. However this technique seems to be more practical, so I was actually simply forced to make the program better.

I’ve moved the code to remove the uploaded files to after the e-mail is sent because otherwise the files were being removed before they have actually been attached to the e-mail. It’s just another difference between the old mailer and PHPMailer.

Overall the new mailer provides some useful features. However the problem of file sizes still remains. In a future version I will either change the code to provide links to the files or split the message into multiple parts the way the previous version did. I’m still not sure which is the better approach.

About Gary Dale

Gary Dale is a long time social justice activist who has served in a number of roles. He is best known for founding and running FaxLeft in the 1990s, for running in Ontario and Canada elections, and for serving on the National Council of Fair Vote Canada. He has had a large number of letters to the editor published in a variety of media and on a wide range of topics.
This entry was posted in Computers, Internet, Science and Mathematics. Bookmark the permalink.

Leave a comment