Implementing a mail queue in Drupal 7

By default Drupal sends an email as soon as you make a call to drupal_mail(). For relatively small sites this may never be a problem. However, if you run a large site with many users, then you need to develop a stratagy for controling how much mail gets sent at a given time.

Imagine that you have a site where users can subscribe to be notified by email everytime you post a piece of content. When you have thousands of users subscribed, a ton of mails will be sent immediatly and php may time out as Drupal tries to process all of the subscriptions at once. The solution is to create a cron queue for sending mails. This means that mails will not be sent right away but will instead be added to a queue to be processed on the next cron run. First we need to define the cron queue that will handle mail items. Cron queues are processed each time cron is run on your site and are defined in hook_cron_queue_info(). For more detailed information about queues, visit http://www.hashbangcode.com/blog/drupal-7-queues-api-579.html.

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
/**
 * Implements hook_cron_queue_info().
 */
function my_module_cron_queue_info() {
  return array(
    'mail_queue' => array(
      'worker callback' => 'my_module_queue_mail_queue',
      'time' => 100,
    ),
  );
}
?>

When Drupal processes the mail queue, each item will be passed to the function defined in ‘worker callback’. The ‘time’ value determines the max amount of time Drupal will spend processing the queue on each cron run. We set ‘time’ to 100 seconds to limit the number of mails that Drupal sends at a time and avoid php time outs. Note: You will likely want to set cron to run very frequently to aviod delays in sending mail. Next, we need to override the default mail system to add items to our cron queue instead of sending them right away. Let’s extend the default mailer class ‘DefaultMailSystem’ provided by Drupal. See DefaultMailSystem.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
/**
 * A custom mail system to queue up messages.
 */
class QueueMailSystem extends DefaultMailSystem  {
  /**
   * Queue mail message instead of sending immediately.
   */
  public function mail(array $message) {
    $queue = DrupalQueue::get('mail_queue');
    $queue->createItem($message);
 
    // Return success.
    return TRUE;
  }
}
?>

You will aslo need to add the following line to your settings.php file so that Drupal will use our new mailer class as the default.

1
2
3
4
<?php
// Override the drupal mail system with our own implementation.
$conf['mail_system'] = array('default-system' => 'QueueMailSystem');
?>

Next, we will add a function to actually send the mail. We’re no longer sending the mail directly from the mail system so create a new function and just copy the code directly from DefaultMailSystem::mail().

1
2
3
4
5
6
7
8
9
10
<?php
/**
 * Send an email using the default mail system.
 *
 * @see DefaultMailSystem::mail()
 */
function my_module_queue_send_mail($message) {
  // Paste code from DefaultMailSystem::mail() here.
}
?>

Finally, all we need to do is create the worker callback that will process each item in the queue. We will do a simple check to put the message back in the queue if it fails to send. Other features could be added here as well such as limiting the number of retires.

1
2
3
4
5
6
7
8
9
10
11
12
<?php
/**
 * Mail queue worker callback.
 */
function my_module_queue_mail_queue($message) {
  if (!ct_mail_queue_send_mail($message)) {
    // Requeue on failure.
    $queue = DrupalQueue::get('mail_queue');
    $queue->createItem($message);
  }
}
?>
Tweet

Comments:

Subscribe & Contact

Know how to listen, and you will profit even from those who talk badly.
~ Plutarch ~