Integrating the SparkPost API With CakePHP 3.4

After setting up a contact form for a project recently, I decided to step up performance by integrating the latest version of the SparkPost API (V2).

By using the API, the project will handle mail a lot more efficiently. Plus, the API opens up a world of new possibilities for the project’s email communications as a whole.

SparkPost is a highly respected provider of transactional email services, and has a well-documented developer-friendly API. They also have a very generous ‘free’ plan that lets you send up to 100,000 emails per month – great for startups and small businesses that don’t do a lot of transactional emailing.

In this tutorial, I will build on work done earlier which covers how to implement Modelless Forms With Ajax & JSON in CakePHP 3.4. In the earlier tutorial, the project sent mail either via SMTP relay or the PHP mail() function, or to a debug log.


Table of Contents

  1. Starting Assumptions
  2. Get a SparkPost API Key
  3. Update the Project Config File
  4. Install the SparkPost PHP Library
  5. Create a Custom Transport
  6. Update the Mailer
  7. Implement and Test
  8. Conclusion

 


1: Starting Assumptions

Before starting, be sure you have at least read through and understand the previous tutorial. Although implementing the SparkPost API doesn’t depend on the work done there, this tutorial will build on that work extensively.


2: Get a SparkPost API Key

The very first thing is to set up a SparkPost account, validate a sending domain for the account, and generate a private API key.

SparkPost does a good job of explaining how this goes, so I won’t repeat the instructions here.


3: Update the Project Config File

Open /src/config/app.php for your project and add the following code near the bottom, just before the closing brace:

/**
 * SparkPost API Key
 */
'SparkPost' => ['Api' => ['key' => '<your key here.>']]

Make sure to insert your own key as indicated.

While here, scroll up to the Email Profiles section and optionally add a new profile. This generally isn’t necessary but there are use cases where a different profile is required. Here’s an example – just be sure to insert your own from and to addresses:

'Email' => [
    'default' => [
        ...
    ],
    'admin' => [
        ...
    ],
    'user' => [
        ...
    ],
    'contact' => [
        'from' => ['admin@mydomain.com'=>'My Contact Form'],
        'to' => 'myemailaddress@anydomain.com',
    ],
],

Whether or not you create a new profile, make sure to have at least one profile set up that includes a ‘from’ address for the domain that was validated earlier with SparkPost. Make sure also that a ‘to’ address is specified here.

Note the name of the profile you plan to use – this needs to be inserted into the Mailer shortly to provide valid from and to addresses.


4: Install the SparkPost PHP Library

Next, download and install the SparkPost PHP library that can be found at https://github.com/SparkPost/php-sparkpost. Instructions are provided there for installing this library to your project using Composer.

Make sure the appropriate dependencies are installed as well, particularly Guzzle.


5: Create a Custom Transport

At the command line, navigate to /src/Mailer. Enter the following code to create a directory to contain the new Custom Transport, then go into this new directory and create an empty Transport file called SparkPostTransport.php:

mkdir Transport
cd Transport
touch SparkPostTransport.php

Make sure permissions and ownership are set correctly for both the directory and the file.

Now open /src/Mailer/Tansport/SparkPostTransport.php, add the following code, and save:

<?php
/*
 * This transport derives from an earlier project created by Syntax Era 
 * Development Studio. (https://github.com/syntaxera/cakephp-sparkpost-plugin)
 */
namespace App\Mailer\Transport;

use Cake\Core\Configure;
use Cake\Mailer\AbstractTransport;
use Cake\Mailer\Email;
use Cake\Network\Exception\BadRequestException;
use SparkPost\APIResponseException;
use SparkPost\SparkPost;
use GuzzleHttp\Client;
use Http\Adapter\Guzzle6\Client as GuzzleAdapter;

/**
 * Spark Post Transport Class
 *
 * Provides an interface between the CakePHP Email functionality and the SparkPost API v.2.
 *
 * @package SparkPost\Mailer\Transport
 */
class SparkPostTransport extends AbstractTransport
{
    /**
     * Send mail via SparkPost REST API
     *
     * @param \Cake\Mailer\Email $email Email message
     * @return array
     */
    public function send(Email $email)
    {
        // instantiate a client adapter
        $key = Configure::read('SparkPost.Api.key');
        $httpClient = new GuzzleAdapter(new Client());

        // instantiate a SparkPost transmission entity
        $sparky = new SparkPost($httpClient, ['key'=>$key]);
        $sparky->setOptions(['async' => false]);

        // Convert the email object's data into the API's required format
        $from = (array) $email->from(); // needs to be a sending domain that is cleared with Sparkpost
        $sender = sprintf('%s <%s>', mb_encode_mimeheader(array_values($from)[0]), array_keys($from)[0]);
        $to = (array) $email->to(); // the email's delivery target(s)
        foreach ($to as $toEmail => $toName) 
        {
            $recipients[] = ['address' => [ 'name' => mb_encode_mimeheader($toName), 'email' => $toEmail]];
        }
        $replyTo = (array) $email->replyTo(); // the person who actually sent the message in the first place
        $reply = sprintf('%s <%s>', mb_encode_mimeheader(array_values($replyTo)[0]), array_keys($replyTo)[0]);

        // Assemble the transmission
        $message = [
            'content'=> [
                'from'=> $sender,
                'reply_to'=>$reply,
                'subject' => mb_decode_mimeheader($email->subject()),
                'html'=>empty($email->message('html')) ? $email->message('text') : $email->message('html'),
                'text'=>$email->message('text')
              ],
            'recipients'=> $recipients
            ];

        // Send message
        try 
        {
            $sparky->transmissions->post($message);
        } 
        catch(APIResponseException $e) 
        {
            throw new BadRequestException(sprintf('SparkPost API error %d (%d): %s (%s)',
                $e->getAPICode(), $e->getCode(), ucfirst($e->getAPIMessage()), $e->getAPIDescription()));
        }
    }
}

