diff --git a/amazon_ses.mail.inc b/amazon_ses.mail.inc
index 3d3268c..ddea004 100644
--- a/amazon_ses.mail.inc
+++ b/amazon_ses.mail.inc
@@ -12,91 +12,206 @@
  */
 
 
-  /**
-   * Send the email message.
-   *
-   * @see drupal_mail()
-   *
-   * @param $message
-   *   A message array, as described in hook_mail_alter().
-   * @return
-   *   TRUE if the mail was successfully accepted, otherwise FALSE.
-   */
-  function amazon_ses_send($message) {
-    $source = variable_get('amazon_ses_from', '');
-    if (!$source) {
-      drupal_set_message(t('Amazon SES can\'t send email. Please <a href="!config">configure a valid From address', array('!from' => url('admin/settings/amazon_ses'))), 'error');
-      return FALSE;
-    }
-    $destination = array();
-    $opt = array();
+/**
+ * Send the email message.
+ *
+ * @see drupal_mail()
+ *
+ * @param $message
+ *   A message array, as described in hook_mail_alter().
+ * @param CFBatchRequest $queue
+ *   If specified, messages will be added to the specified queue for later execution. Otherwise
+ *   messages are sent immediately. See amazon_ses_send_bulk().
+ * @return
+ *   TRUE if the mail was successfully accepted, otherwise FALSE.
+ */
+function amazon_ses_send($message) {
+  // Defines variables: $source, $destination, $ses_message, $options
+  extract(amazon_ses_prepare_message($message));
+  
+  if (variable_get('amazon_ses_debugging', 0)) {
+    watchdog('amazon-ses', 'Sending mail to: @to', array('@to' => $destination['ToAddresses'][0]));
+    drupal_set_message(t('Sending mail to: @to.', array('@to' => $destination['ToAddresses'][0])), 'notice');
+  }
 
-    $destination['ToAddresses'] = (count(explode(',', $message['to']) > 1) ? explode(',', $message['to']) : $message['to']);
+  $mailer = new AmazonSES();
+  $status = $mailer->send_email($source, $destination, $ses_message, $options);
 
-    if (is_array($message['body'])) {
-      $message['body'] = implode("\n\n", $message['body']);
-    }
+  if (!$status->isOK()) {
+    watchdog('amazon-ses', 'Error sending email from @from to @to : !error_message', array('@from' => $source, '@to' => print_r($destination['ToAddresses'], TRUE), '!error_message' => (string) $status->body->Error->Message), WATCHDOG_ERROR);
+    watchdog('amazon-ses', '<pre>Source: !source <br />Destination: !destination <br />Message: !message', array('!source' => print_r($source, TRUE), '!destination' => print_r($destination, TRUE), '!message' => print_r($ses_message, TRUE)), WATCHDOG_ERROR);
+    return FALSE;
+  }
+  return TRUE;
+}
+  
+/**
+ * Sends the same email to multiple recipients, one email per address in the array. This is more 
+ * efficient that repeatedly calling amazon_ses_send().
+ * 
+ * @param array $emails
+ *   An array of recipients.
+ * @param $message
+ *   A message array, as described in hook_mail_alter(). 
+ * @return unknown_type
+ *   TRUE if the mail was successfully accepted, otherwise FALSE.
+ */
+function amazon_ses_send_bulk($emails, $message) {
+  // Defines variables: $source, $destination, $ses_message, $options
+  extract(amazon_ses_prepare_message($message));
+  
+  $mailer = new AmazonSES();
+  
+  // Get MaxSendRate as reported by Amazon for this user's account
+  $result = $mailer->get_send_quota();
+  $max = (int)$result->body->GetSendQuotaResult->MaxSendRate;
 
-    $ses_message = array(
-      'Subject.Data' => $message['subject'],
-      'Body.Text.Data' => $message['body'],
-      'Body.Html.Data' => check_markup($message['body'], variable_get('amazon_ses_filter_format', 2), FALSE),
+  // Run using $max parallel queues.
+  // NOTE: it may still be possible to be throttled if your round-trip to AmazonSES is very quick.
+  // If so, we'll need to add a user-specified (or calculated?) multiplier to $max.
+  $queue = new CFBatchRequest($max);
+  if (variable_get('amazon_ses_debugging', 0)) {
+    watchdog('amazon-ses', 'Using !queues to send !emails.', 
+      array(
+        '!queues' => format_plural($max, '1 queue', '@count queues'),
+        '!emails' => format_plural(count($emails), '1 email', '@count emails'),
+      )
     );
-
-    $opt['ReturnPath'] = $source;
-    if (isset($message['headers']['Reply-To']) && !empty($message['headers']['Reply-To'])) {
-      if (strpos($message['headers']['Reply-To'], '<')) {
-        $opt['ReturnPath'] = preg_replace('/>.*/', '', preg_replace('/.*</', '', $message['headers']['Reply-To']));
-      }
-      else {
-        $opt['ReturnPath'] = $message['headers']['Reply-To'];
-      }
+    drupal_set_message(t('Using !queues to send !emails.', 
+      array(
+        '!queues' => format_plural($max, '1 queue', '@count queues'),
+        '!emails' => format_plural(count($emails), '1 email', '@count emails'),
+      ),
+      'notice'
+    ));
+  }
+  
+  // Add each message to the queue
+  foreach ($emails as $email) {
+    $destination = array('ToAddresses' => $email);
+    
+    if (variable_get('amazon_ses_debugging', 0)) {
+      watchdog('amazon-ses', 'Queuing mail to: @to', array('@to' => $destination['ToAddresses']));
+      drupal_set_message(t('Queuing mail to: @to.', array('@to' => $destination['ToAddresses'])), 'notice');
     }
+    $mailer->batch($queue)->send_email($source, $destination, $ses_message, $options);
+  }
+  
+  // Process the batch
+  if (variable_get('amazon_ses_debugging', 0)) {
+    watchdog('amazon-ses', 'Sending queued emails');
+    drupal_set_message(t('Sending queued emails'), 'notice');
+  }
+  $results = $mailer->batch($queue)->send();
+  
+  // Check for errors
+  $errors = 0;
+  foreach ($results as $index => $result) {
+    if (!$result->isOK()) {
+      $errors++;
+      watchdog('amazon-ses', 'Error sending email to %to: !error_message : <pre>!error_dump</pre>', 
+        array(
+          '%to' => $emails[$index],
+          '!error_message' => (string) $status->body->Error->Message, 
+          '!error_dump' => print_r($result, TRUE),
+        ),
+        WATCHDOG_ERROR
+      );
+    }
+  }
+  if ($errors) {
+    drupal_set_message(t(
+      '!err not sent due to errors. Check the !link for details.',
+      array(
+        '!err' => format_plural($errors, '1 email was', '@count emails were'),
+        '!link' => l('recent logs entries', 'admin/reports/dblog'),
+      )
+    ));
+    return FALSE;
+  }
+  else {
+    return TRUE;
+  }
+}  
 
-    // Parse the headers of the message
-    foreach ($message['headers'] as $key => $value) {
-      switch (drupal_strtolower($key)) {
-        case 'content-type':
-          $vars = explode('; ', $value);
-          foreach ($vars as $i => $var) {
-            if ($cut = strpos($var, '=')) {
-              $new_var = drupal_strtolower(drupal_substr($var, $cut + 1));
-              $new_key = drupal_substr($var, 0, $cut);
-              unset($vars[$i]);
-              $vars[$new_key] = $new_var;
-            }
-          }
-          // Set the charset based on the provided value, if there is one.
-          $charset = $vars['charset'];
-          if ($charset) {
-            $ses_message['Subject.Text.Charset'] = $charset;
-            $ses_message['Body.Text.Charset'] = $charset;
-            $ses_message['Body.Html.Charset'] = $charset;
-          }
-        break;
+/**
+ * Convert a Drupal message into an AmazonSES-ready message.
+ * 
+ * @see SendEmail documentation at http://docs.amazonwebservices.com/ses/latest/APIReference.
+ * 
+ * @param $message
+ *   A message array as described by hook_mail_alter().
+ * @return associative array 
+ *   Containing keys: source, destination, ses_message, options.
+ */
+function amazon_ses_prepare_message($message) {
+  $source = variable_get('amazon_ses_from', '');
+  if (!$source) {
+    drupal_set_message(t('Amazon SES can\'t send email. Please <a href="!config">configure a valid From address', array('!from' => url('admin/settings/amazon_ses'))), 'error');
+    return FALSE;
+  }
+  $destination = array();
+  $opt = array();
+
+  if (isset($message['to'])) {
+    $destination['ToAddresses'] = (count(explode(',', $message['to']) > 1) ? explode(',', $message['to']) : $message['to']);
+  }
 
-        case 'cc':
-          $destination['CcAddresses'] = split(',', $value);
-        break;
+  if (is_array($message['body'])) {
+    $message['body'] = implode("\n\n", $message['body']);
+  }
 
-        case 'bcc':
-          $destination['BccAddresses'] = split(',', $value);
-        break;
-      }
-    }
+  $ses_message = array(
+    'Subject.Data' => $message['subject'],
+    'Body.Text.Data' => $message['body'],
+    'Body.Html.Data' => check_markup($message['body'], variable_get('amazon_ses_filter_format', 2), FALSE),
+  );
 
-    if (variable_get('amazon_ses_debugging', 0)) {
-      watchdog('amazon-ses', 'Sending mail to: @to', array('@to' => $destination['ToAddresses'][0]));
-      drupal_set_message(t('Sending mail to: @to.', array('@to' => $destination['ToAddresses'][0])), 'notice');
+  $opt['ReturnPath'] = $source;
+  if (isset($message['headers']['Reply-To']) && !empty($message['headers']['Reply-To'])) {
+    if (strpos($message['headers']['Reply-To'], '<')) {
+      $opt['ReturnPath'] = preg_replace('/>.*/', '', preg_replace('/.*</', '', $message['headers']['Reply-To']));
+    }
+    else {
+      $opt['ReturnPath'] = $message['headers']['Reply-To'];
     }
+  }
+
+  // Parse the headers of the message
+  foreach ($message['headers'] as $key => $value) {
+    switch (drupal_strtolower($key)) {
+      case 'content-type':
+        $vars = explode('; ', $value);
+        foreach ($vars as $i => $var) {
+          if ($cut = strpos($var, '=')) {
+            $new_var = drupal_strtolower(drupal_substr($var, $cut + 1));
+            $new_key = drupal_substr($var, 0, $cut);
+            unset($vars[$i]);
+            $vars[$new_key] = $new_var;
+          }
+        }
+        // Set the charset based on the provided value, if there is one.
+        $charset = $vars['charset'];
+        if ($charset) {
+          $ses_message['Subject.Text.Charset'] = $charset;
+          $ses_message['Body.Text.Charset'] = $charset;
+          $ses_message['Body.Html.Charset'] = $charset;
+        }
+      break;
 
-    $mailer = new AmazonSES();
-    $status = $mailer->send_email($source, $destination, $ses_message, $opt);
+      case 'cc':
+        $destination['CcAddresses'] = split(',', $value);
+      break;
 
-    if (!$status->isOK()) {
-      watchdog('amazon-ses', 'Error sending email from @from to @to : !error_message', array('@from' => $source, '@to' => print_r($destination['ToAddresses'], TRUE), '!error_message' => (string) $status->body->Error->Message), WATCHDOG_ERROR);
-      watchdog('amazon-ses', '<pre>Source: !source <br />Destination: !destination <br />Message: !message', array('!source' => print_r($source, TRUE), '!destination' => print_r($destination, TRUE), '!message' => print_r($ses_message, TRUE)), WATCHDOG_ERROR);
-      return FALSE;
+      case 'bcc':
+        $destination['BccAddresses'] = split(',', $value);
+      break;
     }
-    return TRUE;
   }
+  return array(
+    'source' => $source,
+    'destination' => $destination,
+    'ses_message' => $ses_message,
+    'options' => $opt,
+  );
+}
