CakePHP 2: Sending Emails In The Background

Welcome to my new blog, this is my first post in which I’ll show you how to setup a database transport for your CakePHP Emails.

CakePHP makes it easy to send emails from within your models and controllers, for example maybe you will send a order confirmation email after a customer purchases an item, or perhaps a user activation email when they first sign up for your website. The problem with this direct approach is that your user will have to wait for your application to send the email before the page will load and in some instances this can make the experience very slow at times.

I’ll show you a method in which all emails will automatically be queued up in the database and sent from a shell using a cron job, whilst it’s not the perfect solution for applications sending thousands of emails each hour, the chances are this will speed up your application for your users.

Step 1 – Setting up the Database Transport

To begin the code I am showing you is freely available here as a full plugin, I will show you how to recreate this exactly rather than building it directly into your application. First we need to define how CakePHP should handle the email request and save it into the database.

File: app/Plugin/DatabaseTransport/Network/Email/DatabaseTransport.php

The focus of the above code is the send method, all this is doing is taking the information from CakeEmail and serializing the email content and extracting the headers such as ‘to’ and ‘from’. This is then saved as a database record in the email_queue table which will be picked up by the shell later on.

Step 2 – Setting up the database table

This is a nice and straightforward step, we just need to make sure we setup the database table to hold these emails.

Step 3 – Creating the Shell & Model

This is the really fun part, we’re going to create a shell (and associated model) which will query the database table for new emails at set intervals, when it detects new emails it will assign a unique id to the new batch and start sending them one by one. The reason we use a unique id is because let’s say you check every minute, but your shell is handling 1000 emails which takes more than a minute to send, we don’t want the next shell to queue up emails again and send duplicates.

Part 1 – Create the Shell Skeleton
File: app/Plugin/DatabaseTransport/Console/Command/EmailQueueShell.php

In the above snippet all we’re doing is laying out the outline of the shell, which is as follows:

  • Line 13: Include the CakeEmail object for when we send each email
  • Line 27: Tell the Shell which models it should use (for queries and such)
  • Line 34: Write a convenient header method which we can call on all methods (consistency is king)
  • Line 43: Write the main method which tells our console of available commands

Part 2 – Create the Model Skeleton

Before we can start our main email handling method of the shell, we’re going to need to create a model to handle some of the data first, set up the following model skeleton.

File: app/Plugin/DatabaseTransport/Model/EmailQueue.php

The above is a simple model layout, we will build it up with four crucial methods next, it’s important to have the $useTable parameter set up so CakePHP knows which table to use.

Model Method: updatePending
This method will find all pending emails and assign a consumer id to them, so they may be picked up by the shell and processed.

Model Method: pendingItems
This method will find all email items by consumer id which have not yet been processed by the shell.

Model Method: saveItemStatus
This method will set a status (0, 1 or 2) and processed date to an email record by primary key, used to log whether that particular email was successful or not.

Model Method: countByStatus
This method is used to calculate statistics for the status shell command, giving statistics for the last 24 hours.

The completed model
Just for your reference, here is the completed model file in its entirety.

Part 3 – Completing the Shell

Finally we may now complete the shell’s run and status methods and tie up some loose ends to give it a whirl. I’ll begin with the run method, I’ve commented the method more than I normally would but I’ll also attempt to explain a few things under the snippet too.

There are a few important things happening in the above method which I will outline below

  • Line 15: We’re calling the updatePending method from earlier to assign a batch
  • Line 20: We’re calling the pendingItems method from earlier to query the batch so we can loop and send further down.
  • Line 29: We’re defining our CakeEmail config to actually send the email (change this to whatever you normally use)
  • Line 32: We’re attempting to send the email by chaining through transportClass() directly.
  • Line 41: We’re logging the status of the email to the item record (success, failed).

Next up is the status method, it’s very straight forward as it just uses our countByStatus method from earlier to output information to the console window.

The complete shell

Below you can find the complete shell snippet for you to copy and paste if you wish.

 

Step 4 – Tying it all together

We’re almost done now, we just have to load the plugin in our app/config/bootstrap.php file using the following snippet  CakePlugin::load('DatabaseTransport');  and last but not least we should setup our email configuration to use the DatabaseTransport. In app/config/email.php do the following:

Now any time you send an email you can do the following  $email = new CakeEmail('database');  and set up the rest of the email as normal.

Don’t forget to set up your cron job to run the email queue shell using the following command  app/Console/cake DatabaseTransport.EmailQueue run

And that’s that! I hope you liked this blog post, please let me know if you have any questions or suggestions on this topic, I’d love to hear them.

3 thoughts on “CakePHP 2: Sending Emails In The Background”

  1. Hi there Stephen – *exactly* was I was looking for – thank you so much… except – it’s just not firing for me…

    — I’ve loaded the files/folders into app/Plugins; database table is in; updated bootstrap; and added the $database option in config/email.php.
    — I’m now firing off email via CakeEmail(‘database’), but no response from Cake – it keeps on using the default CakeEmail.
    — I’ve tried debug = 2; manually clearing all tmp cache etc – it just purrs along, working, as if the plugin wasn’t fired.

    I’d really value your thoughts. Shoot me an email direct (I’ll post any resolution to my stoopidness here afterwards!).

    Very happy to offer you a lifetime Pro membership on my site in return thanks! AB

  2. OK – solved, and an update/clarification, I think…

    $email = new CakeEmail(‘database’); transport(‘database’); transport(‘database’);
    + the rest of your email settings…

    Worked perfectly! (Still give my site a lookin, and I’ll promote you to a Pro)

    Many thanks, Stephen.

    AB

  3. [Edited to reformat/properly display code symbols]

    OK – solved, and an update/clarification, I think…

    $email = new CakeEmail(‘database’);
    – By itself, did not work for me: it kept using the default setup in config/email.php

    $Email->transport(‘database’);
    – But declaring the transport authoritatively within the CakeEmail worked a charm : )

    So, for clarity:
    $Email = new CakeEmail();
    $Email->transport(‘database’);
    + the rest of your email settings…

    Worked perfectly! (Still give my site a go, and I’ll promote you to a Pro)

    Many thanks, Stephen.

    AB

Leave a Reply

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