Most of the above is self-evident, otherwise the CakePHP book and API documentation will be helpful.


6: Update the Mailer

A Mailer class was created at /src/Mailer/ContactUsMailer.php as part of the work in the previous tutorial. Open this file and modify the code near the top, just below the ‘namespace’ statement, to look like this:

<?php
namespace App\Mailer;

use Cake\Core\Configure; // ADDED
use Cake\Mailer\Email; // ADDED
use Cake\Mailer\Mailer;
use Cake\Mailer\AbstractTransport; //ADDED
use App\Mailer\Transport; //ADDED

/**
 * ContactUs mailer.
 */
...

Now scroll down into the submission function, and modify the code to look like this:

// configure the email transport
Email::setConfigTransport('sparkpost', [
    'className'=>'SparkPost',
    'apiKey'=>Configure::read('SparkPost.Api.key')
]); // ADDED
// configure the email
$this
    ->setTransport('sparkpost') // ADDED
    ->setProfile('<YOUR EMAIL PROFILE NAME HERE>') // ADDED
    ->setReplyTo($data['email'], $data['name']) 
    ->setSubject($data['name'].' just sent us a contact message')
    ->set(['content' => (array_key_exists('phone',$data) && $data['phone'] != "") ? '<p>Phone#: '.$data['phone'].'</p>'.$data['message'] : $data['message']])
    ->setTemplate('default') // .../src/Template/Email/[html|text]/default.ctp
    ->setLayout('default') // .../src/Template/Layout/Email/[html|text]/default.ctp
    ->setEmailFormat('both');

Make sure to insert the name of the email profile to be used. This is necessary to pick up the from and to addresses.

Now save the file.


7: Implement and Test

At this point, the coding is done and you should be ready to go. All that remains is to upload and test what you’ve done.

Again, I purposely avoided using a specific testing regime in this tutorial. This is because just about everyone has their own approach and toolset for testing. Simply adjust the instructions provided to suit your preferred methodology.


Conclusion

If you’ve been following both tutorials, you can now send your project’s contact form messages via the SparkPost API.

More importantly, whether or not you built out the contact form, you can now use this new custom email transport with all of your project’s email communications.

Modelless Forms With Ajax & JSON in CakePHP 3.4

Earlier this week, I needed to implement a simple contact form on a project’s landing page. Since I didn’t want the form to interact with a database, this was the perfect opportunity for what CakePHP calls a ‘modelless form’.

This tutorial describes how to implement a modelless form in CakePHP 3.4 using Ajax and JSON to communicate between the client and server.

(In a follow-up to this tutorial, I take the project one step further by Integrating the SparkPost API.)


Table of Contents

  1. Starting Assumptions
  2. Bake a Contact Form
  3. Bake a Mailer Class for the Form
  4. Bake a Form Controller With No Actions
  5. Create the Contact Form View
  6. Create the Ajax Script
  7. Implement and Test
  8. Conclusion

1: Starting Assumptions

Before starting, be sure the following is true:

  • CakePHP 3.4 is installed for your project, and you are familiar with how it works
  • A ‘typical’ email transfer has been implemented and tested (i.e. SMTP, Debug, etc)
  • jQuery is installed for your project
  • You are comfortable working with PHP, Javascript, jQuery, and HTML
  • You are familiar with command-line basics *

* Command-line instructions are shown for Linux Ubuntu 16.04. You may need to make adjustments if you are using a different OS, or if you prefer to use an IDE or GUI-based text-editor (i.e SublimeText).


2: Bake a Contact Form

At the command line, go to the site’s root directory and enter the following to bake a new form:

bin/Cake bake Form Contact

By default, CakePHP saves the new form as src/Form/ContactForm.php. Open the file, and you should see something like this:

<?php
namespace App\Form;

use Cake\Form\Form;
use Cake\Form\Schema;
use Cake\Validation\Validator;

class ContactForm extends Form
{
    protected function _buildSchema(Schema $schema)
    {
        return $schema;
    }

    protected function _buildValidator(Validator $validator)
    {
        return $validator;
    }

    protected function _execute(array $data)
    {
        return true;
    }
}

This file includes three magic function signatures to use as a starting point for coding. CakePHP will run these functions automatically when creating, validating, and executing the form.

Let’s start adding some code to this class. First, add the following two namespaces at the top of the file:

use Cake\Log\LogTrait;
use Cake\Mailer\MailerAwareTrait;

We’ll be using both of these shortly.

Now replace the return statement in the _buildSchema function with the following:

return $schema->addField('name', 'string')
            ->addField('email', ['type' => 'email'])
            ->addField('phone', ['type' => 'tel'])
            ->addField('message', ['type' => 'text']);

This bit of code tells CakePHP’s FormHelper about the inputs the form needs to include.

