Sending emails from a PHP application is a common task, whether it's only for confirmation emails or otherwise. However, the inbuilt PHP mail function is often used, and emails are sent inline in application functions, both of which are bad ideas for a number of reasons:

  • Using PHP's mail function means that email is being sent from the application servers, rather than dedicated mail servers.

  • PHP mail rely's on your application server having sendmail installed and configured.

  • Email providers often automatically reject emails sent by PHP mail.

  • Sending mail inside core application functionality drastically slows it down.

These issues can be solved quite easily by using a combination of a third party email sending service, with a method of queuing email.

  • A third party email service has already solved the above sendability problems, so there is no need to reinvent the wheel. Examples of these include MailChimp's Mandrill, SendGrid and Amazon's SES.

  • Using a queuing system means that the sending is done out-of-line of application functionality, it is much faster to add a job to a queue that send an email. Examples of these include Beanstalkd, Amazon's SQS and RabbitMQ.

In this tutorial we will use the Mandrill (using the Mandrill PHP SDK) with Beanstalkd (Using the PHP Pheanstalk library) to set up a simple email solution.

Setting Up

The first thing you'll need to do is to set up a Mandrill account and get an API key. The first 2000 emails with Mandrill are free, which should be more than enough to get you off the ground. Visit (http://mandrill.com/) to sign up, and then go to settings and click New API Key.

Next you'll need to install Beanstalkd. Visit http://kr.github.io/beanstalkd/download.html for platform specific instructions, but you can install on OSX using Homebrew by running brew install beanstalkd. Start beanstalkd running locally to where you will be running PHP. On OSX you can simply run beanstalkd. Please note that on a production environment you will want to run beanstalkd as a fault tolerant background service, i.e. as a daemon.

Now we have Mandrill and Beanstalkd set up, we can start writing code. The first step we will need to take is installing the Mandrill PHP SDK and Pheanstalk to our application using Composer. If you are not familiar with composer please check out https://getcomposer.org/

To install these dependencies simply run composer require mandrill/mandrill pda/pheanstalk

Adding Email to the Queue

The first thing we'll do is to write some code which adds an email job to our beanstalkd queue. This code mimics what you would do inside your application when sending an email.

send_email.php

<?php

require_once __DIR__ . '/vendor/autoload.php';

$email = array(
    'to' => 'xyz@example.com',
    'from' => 'abc@example.com',
    'subject' => 'Hello',
    'body' => 'Hello old chap!'
);

$pheanstalk = new \Pheanstalk\Pheanstalk('127.0.0.1');

// Add json for job to "email_queue" beanstalk tube
$pheanstalk
    ->useTube('email_queue')
    ->put(json_encode($email));

By running this (either on the command line or through a browser) a job is added to the tube called "email_queue" to the beanstalkd process running locally on 127.0.0.1. The job itself is a JSON string of a simple array, with basic details about the email we wish to send. In reality we would write this functionality into a class or function and call when we want to send a email in our application.

Also, please note that you will need to change the to and from email addresses here, so you recieve the test emails. From email addresses may need to be verified by Mandrill, to check you can send email from this address - this is a simple process only involving clicking a link in an email.

Writing the Queue Worker

Now we can add jobs to our email queue, we need to write a worker which will monitor the queue and process any emails on it. (Remember to change to your own Mandrill API Key).

email_worker.php

<?php

require_once __DIR__ . '/vendor/autoload.php';

$pheanstalk = new \Pheanstalk\Pheanstalk('127.0.0.1');

// Continuously loop to endlessly monitor beanstalk queue
while (true) {
    // Checks beanstalk connection
    if (!$pheanstalk->getConnection()->isServiceListening()) {
        echo "error connecting to beanstalk, sleeping for 5 seconds... \n";

        // Sleep for 5 seconds
        sleep(5);

        // Skip to next iteration of loop
        continue;
    }

    // Get job from queue, if none wait for a job to be available
    $job = $pheanstalk
        ->watch('email_queue')
        ->ignore('default')
        ->reserve();

    $email = json_decode($job->getData(), true);

    try {
        // Send the email using the mandrill api
        $mandrill = new Mandrill('[MANDRILL-API-KEY');

        $message = array(
            'html' => $email['body'],
            'subject' => $email['subject'],
            'from_email' => $email['from'],
            'to' => array(
                array(
                    'email' => $email['to'],
                )
            )
        );

        // Send the message
        $result = $mandrill->messages->send($message);

        // Remove the job from the queue
        $pheanstalk->delete($job);

        echo "message successfully sent \n";

    } catch (Exception $e) {
        echo "Error sending message - {$e->getMessage()} \n";
    }
}

The above code may seem complicated, but I will explain it section by section:

  • First we include the composer autoloader, so we have access to our dependencies

  • We then open our connection to beanstalkd on 127.0.0.1

  • An endless loop is started, as this a worker it needs to run continuously

  • We check if the beanstalkd process is accessible, if not we print a message and sleep for 5 seconds before starting the loop again.

  • Next a job is retrieved from the queue. If there are no jobs PHP waits here until there is. The JSON data in the job is turned back into an array $email, ready for use.

  • Inside a try catch block, to handle any Mandrill errors, we use the Mandrill SDK to send the message.

  • If there is an error we output a message and let the loop continue. The reserved job will be added back to the queue after a delay.

  • If it is successful, the job is deleted from the queue so it isn't used again, and a success message is outputted.

As long as this worker is running, it will continue to monitor the email_queue beanstalk queue, and send any emails added to the queue.

Running the Worker

You can start the worker by simply running php email_worker.php in development, but for production environments you'll want to do this slightly differently. Here the output will be displayed directly to the terminal for debugging.

To run the worker in the background you can run this instead:

nohup php email_worker.php > path/to/logfile.txt &

This runs the process in the background and outputs to a log file instead. This is much better than the above example, but you may want to write a cron job to check if the process is still running and start it if it stops for some reason or another. You could go one step further and run it as a daemon using the PHP POSIX and PCNTL extensions.

Next Steps

Now you have a basic working example there are several things that can be done to extend it:

  • If there is a bad message in the queue which keeps getting processed and errors every time, these will need to be handled. For example, this could be by sending a notification to yourself, so you can investigate the reason for failure.

  • It would be useful to monitor the queue, so you can see how many jobs build up at various times, if any. Using this information you can see how you need to scale your email worker, or to see if there are any problems.

  • For auditing purposes it would be useful to save a copy of all emails your application sends. You could do this using a BCC or writing functionality for them to be saved in a database into the worker.

You can find all of the above code samples at https://github.com/gaw508/tutorials/tree/master/php-email-mandrill-beanstalk-tutorial