diff --git a/includes/simplenews.admin.inc b/includes/simplenews.admin.inc
index 9350fc3..1f26c67 100644
--- a/includes/simplenews.admin.inc
+++ b/includes/simplenews.admin.inc
@@ -1463,7 +1463,7 @@ function simplenews_admin_settings_mail($form, &$form_state) {
   );
 
   $throttle = drupal_map_assoc(array(1, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000));
-  $throttle[999999] = t('Unlimited');
+  $throttle[SIMPLENEWS_UNLIMITED] = t('Unlimited');
   if (function_exists('getrusage')) {
     $description_extra = '<br />' . t('Cron execution must not exceed the PHP maximum execution time of %max seconds. You find the time spend to send emails in the <a href="/admin/reports/dblog">Recent log entries</a>.', array('%max' => ini_get('max_execution_time')));
   }
diff --git a/includes/simplenews.api.php b/includes/simplenews.api.php
index 6d104bf..5afc107 100644
--- a/includes/simplenews.api.php
+++ b/includes/simplenews.api.php
@@ -20,32 +20,6 @@ function hook_simplenews_subscription_operations() {
 }
 
 /**
- * Collect recipients for a simplenews newsletter.
- *
- * Get both subscribed and unsubscribed recipients per newsletter category.
- * Special care should be taken to handle unsubscribed users correctly.
- *
- * @param $recipients
- *   Array of recipient objects with the following as minimum:
- *   $recipients['mail@example.com']
- *     recipient->mail      Email address
- *     recipient->status    Status flag (1, 0)
- *                          1 = may receive email, is subscribed;
- *                          0 = should not receive email, is unsubscribed.
- *     recipient->language  Language code of preferred email language
- * @param $scid
- *   Newsletter category ID.
- *
- * @todo Is 'uid' also required?
- *      recipient->uid       User ID of recipient (0 for anonymous)
- */
-function hook_simplenews_recipients_alter(&$recipients, $tid) {
-  $category = simplenews_category_load($tid);
-  $recipients = simplenews_array_merge($recipients, simplenews_get_subscriptions_by_list($category->tid), 'simplenews_check_status');
-}
-
-
-/**
  * @todo
  */
 function hook_simplenews_category_insert($category) {
diff --git a/includes/simplenews.mail.inc b/includes/simplenews.mail.inc
index 5e7749c..9fddb3f 100644
--- a/includes/simplenews.mail.inc
+++ b/includes/simplenews.mail.inc
@@ -30,49 +30,18 @@ function simplenews_add_node_to_spool($node, $accounts = array()) {
   }
 
   if (is_object($node)) {
-    $spool_data['nid'] = $node->nid;
-    $spool_data['tid'] = $node->simplenews->tid;
-
-    // Get accounts subscribed to this newsletter.
-    // Using hook_simplenews_recipients modules can add recipients.
-    // @todo: This the result of this hook is ignored, remove this.
-    $recipients = simplenews_get_subscriptions_by_list($node->simplenews->tid);
-    foreach (module_implements('simplenews_recipients_alter') as $module) {
-      $function = $module . '_simplenews_recipients_alter';
-      $function($recipients, $node->simplenews->tid);
-    }
-
-    // Build data array of specified accounts.
-    // First we use the recipient data collected by hook_simplenews_recipients().
-    // If this fails we get the data from $accounts
-    if ($accounts) {
-      $temp_recipients = array();
-      foreach ($accounts as $account) {
-        if (isset($recipients[$account->mail])) {
-          $temp_recipients[$account->mail] = $recipients[$account->mail];
-        }
-        else {
-          // Set these accounts to 'subscribed'.
-          $account->status = 1;
-          $temp_recipients[$account->mail] = $account;
-        }
-      }
-      $recipients = $temp_recipients;
-    }
-
     // To send the newsletter, the node id and target email addresses
     // are stored in the spool.
     // Only subscribed recipients are stored in the spool (status = 1).
-
     $select = db_select('simplenews_subscriber', 's');
     $select->innerJoin('simplenews_subscription', 't', 's.snid = t.snid');
     $select->addField('s', 'mail');
     $select->addField('s', 'snid');
     $select->addField('t', 'tid');
-    $select->addExpression($spool_data['nid'], 'nid');
+    $select->addExpression($node->nid, 'nid');
     $select->addExpression(SIMPLENEWS_SUBSCRIPTION_STATUS_SUBSCRIBED, 'status');
     $select->addExpression(REQUEST_TIME, 'timestamp');
-    $select->condition('t.tid', $spool_data['tid']);
+    $select->condition('t.tid', $node->simplenews->tid);
     $select->condition('t.status', SIMPLENEWS_SUBSCRIPTION_STATUS_SUBSCRIBED);
     $select->condition('s.activated', SIMPLENEWS_SUBSCRIPTION_ACTIVE);
 
@@ -84,7 +53,7 @@ function simplenews_add_node_to_spool($node, $accounts = array()) {
     // in the spool. When cron is used newsletters are send to addresses in the
     // spool during the next (and following) cron run.
     if (variable_get('simplenews_use_cron', TRUE) == FALSE) {
-      simplenews_mail_spool(99999);
+      simplenews_mail_spool();
       drupal_set_message(t('Newsletter sent.'));
       simplenews_clear_spool();
       simplenews_send_status_update();
@@ -207,21 +176,14 @@ function simplenews_send_source(SimplenewsSourceInterface $source) {
  *
  * TODO: Redesign API to allow language counter in multilingual sends.
  */
-function simplenews_mail_spool($limit = NULL) {
+function simplenews_mail_spool($limit = SIMPLENEWS_UNLIMITED) {
   $check_counter = 0;
 
   // Send pending messages from database cache
-  // A limited number of mails is retrieved from the spool
-  $limit = isset($limit) ? $limit : variable_get('simplenews_throttle', 20);
   if ($spool_list = simplenews_get_spool(array(SIMPLENEWS_SPOOL_PENDING, SIMPLENEWS_SPOOL_IN_PROGRESS), $limit)) {
-    // Prevent session information from being saved while sending.
-    if ($original_session = drupal_save_session()) {
-      drupal_save_session(FALSE);
-    }
 
-    // Force the current user to anonymous to ensure consistent permissions.
-    $original_user = $GLOBALS['user'];
-    $GLOBALS['user'] = drupal_anonymous_user();
+    // Switch to the anonymous user.
+    simplenews_impersonate_user(drupal_anonymous_user());
 
     $count_fail = $count_success = 0;
 
@@ -285,11 +247,7 @@ function simplenews_mail_spool($limit = NULL) {
     variable_set('simplenews_last_cron', REQUEST_TIME);
     variable_set('simplenews_last_sent', $count_success);
 
-    // Restore the user.
-    $GLOBALS['user'] = $original_user;
-    if ($original_session) {
-      drupal_save_session(TRUE);
-    }
+    simplenews_revert_user();
   }
 }
 
@@ -379,7 +337,7 @@ function simplenews_get_expiration_time() {
  * @return array
  *   An array of message ids to be sent in the current run.
  */
-function simplenews_get_spool($status, $limit = NULL) {
+function simplenews_get_spool($status, $limit = SIMPLENEWS_UNLIMITED) {
   $messages = array();
   $clauses = array();
   $params = array();
@@ -414,7 +372,7 @@ function simplenews_get_spool($status, $limit = NULL) {
   if (lock_acquire('simplenews_acquire_mail')) {
     // Get message id's
     // Allocate messages
-    if (is_numeric($limit)) {
+    if ($limit > 0) {
       $query->range(0, $limit);
     }
     foreach ($query->execute() as $message) {
diff --git a/includes/simplenews.source.inc b/includes/simplenews.source.inc
index 19bfdd8..4b244d9 100644
--- a/includes/simplenews.source.inc
+++ b/includes/simplenews.source.inc
@@ -643,10 +643,26 @@ class SimplenewsSourceNode implements SimplenewsSourceNodeInterface {
    * Implements SimplenewsSourceInterface::getBody().
    */
   public function getBody() {
+
+    // Impersonate the subscriber if he is a user.
+    if ($uid = $this->getSubscriber()->uid) {
+      simplenews_impersonate_user($uid);
+    }
+
+    if ($cache = $this->cache->get('final', 'body')) {
+      return $cache;
+    }
+
     $body = $this->buildBody();
 
     // Build message body, replace tokens.
     $body = token_replace($body, $this->getTokenContext(), array('sanitize' => FALSE));
+    $this->cache->set('final', 'body', $body);
+
+    // Switch back to the previous user.
+    if ($uid) {
+      simplenews_revert_user();
+    }
     return $body;
   }
 
@@ -654,11 +670,26 @@ class SimplenewsSourceNode implements SimplenewsSourceNodeInterface {
    * Implements SimplenewsSourceInterface::getBody().
    */
   public function getPlainBody() {
+
+    // Impersonate the subscriber if he is a user.
+    if ($uid = $this->getSubscriber()->uid) {
+      simplenews_impersonate_user($uid);
+    }
+
+    if ($cache = $this->cache->get('final', 'body:plain')) {
+      return $cache;
+    }
     $body = $this->buildBody('plain');
 
     // Build message body, replace tokens.
     $body = token_replace($body, $this->getTokenContext(), array('sanitize' => FALSE));
     $body = simplenews_html_to_text($body, $this->getCategory()->hyperlinks);
+    $this->cache->set('final', 'body:plain', $body);
+
+    // Switch back to the previous user.
+    if ($uid) {
+      simplenews_revert_user();
+    }
     return $body;
   }
 
@@ -831,6 +862,12 @@ class SimplenewsSourceCacheBuild extends SimplenewsSourceCacheStatic {
    * Implements SimplenewsSourceCacheStatic::set().
    */
   function isCacheable($group, $key) {
+
+    // Only cache for anon users.
+    if (user_is_logged_in()) {
+      return FALSE;
+    }
+
      // Only cache data and build information.
     return in_array($group, array('data', 'build'));
   }
diff --git a/simplenews.module b/simplenews.module
index 1fd723c..b074955 100644
--- a/simplenews.module
+++ b/simplenews.module
@@ -82,6 +82,11 @@ define('SIMPLENEWS_OPT_INOUT_SINGLE', 'single');
 define('SIMPLENEWS_OPT_INOUT_DOUBLE', 'double');
 
 /**
+ * Used when sending an unlimited amount of mails from the spool.
+ */
+define('SIMPLENEWS_UNLIMITED', -1);
+
+/**
  * Implementation of hook_permission().
  *
  * @todo Change sort order where required: http://drupal.org/node/224333#sorting_permissions
@@ -792,7 +797,7 @@ function simplenews_field_ui_view_modes_tabs() {
  */
 function simplenews_cron() {
   module_load_include('inc', 'simplenews', 'includes/simplenews.mail');
-  simplenews_mail_spool();
+  simplenews_mail_spool(variable_get('simplenews_throttle', 20));
   simplenews_clear_spool();
   // Update sent status for newsletter admin panel.
   simplenews_send_status_update();
@@ -3150,3 +3155,66 @@ function simplenews_simplenews_source_cache_info() {
     ),
   );
 }
+
+
+/**
+ * Impersonates another user.
+ *
+ * Each time this function is called, the active user is saved and $new_user
+ * becomes the active user. Multiple calls to this function can be nested,
+ * and session saving will be disabled until all impersonation attempts have
+ * been reverted using user_revert_user().
+ *
+ * @todo: This function is a backport of http://drupal.org/node/287292. Switch
+ * to that once available.
+ *
+ * @param $new_user
+ *   User to impersonate, either a UID or a user object.
+ *
+ * @return
+ *   Current user object.
+ *
+ * @see simplenews_revert_user()
+ */
+function simplenews_impersonate_user($new_user = NULL) {
+  global $user;
+  $user_original = &drupal_static(__FUNCTION__);
+
+  if (!isset($new_user)) {
+    if (isset($user_original) && !empty($user_original)) {
+      // Restore the previous user from the stack.
+      $user = array_pop($user_original);
+
+      // Re-enable session saving if we are no longer impersonating a user.
+      if (empty($user_original)) {
+        drupal_save_session(TRUE);
+      }
+    }
+  }
+  else {
+    // Push the original user onto the stack and prevent session saving.
+    $user_original[] = $user;
+    drupal_save_session(FALSE);
+
+    if (is_numeric($new_user)) {
+      $user = user_load($new_user);
+    }
+    else {
+      $user = is_object($new_user) ? $new_user : (object) $new_user;
+    }
+  }
+
+  return $user;
+}
+
+/**
+ * Reverts to the previous user after impersonating.
+ *
+ * @return
+ *   Current user.
+ *
+ * @see simplenews_impersonate_user()
+ */
+function simplenews_revert_user() {
+  return simplenews_impersonate_user();
+}
\ No newline at end of file