Next, add the following validators to the _buildValidator function, just above the return statement:

// 'name' field
$validator
    ->requirePresence('name',[
        'message' => __('Please provide your name'),
        ])
    ->notBlank('name',__('Name cannot be blank'))
    ->add('name', [
        'minlength' => [
            'rule' => ['minLength', 2],
            'message' => __('Name must be at least 2 characters long')
            ],
        'maxlength' => [
            'rule' => ['maxLength', 50],
            'message' => __('Name cannot be more than 50 characters long')
            ]
        ]);
// 'email' field
$validator
    ->requirePresence('email',[
        'message' => __('Please provide your email address'),
        ])
    ->notBlank('email',__('Email address cannot be blank'))
    ->add('email', 'format', [
        'rule' => 'email',
        'message' => __('Please provide a valid email address')
        ]);
// 'phone' field
$validator
    ->allowEmpty('phone')
    ->add('phone', [
        'minlength' => [
            'rule' => ['minLength', 7],
            'message' => __('Your phone# must be at least 7 characters long'),
            'on' => function ($context) {return !empty($context['data']['phone']);} // conditional on presence
            ],
        'maxlength' => [
            'rule' => ['maxLength', 30],
            'message' => __('Your phone# cannot be more than 30 characters long')
            ]
        ]);
// 'message' field
$validator
    ->requirePresence('message',[
        'message' => __('Please include your message'),
        ])
    ->notBlank('name',__('Your message cannot be blank'))
    ->add('message', [
        'minlength' => [
            'rule' => ['minLength', 4],
            'message' => __('Your message must be at least 4 characters long')
            ],
        'maxlength' => [
            'rule' => ['maxLength', 2048],
            'message' => __('Your message cannot be more than 2048 characters long')
            ]
        ]);

Basically, this bit of code takes care of the validations that would normally be found in a Model class. These validators help CakePHP’s FormHelper set up the form properly, and are used to validate whatever data the form sends when submitted.

Last, add the following statements in the _execute function above the return statement:

$this->getMailer('ContactUs')->send('submission',[$data]);

$this->log('Someone just sent us a contact message', 'info');

The first statement uses the Mailer to send the form, the second takes care of logging the event.


3: Bake a Mailer Class For the Form

At the command line, go to the site’s root directory and enter the following to bake a new Mailer class dedicated to the form:

bin/Cake bake mailer ContactUs

By default, CakePHP saves the new Mailer as src/Mailer/ContactUsMailer.php. Open the file, and you should see something like this:

<?php
namespace App\Mailer;

use Cake\Mailer\Mailer;

class ContactUsMailer extends Mailer
{
    static public $name = 'ContactUs';
}

Now add the following public function to the class:

public function submission(array $data)
{
    $this
        ->setReplyTo($data['email'], $data['name'])
        ->setSubject($data['name'].' just sent us a message')
        ->set(['content' => (array_key_exists('phone',$data) && $data['phone'] != "") ? '<p>Phone#: '.$data['phone'].'</p>'.$data['message'] : $data['message']])
        ->setTemplate('default')
        ->setLayout('default')
        ->setEmailFormat('both');
}

This new Mailer class sets up the email submission defaults needed to post the message. The mailer doesn’t actually send the email – that happens in the controller.


4: Bake a Form Controller With No Actions

At the command line, go to the site’s root directory and enter the following to bake a new controller without any actions:

bin/Cake bake controller Contacts --no-actions

By default, CakePHP saves the new form as src/Controller/ContactsController.php. Open the file, and you should see something like this:

<?php
namespace App\Controller;

use App\Controller\AppController;

class ContactsController extends AppController
{
}

Once again, this file serves as a starting point for coding. First, add the following namespaces at the top of the file:

use App\Form\ContactForm;
use Cake\Validation\Validator;

Here, we take a step back from the suggested approach in the CakePHP documentation to follow a slightly different path. Instead of creating an index() function, create a function called contactUs() by adding the following public function inside the ContactsController class:

public function contactUs()
{
    $this->autoRender = 'false';
    $this->viewBuilder()->layout('ajax'); // src/Template/Layout/ajax.ctp
    
    // Disallow direct access via browser URL
    $this->request->allowMethod('ajax');

    $mailsent = [
        'status'=>'error'
        ,'message'=>__('Your message could not be sent')
        ,'alert'=>'danger'];

    $form = new ContactForm();

    if ($this->request->is('post')) 
    {
        if ($form->execute($this->request->data)) 
        {
            $mailsent = [
                'status'=>'sent'
                ,'message'=>__('Your message has been sent')
                ,'alert'=>'success'];
        }
        else 
        {
            $errors = $form->errors();

            if (count($errors) > 0) 
            {
                $mailsent['message'] .= ' &mdash; ' . __('Please correct any errors and try again.');

                foreach ($errors as $key => $value) 
                {
                    $mailsent['errors'][$key] = $value;
                }
            }
        }
    } // this is not a post request (error condition)

    $mailsent = json_encode($mailsent, JSON_FORCE_OBJECT);
    $this->set(compact('mailsent'));
}

This function will only be callable via Ajax, and contains the code needed to process the form. It returns a status code, a message, and potentially an array of error objects, all in a JSON format.


5: Create the Contact Form View

At the command line, go to the site’s src/Template directory and enter the following to create a new folder for the view:

