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.

Leave a Reply

Your email address will not be published. Required fields are marked *