mkdir Contacts

Once again, we depart from the suggested approach here. The documentation calls for creating a src/Templates/Contacts/index.ctp page to contain the form. Instead, go into the src/Template/Contacts folder and enter the following to create a new view file that CakePHP can match up with the contactUs() controller function:

touch contact_us.ctp

Open the file, and add the following:

<?= $mailsent ?>

The controller will use this file to pass Ajax responses back to the caller.

Also, before moving on, make sure ownership and permissions are set correctly on both the folder and the file.

Next, embed the form into whatever view file you want. In my case, I placed the form on a website landing page. Here’s the code to generate the form:

<?= $this->Form->create($form,['url'=>['controller'=>'Contacts','action'=>'contactUs'],'id'=>'contactForm','class'=>'async','role'=>'form','name'=>'sentMessage']); ?>
    <?= $this->Form->control('name',['id'=>'name','placeholder'=>'Your Name (required)']); ?>
    <?= $this->Form->control('email',['id'=>'email','placeholder'=>'Your Email (required)']); ?>
    <?= $this->Form->control('phone',['id'=>'phone','placeholder'=>'Your Phone # (optional)']); ?>
    <?= $this->Form->control('message',['id'=>'message','placeholder'=>'Your Message (required)']); ?>
    <div id="responseMessage"></div>
    <?= $this->Form->button('Send Message', ['type' => 'submit','class'=>'btn btn-xl submit']);?>
<?= $this->Form->end() ?>

This code will tell CakePHP everything it needs to know in order to generate an Ajax-ready form inside the view.


6: Create the Ajax Script

Next step is to create the Ajax script that will handle the to-and-fro between client and server. At the command line, go the folder in the site’s webroot where the .js files are stored and enter the following to create the file:

touch ajax.js

Again, make sure ownership and permissions are set correctly, then include this file in your site’s layout template:

<?= $this->Html->script('/js/ajax.js') ?>

Adjust this as needed to target the correct folder for your project. Also, ajax.ctp will use jQuery, so make sure this is included as well.

Now open ajax.js and add the following script:

$(function() {
    "use strict";

    var contentTarget = $("div#responseMessage");

    // handle form submissions
    $(".async").submit(function(e)
    {
        // prevent the form from submitting 
        e.preventDefault();

        // set a convenience variable for the form
        var form = $(this);

        // disable the submit button to prevent repeated clicks
        form.find('.submit').prop('disabled', true);

        // create and post a temporary spinner
        var spinner = '<div class="alert alert-info" role="alert" style="text-align: center;">';
        spinner += '<p class="lead"><span class="fa fa-spinner fa-spin"></span></p>';
        spinner += '</div>';
        contentTarget.html(spinner);

        // create a text string of form variables in standard URL-encoded notation
        var data = form.serialize(); 

        // set up the Ajax submission defaults
        var type = "POST";
        var url = form.attr('action');

        // submit the form
        makeAjaxCall(data,type,url,form);
    });

    // makeAjaxCall function
    function makeAjaxCall(data,type,url,form)
    {
        $.ajax({
            type    : type,
            url     : url,
            data    : data,
            cache    : false,
        }).done(successFunction)
          .fail(failFunction)
          .always(alwaysFunction(form));
    }

    // success function (when the server responds)
    function successFunction(data,textStatus,jqXHR) 
    {
        // create a JS object from the returned string
        var jsonData = JSON.parse(data); // best practice: use native JS instead of jQuery

        // create the server response message
        var message = '<div class="alert alert-'+jsonData.alert+' alert-dismissible" role="alert">';
        message += '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>';
        message += '<p class="lead">'+jsonData.message+'</p>';
        message += '</div>';

        // process the server response
         if (jsonData.status == 'sent')
         {
            // reset the form fields back to their defaults
            $(".async")[0].reset(); // best practice: using [0] will return the native html element, not the DOM element
        }
        else // aka. status is 'error'
        {
            /**
             * @TODO: on (re)submit, clear any old error messages that were appended to the inputs
             */
            // append error messages to matching inputs 
            $.each(jsonData.errors, function(input,error)
                {
                    $.each(error, function(type,message)
                        {
                            var notice = '<p class="help-block text-danger">'+type+' : '+message+'</p>';
                            var errorTarget = "#"+input;
                            $(errorTarget).parent().append(notice);
                        });
                });
        }
        // post the server response message
        contentTarget.html(message);
    }

    // fail function (when Ajax fails to make the connection to the server)
    function failFunction(request, textStatus, errorThrown) 
    {
        // create & post the 'failed request' message
        var message = '<div class="alert alert-danger alert-dismissible" role="alert">';
        message += '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>';
        message += '<p class="lead">OOPS! An error occurred during your request: ' + request.status + ' ' + textStatus + ': ' + errorThrown+'</p>';
        message += '</div>';
        // post the fail message
        contentTarget.html(message);
    }

    // always function, executes regardless of success or failure
    function alwaysFunction(form) 
    {
        // re-enable the submit button
        form.find('.submit').prop('disabled', false);
    }
});

Note, the alert messages in this script are marked up to work with Bootstrap 3 and FontAwesome 4. Adjust these as needed for your project.

Also, look closely at this script and you’ll notice it can handle any form with a class of .async, not just the contact form!


7: Implement and Test

At this point, the coding is done. All that remains is to implement and test what you’ve done.

Note that I purposely avoided using a specific testing regime in this tutorial. This is because just about everyone has their own approach and toolset for testing. Simply adjust the instructions provided to suit your preferred methodology.


Conclusion

Assuming all has gone well, you should now have in hand a simple, robust, and highly-performant contact form that uses Ajax, jQuery, and JSON to leverage many of CakePHP’s powerful new features.


Next Steps…

In a follow-up to this tutorial, I take the project one step further by Integrating the SparkPost API With CakePHP 3.4.

Implementing PHPMailer With Ajax & SMTP

I’ve been implementing a lot of small websites lately that use Bootstrap templates. These templates generally include a contact form, and most also include Javascript and PHP scripts to submit the form.

The majority of these scripts rely on the PHP ‘mail()’ function to send email. I prefer to use the PHPMailer class. To keep things simple, I also prefer to send email using the SMTP protocol on small projects.

This tutorial describes how to implement a simple, secure, highly-performant contact form using PHPMailer(), Ajax, and SMTP.


Table of Contents

  1. Starting Assumptions
  2. Install PHPMailer
  3. Create the Contact Form
  4. Include the Client-side Validator
  5. Include the Server-side Validator
  6. Create the Form Processor Script
  7. Create the Form Submission Script
  8. Implement and Test
  9. Conclusion

1: Starting Assumptions

Before starting, be sure the following is true for your project:

  • PHP 5.6 or higher is installed
  • jQuery is installed
  • An email hosting account is already set up to send emails using SMTP *
  • You are comfortable working with PHP, Javascript, jQuery, and HTML
  • You are familiar with command-line basics **

* I like using Zoho Mail as well as SparkPost, but any SMTP-capable hosting service will do.

** Command-line instructions are shown for Linux Ubuntu 16.04. You may need to make adjustments if you are using a different OS, or if you prefer to use an IDE or GUI-based text-editor (i.e SublimeText).


2: Install PHPMailer

Go to the command line, navigate to the main folder for the website project, and type the following code to install the PHPMailer class:

composer require phpmailer/phpmailer

Be patient – it takes a minute or two for Composer to get everything organized.


3: Create the Contact Form

The contact form can be placed almost anywhere in your website project. I often include mine on the index page within a modal. Here’s the code I use:

<!-- Modal -->
<div class="modal fade" id="contactModal" tabindex="-1" role="dialog" aria-labelledby="contactModalLabel">
    <div class="modal-dialog" role="document">
        <div class="modal-content">

            <style type="text/css">#contactForm .control-label {display: none; visibility: hidden;}</style>
            <form method="post" accept-charset="utf-8" id="contactForm" class="async" role="form" name="sentMessage" action="assets/php/contact.php">

                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                    <h4 class="modal-title" id="contactModalLabel">Send me a message</h4>
                </div>

                <div class="modal-body">
                    <div class="row">
                        <div class="col-sm-12">
                            <input type="text" name="Title" id="Title" type="hidden" style="display: none;" />
                            <div class="form-group text required">
                                <label class="control-label" for="name">Name</label>
                                <input class="form-control" type="text" name="name" id="name" placeholder="Your Name (required)" required="required"/>
                            </div>

                            <div class="form-group email required">
                                <label class="control-label" for="email">Email</label>
                                <input class="form-control" type="email" name="email" id="email" placeholder="Your Email (required)" required="required"/>
                            </div>

                            <div class="form-group tel">
                                <label class="control-label" for="phone">Phone</label>
                                <input class="form-control" type="tel" name="phone" id="phone" placeholder="Your Phone# (optional)"/>
                            </div>

                            <div class="form-group textarea required">
                                <label class="control-label" for="message">Message</label>
                                <textarea class="form-control" name="message" id="message" required="required" rows="5"></textarea>
                            </div>

                        </div>
                    </div>
                </div>

                <div class="modal-footer">
                    <div id="responseMessage"></div>
                    <button type="submit" class="btn btn-xl btn-primary submit"><i class="fa fa-paper-plane"></i> Send Message</button>
                </div>

            </form>

        </div>
    </div>
</div>

Add this code to whichever page you want in your website project. For this tutorial, I’m using index.php. If you prefer not to include your form in a modal or to use Bootstrap, just strip out the related markup and you’re good to go.

Note that the markup for this form includes a novalidate attribute. This tells the client not to apply native HTML5 validation rules. Instead, we’ll add a custom validator to take care of this in the next step.


4: Include the Client-side Validator

NOTE: For the moment, I’m skipping this step

In a browser, go to http://reactiveraven.github.io/jqBootstrapValidation/ and download the latest version of the validator.

Now unzip this file into the directory where you store JS files for your project, then shorten the plugin folder’s name to jqBootstrapValidation.

This folder contains several files in addition to the validation script, including the readme.md and license files. Here’s how this looks for my projects:

../assets/js/jsBootstrapValidation/jsBootstrapValidation.js
../assets/js/jsBootstrapValidation/readme.md
../etc...

Now include jqBootstrapValidation.js into your project’s page template. If you’re following this tutorial, add the following to the bottom of the index.php document:

<script type="text/javascript" src="assets/js/jqBootstrapValidation/jqBootstrapValidation.js"></script>

Make sure to add this into the load sequence after jQuery and Bootstrap.


5: Include the Server-Side Validator

I like to use a validator on the server-side as well as client-side. For this tutorial, I’ll use this email validation script from Lars Moelleken.

Navigate to the folder where the project’s PHP scripts are stored, and create a new file called check_email.php. Here’s an example:

../assets/php/check_email.php

In a browser, go to http://gist.suckup.de/#sec_b88f0538d0dbcb2fc189, and copy the code for checkEmail.php to the clipboard. Open check_mail.php, and paste in the code you just copied.

You should end up with something that looks like this:

<?php
/**
 * checkEmail
 * allows for international email addresses
 * http://gist.suckup.de/#sec_b88f0538d0dbcb2fc189
 * @param String  $email
 * @param Boolean $mxCheck   (do not use, if you don't need it)
 *
 * @return Boolean
 */
function checkEmail($email, $mxCheck = false)
{
  if (!is_string($email) || strlen($email) >= 320) {
    $valid = false;
  } elseif (!preg_match('/^(.*<?)(.*)@(.*)(>?)$/', $email, $matches)) {
    $valid = false;
  } else {
    $domain = $matches[3];
    if (function_exists('idn_to_ascii')) {
      $email = $matches[1] . $matches[2] . '@' . idn_to_ascii($domain) . $matches[4];
    } else {
      $email = $matches[1] . $matches[2] . '@' . $domain . $matches[4];
    }
    if (!$domain || !$email) {
      $valid = false;
    } else {
      if (function_exists('filter_var') && function_exists('idn_to_ascii')) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
          $valid = false;
        } else {
          $valid = true;
        }
      } else {
        if (function_exists('idn_to_ascii')) {
          $regEx = "/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i";
        } else {
          $regEx = "/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([öäüa-z0-9]{1}[öäüa-z0-9\-]{0,62}[öäüa-z0-9]{1})|[öäüa-z])\.)+[öäüa-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i";
        }
        if (!preg_match($regEx, $email)) {
          $valid = false;
        } else {
          $valid = true;
        }
      }
      if ($valid && $mxCheck && function_exists('checkdnsrr')) {
        $valid = checkdnsrr($domain . '.', 'MX') || checkdnsrr($domain, 'A');
      }
    }
  }
  return $valid;
}

This server-side script goes deeper than the client-side validator to make sure the contact form’s sender has used a safe and valid email address.


6: Create the Form Processor Script

In the folder where the project’s PHP scripts are stored, create a new file called contact.php, as so:

../assets/php/contact.php

Open this new file and add the following code:

<?php
// Set the PHP time zone for SMTP to work. Do this in php.ini, or here if no access
date_default_timezone_set('Etc/UTC');
// load up PHPMailer
require_once('../../vendor/phpmailer/phpmailer/PHPMailerAutoload.php');
// load up the email validator
require_once('check_email.php');
// confirm this is a POST request
if ($_SERVER["REQUEST_METHOD"] == "POST") 
{
    // test for bots
    if(isset($_POST['Title']) && !empty($_POST['Title'])) 
    {
        die();
    } 
    else 
    {
        // set the default return message
        $mailsent = [
            'status'=>'error'
            ,'message'=>"OOPS, we couldn't validate the information you sent"
            ,'alert'=>'danger'];

        $errors = [];

        // cleanup method
        function clean_inputs($data) 
        {
            $data = trim($data);
            $data = stripslashes($data);
            $data = htmlspecialchars($data);
            return $data;
        }    

        // clean the submitted form fields data
        $fields = array(
            'name'         => clean_inputs($_POST['name']),
            'email'     => clean_inputs($_POST['email']),
            'phone'     => clean_inputs($_POST['phone']),
            'message'    => clean_inputs($_POST['message'])
        );

        // validate the name
        if (empty($fields["name"]))
        {
            // string is empty
            $errors['name']='Please provide your name';
        } 
        else
        {
            if (!preg_match("/^[-a-zA-Z ]*$/",$fields["name"])) 
            {
                // string contains invalid characters
                $errors['name']="Your name contains characters we can't recognize";
            }

            if (strlen($fields["name"]) < 2 )
            {
                // string too short
                $errors['name']='Your name must be at least 2 characters long';
            }

            if (strlen($fields["name"]) > 50 )
            {
                // string too long
                $errors['name']='Your name must be less than 50 characters long';
            }
        }

        // validate the email address
        if (empty($fields["email"]))
        {
            // string is empty
            $errors['email']='Please provide your email address';
        } 
        else
        {
            if (!checkEmail($fields["email"])) 
            {
                // string is not a valid email
                $errors['email']='Your email address is not properly formed';
            }
        }

        /**
         * @TODO: implement Google's libphonenumber client-side
         * Can also be implemented server-side with "https://github.com/giggsey/libphonenumber-for-php"
         */
        // validate the phone number, if provided
        if (!empty($fields["phone"]))
        {
            if (strlen($fields["phone"]) < 7)
            {
                // string too short
                $errors['phone']='Your phone# must be at least 7 characters long';
            }

            if (strlen($fields["phone"]) > 30)
            {
                // string too long
                $errors['phone']='Your phone# must not be more than 30 characters long';
            }
        } 

        // validate the message
        if (empty($fields["message"]))
        {
            // string is empty
            $errors['message']='Please include a message';
        } 
        else 
        {
            if (strlen($fields["message"]) <= 4) 
            {
                // string too short
                $errors['message']='Your message must be at least 4 characters long';
            }
            if (strlen($fields["message"]) >= 2048) 
            {
                // string too long
                $errors['message']='Your message cannot be more than 2048 characters long';
            }
        }

        // if no validation errors, send the email
        if (empty($errors))
        {
            // process the submitted data
            $from = $fields['email'];
            $from_name = $fields['name'];
            $content = $fields["message"];
            $phone = $fields["phone"];
            $to = "you@yourdomain.com";
            $to_name = "Your Name";
            $time =  strftime("%Y-%m-%d %H:%M:%S", time());
            $subject = "Contact form submitted from my website.";
            $message = "You have received a new message from your website contact form at $time.<br>";
            $message .= "Here are the details:<br>";
            $message .= "Name: $from_name<br>Email: $from<br>Phone: $phone<br>Message:<br>$content";
            $message = wordwrap($message, 70,"<br>");

            // Instantiate a Mailer entity
            $mail = new PHPMailer;

            // Set the email transport type
            $mail->isSMTP(); 

            // Enable SMTP debugging - careful, this can throw off AJAX testing!
            // 0 = off (for production use)
            // 1 = client messages
            // 2 = client and server messages
            $mail->SMTPDebug = 0;
            $mail->Debugoutput = 'html'; // ask for browser-friendly debugging output

            // Set the email server account details
            $mail->Host = "your mailserver address"; // mail server
            $mail->SMTPAuth = true; // Enable SMTP authentication
            $mail->Username = 'your mailserver username'; // SMTP username
            $mail->Password = 'your mailserver password'; // SMTP password
            $mail->SMTPSecure = 'tls';// Enable TLS encryption, `ssl` also accepted
            $mail->Port = 587; // the SMTP port number: likely to be 25, 465, or 587

            // prepare the email
            $mail->setFrom($from, $from_name);
            $mail->addReplyTo($from, $from_name);
            $mail->addAddress($to, $to_name); // Add a recipient
            $mail->isHTML(true); // Set email format to HTML
            $mail->Subject = $subject;
            $mail->Body = $message;
            //$mail->AltBody = 'This is a plain-text message body'; 

            // send the email
            if($mail->send()) 
            {
                $mailsent = [
                    'status'=>'sent'
                    ,'message'=>"Thanks for contacting me. I'll get back to you ASAP!"
                    ,'alert'=>'success'];
            } 
            else
            {
                $mailsent["message"]="OOPS, something went wrong with our server. Your email could not be sent.";
            }
        }
        else 
        {
            $mailsent['message'] = $mailsent['message'] . ' &mdash; ' . 'Please correct any errors and try again.';
            // include an array of specific errors in the response
            foreach ($errors as $key => $value) 
            {
                $mailsent['errors'][$key] = $value;
            }
        }
    }
    // return the response
    echo json_encode($mailsent, JSON_FORCE_OBJECT);
}
else // this is a GET request
{
    die;
}

This script handles all of the server-side functionality for creating, validating, and sending an email based on the data submitted via the contact form.

Don’t forget to replace the bolded items with appropriate values for your SMTP mail hosting service.


7: Create the Form Submission Script

Next, navigate to the folder containing the project’s javascript assets and create a new file called contact.js, like so:

../assets/js/contact.js

Open this new empty file, and paste in the following script:

$(function() {
    "use strict";

    var contentTarget = $("div#responseMessage");

    // handle form submissions
    $("#contactForm").submit(function(e)
    {
        // prevent the form from submitting 
        e.preventDefault();

        // set a convenience variable for the form
        var form = $(this);

        // disable the submit button to prevent repeated clicks
        form.find('.submit').prop('disabled', true);

        // create and post a temporary spinner
        var spinner = '<div class="alert alert-info" role="alert" style="text-align: center;">';
        spinner += '<p class="lead"><span class="fa fa-spinner fa-spin"></span></p>';
        spinner += '</div>';
        contentTarget.html(spinner);

        // create a text string of form variables in standard URL-encoded notation
        var data = form.serialize(); 

        // set up the Ajax submission defaults
        var type = "POST";
        var url = './'+form.attr('action');
//        var url = './assets/php/contact.php';
        var dataType =  'json';

        // submit the form
        makeAjaxCall(data,type,url,form,dataType);
    });

    // makeAjaxCall function
    function makeAjaxCall(data,type,url,form,dataType)
    {
        $.ajax({
            type    : type,
            url     : url,
            dataType: dataType,
            data    : data,
            cache    : false,
        }).done(successFunction)
          .fail(failFunction)
          .always(alwaysFunction(form));
    }

    // success function (when the server responds)
    function successFunction(data,textStatus,jqXHR) 
    {
        // create a JS object from the returned string
        var jsonData = JSON.parse(data); // best practice: use native JS instead of jQuery

        // create the server response message
/*
        var message = '<div class="alert alert-'+jsonData.alert+' alert-dismissible" role="alert" style="text-align: center;">';
        message += '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>';
        message += '<p class="lead">'+jsonData.message+'</p>';
        message += '</div>';
*/
        var message = '<div class="alert alert-'+jsonData.alert+'" role="alert" style="text-align: center;">';
        message += '<p class="lead">'+jsonData.message+'</p>';
        message += '</div>';

        // process the server response
         if (jsonData.status == 'sent')
         {
            // reset the form fields back to their defaults
            $(".async")[0].reset(); // best practice: using [0] will return the native html element, not the DOM element
        }
        else // aka. status is 'error'
        {
            /**
             * @TODO: on (re)submit, clear any old error messages that were appended to the inputs
             */
            // append error messages to matching inputs 
            $.each(jsonData.errors, function(input,error)
                {
                    var notice = '<p class="help-block text-danger"><strong><span class="fa fa-times"></span> '+error+'</strong></p>';
                    var errorTarget = "#"+input;
                    $(errorTarget).parent().append(notice);
                });
        }
        // post the server response message
        contentTarget.html(message);
    }

    // fail function (when Ajax fails to make the connection to the server)
    function failFunction(request, textStatus, errorThrown) 
    {
        // create & post the 'failed request' message
        var message = '<div class="alert alert-danger" role="alert" style="text-align: center;">';
        message += '<p class="lead">OOPS! An error occurred during your request: ' + request.status + ' ' + textStatus + ': ' + errorThrown+'</p>';
        message += '</div>';
        // post the fail message
        contentTarget.html(message);
    }

    // always function, executes regardless of success or failure
    function alwaysFunction(form) 
    {
        // re-enable the submit button
        form.find('.submit').prop('disabled', false);
    }
});

This script will trigger the browser’s native client-side validation whenever the form is submitted. On success, it then submits the form data to the server to be processed. It then posts the server’s response to the page.

Make sure this script is included in the project’s template file by adding the following just after the include for the client-side validator script, like so:

<script type="text/javascript" src="assets/js/contact.js"></script>

Fwiw, this script was built purely for demonstration purposes. In real life, your project may already have Ajax scripting in place that should be used instead. If not, feel free to adapt the above as needed.


8: Implement and Test

At this point, the coding is done and you should be ready to go. All that remains is to upload and test what you’ve done.

I purposely avoided using a specific testing regime in this tutorial because almost everyone has their own approach and toolset. Simply adjust the instructions provided to suit your preferred methodology.


Conclusion

Assuming all has gone well, you should now have in hand a simple, robust, and highly-performant contact form that uses PHPMailer() with Ajax, jQuery, and JSON.


Next Steps:

Phone# Validation

The above code checks server-side if ‘something’ was submitted for the phone# field. This should be updated to use Google’s libphonenumber to validate client-side, and this script from Giggsey as a PHP wrapper for Google’s stuff to validate server-side.

Unfortunately, Google’s JavaScript version of the script is embedded within a large utility library. Here’s a shortened JS-only version that looks like it might do the trick.

Meantime, I’ll just go with enforcing a non-blank field, then stripping out anything offensive with trim(), stripslashed(), and htmlspecialchars().

Client-side Validation

Revisit Step 4 above and sort out implementing a custom client-side validator.

Meantime, the server-side validation is strong enough to pick up anything that might slip past the browser’s native HTML5 validation.

Setting Up Zoho Mail

I was recently called upon to implement a mail hosting account for a client. I’ve had great success with Zoho Mail in the past, so this is my tool of choice once again. Here are the steps:

Step One:

Have the user sign up for a Zoho Account. They’ll need to login, click on the ‘all apps’ link at the bottom of the page, then click on the ‘mail’ icon. At this stage, Zoho will ask them to link a phone number to their account – make sure this step is completed, then have them send you their login credentials.

Step Two:

Login with the account owner’s credentials, then click on the email icon. Select the free ‘Lite’ plan, and click through.

Step Three:

Enter the user’s domain name and click through. This will create an account that serves mail for up to 25 users (aka: email addresses) for a single domain. Now click through the confirmation screen to set up the user’s account.

Step Four:

Verify the domain. Just follow the onscreen instructions to: a) create a CNAME zone record for the domain in GoDaddy; b) click the ‘verify button at the bottom of the Zoho screen.

Step Five:

Assuming all has gone well, Zoho should now be asking for an email address with which to create the first account. Enter the user’s preferred primary email address and click ‘create account’.

Step Six:

Click ‘skip to proceed’ on the next form, since we’re only setting up a single user account here. Same goes for the next screen where we have a chance to set up group emails.

Step Seven:

Set up the MX records on the server per instructions provided. Using http://mxtoolbox.com , test the domain to make sure the correct MX records are set, then click through to the next step.

Step Eight:

Proceed with adding the SPF/DKIM records to the domain’s zone file per instructions. In GoDaddy, add a TXT record with the SPF string provided by Zoho. Click through to the ‘Proceed to Configure DKIM’ step.

Step Nine:

Follow the DKIM setup instructions to create a key, add a TXT record to the domain’s zone file, and verify the key works. Click “Back to Setup” when verified.

Step Ten:

Skip the “Migrate”  and “Mobile Setup” steps, and proceed to the control panel. Navigate to “Mail Administration>Email Authentication>DKIM” and ‘enable’ DKIM for the domain.

Step Eleven:

Navigate to ‘Email Forwarding>Access>Email forwarding and POP/IMAP, then enable IMAP. Confirm the settings for both POP and IMAP clients are visible.

Step Twelve:

Let the client know to follow the same steps as in Eleven to access their POP/IMAP settings so they can set up their email client.

And that’s it!