diff --git a/search_api_saved_searches.module b/search_api_saved_searches.module
index b9ac346..1702171 100644
--- a/search_api_saved_searches.module
+++ b/search_api_saved_searches.module
@@ -363,7 +363,137 @@ function search_api_saved_searches_user_delete($account) {
   entity_delete_multiple('search_api_saved_search', array_keys(search_api_saved_search_load_multiple(FALSE, array('uid' => $account->uid))));
 }
 
-// @todo Rules integration
+/**
+ * Implements hook_rules_action_info().
+ */
+function search_api_saved_searches_rules_action_info() {
+  return array(
+    'search_api_saved_searches_rules_index_results' => array(
+      'label' => t('Fetch the saved searches'),
+      'parameter' => array(
+        'index_id' => array(
+          'type' => 'integer',
+          'label' => t('Index for which to retrieve searches'),
+          'description' => t('Select the search index for which saved searches should be retrieved.'),
+          'options list' => '_search_api_saved_searches_settings_options_list',
+        ),
+      ),
+      'provides' => array(
+        'search_api_saved_search' => array(
+          'type' => 'list<integer>',
+          'label' => t('List of the IDs of the saved searches that require executing.'),
+        ),
+      ),
+      'group' => t('Search API Saved Searches'),
+    ),
+    'search_api_saved_searches_rules_get_saved_search_new_items' => array(
+      'label' => t('Fetch the new results for a saved search'),
+      'parameter' => array(
+        'index_id' => array(
+          'type' => 'integer',
+          'label' => t('Saved search ID'),
+          'description' => t('The ID of the saved search for which to retrieve new results.'),
+        ),
+      ),
+      'provides' => array(
+        'search' => array(
+          'type' => 'search_api_saved_search',
+          'label' => t('The executed search.'),
+        ),
+        'result_count' => array(
+          'type' => 'integer',
+          'label' => t('The count of results that were found.'),
+        ),
+        'results' => array(
+          'type' => 'list<integer>',
+          'label' => t('The list of new results for the saved search since it was last executed.'),
+        ),
+      ),
+      'group' => t('Search API Saved Searches'),
+    ),
+  );
+}
+
+/**
+ * Retrieves the options list for selecting a saved search settings entity.
+ *
+ * @return string[]
+ *   An associative array mapping saved search settings IDs to index names.
+ */
+function _search_api_saved_searches_settings_options_list() {
+  // Fetch the list of saved searches setting and make a list of values.
+  $entities = entity_load('search_api_saved_searches_settings');
+  $ids = array();
+  foreach ($entities as $entity) {
+    $ids[$entity->index_id][] = $entity->id;
+  }
+
+  $indexes = search_api_index_load_multiple(array_keys($ids));
+  $options = array();
+  foreach ($indexes as $index_id => $index) {
+    foreach ($ids[$index_id] as $settings_id) {
+      $options[$settings_id] = $index->label();
+    }
+  }
+  return $options;
+}
+
+/**
+ * Callback: Implements the "Fetch the saved searches" rules action.
+ *
+ * @param int|null $settings_id
+ *   (optional) The ID of the saved search settings entity for which to retrieve
+ *   searches. NULL to retrieve for all.
+ *
+ * @return array
+ *   An associative array with key "search_api_saved_search" containing the IDs
+ *   of all searches that should be executed.
+ */
+function search_api_saved_searches_rules_index_results($settings_id) {
+  return array(
+    'search_api_saved_search' => search_api_saved_searches_get_searches_to_be_executed($settings_id),
+  );
+}
+
+/**
+ * Retrieves the saved searches that need to be executed.
+ *
+ * @param int|null $settings_id
+ *   (optional) The ID of the saved search settings entity for which to retrieve
+ *   searches. NULL to retrieve for all.
+ *
+ * @return int[]
+ *   The IDs of all searches that need to be executed.
+ */
+function search_api_saved_searches_get_searches_to_be_executed($settings_id = NULL) {
+  // Get all searches whose last execution lies more than the notify_interval
+  // in the past. Add a small amount to the current time, so small differences
+  // in execution time don't result in a delay until the next cron run.
+  $select = db_select('search_api_saved_search', 's');
+  $select->fields('s', array('id'))
+    ->condition('enabled', 1)
+    ->condition('notify_interval', 0, '>=')
+    ->where('last_execute >= last_queued')
+    ->where('last_queued + notify_interval < :time', array(':time' => REQUEST_TIME + 15));
+  if ($settings_id !== NULL) {
+    $select->condition('settings_id', $settings_id);
+  }
+  return $select->execute()->fetchCol();
+}
+
+/**
+ * Callback: Implements the "Fetch the new results for a search" rules action.
+ *
+ * @param int $search_id
+ *   The ID of the saved search setting entity.
+ *
+ * @return array
+ *   Array of the results count and the results list for the given search ID.
+ */
+function search_api_saved_searches_rules_get_saved_search_new_items($search_id) {
+  $search = search_api_saved_search_load($search_id);
+  return search_api_saved_search_fetch_search_results($search);
+}
 
 /**
  * Implements hook_search_api_index_update().
@@ -1172,17 +1302,7 @@ function search_api_saved_searches_mail($key, array &$message, array $params) {
  * Queue the saved searches that should be checked for new items.
  */
 function search_api_saved_searches_cron() {
-  // Get all searches whose last execution lies more than the notify_interval
-  // in the past. Add a small amount to the current time, so small differences
-  // in execution time don't result in a delay until the next cron run.
-  $ids = db_select('search_api_saved_search', 's')
-    ->fields('s', array('id'))
-    ->condition('enabled', 1)
-    ->condition('notify_interval', 0, '>=')
-    ->where('last_execute >= last_queued')
-    ->where('last_queued + notify_interval < :time', array(':time' => REQUEST_TIME + 15))
-    ->execute()
-    ->fetchCol();
+  $ids = search_api_saved_searches_get_searches_to_be_executed();
   if (!$ids) {
     return;
   }
@@ -1260,81 +1380,108 @@ function search_api_saved_searches_check_updates(array $search_ids) {
   $index = $settings->index();
   $mail_params = array();
   foreach ($searches as $search) {
-    try {
-      // Make sure we run the query as the user who owns the saved search.
-      // Otherwise node access will not work properly.
-      $search->query['options']['search_api_access_account'] = $search->uid;
-      // Get actual results for the query.
-      $query = $search->query();
-
-      // If a date field is set, use that to filter results.
-      if (!empty($settings->options['date_field'])) {
-        $query->condition($settings->options['date_field'], $search->last_execute, '>');
-      }
-      $response = $query->execute();
-      if (!empty($response['results'])) {
-        $old = array();
-        $new = $results = drupal_map_assoc(array_keys($response['results']));
-        if (empty($settings->options['date_field'])) {
-          // ID-based method: Compare these results to the old ones.
-          $old = drupal_map_assoc(explode(',', $search->results));
-          $new = array_diff_key($results, $old);
-        }
-        if ($new) {
-          // We have new results: send them to the user.
-          // Only load those items that will be sent.
-          $sent_new = $new;
-          if (!empty($settings->options['mail']['notify']['max_results'])) {
-            $sent_new = array_slice($new, 0, $settings->options['mail']['notify']['max_results']);
-          }
-          $sent_new = $index->loadItems($sent_new);
-          $new_results = $sent_new + $new;
-          // Let other modules alter these results.
-          drupal_alter('search_api_saved_searches_new_results', $new_results, $search);
-          if ($new_results) {
-            // We have to slice again in case some items were moved around or
-            // removed by alter hooks.
-            $sent_new = $new_results;
-            if (!empty($settings->options['mail']['notify']['max_results'])) {
-              $sent_new = array_slice($new_results, 0, $settings->options['mail']['notify']['max_results']);
-              // Now some of the top results might still be unloaded.
-              if ($unloaded = array_filter($sent_new, 'is_scalar')) {
-                $sent_new = $index->loadItems($unloaded) + $sent_new;
-              }
-            }
-            $num_results = count($new_results);
-            $mail_params['searches'][] = array(
-              'search' => $search,
-              'num_results' => $num_results,
-              'results' => $sent_new,
-            );
-          }
-        }
-        if (empty($settings->options['date_field']) && ($new || array_diff($old, $results))) {
-          // The results changed in some way: store the latest version.
-          $search->results = implode(',', $results);
-        }
-      }
-      // Use time() instead of REQUEST_TIME to minimize the potential of sending
-      // duplicate results due to longer-running cron queue workers.
-      $search->last_execute = time();
-      $search->save();
+    $results = search_api_saved_search_fetch_search_results($search);
+    if (!$results['result_count']) {
+      continue;
     }
-    catch (SearchApiException $e) {
-      $args = _drupal_decode_exception($e);
-      $args['@id'] = $search->id;
-      throw new SearchApiException(t('%type while trying to check for new results on saved search @id: !message in %function (line %line of %file).', $args));
+    // Load the result items.
+    if ($results['results']) {
+      $results['results'] = $index->loadItems($results['results']);
     }
+    $mail_params['searches'][] = $results;
   }
   // If we set any searches in the mail parameters, send the mail.
   if ($mail_params) {
     $mail_params['user'] = user_load($search->uid);
     $mail_params['settings'] = $settings;
     $message = drupal_mail('search_api_saved_searches', 'notify', $search->mail,
-        user_preferred_language($mail_params['user']), $mail_params);
+      user_preferred_language($mail_params['user']), $mail_params);
     if ($message['result']) {
       watchdog('search_api_saved_searches', 'A mail with new saved search results was sent to @mail.',
-          array('@mail' => $search->mail), WATCHDOG_INFO);
+        array('@mail' => $search->mail), WATCHDOG_INFO);
+    }
+  }
+}
+
+/**
+ * Fetches the results for a given search object.
+ *
+ * @param SearchApiSavedSearch $search
+ *   The saved search to check for new results.
+ *
+ * @return array
+ *   An associative array with the following keys:
+ *   - search: The executed search.
+ *   - result_count: The number of new results.
+ *   - results: The IDs of the new results.
+ *
+ * @throws SearchApiException
+ *   If an error occurred in the search.
+ */
+function search_api_saved_search_fetch_search_results(SearchApiSavedSearch $search) {
+  $return = array(
+    'search' => $search,
+    'result_count' => 0,
+    'results' => array(),
+  );
+
+  $settings = $search->settings();
+  try {
+    // Make sure we run the query as the user who owns the saved search.
+    // Otherwise node access will not work properly.
+    $search->query['options']['search_api_access_account'] = $search->uid;
+    // Get actual results for the query.
+    $query = $search->query();
+
+    // If a date field is set, use that to filter results.
+    if (!empty($settings->options['date_field'])) {
+      $query->condition($settings->options['date_field'], $search->last_execute, '>');
+    }
+    $response = $query->execute();
+    if (!empty($response['results'])) {
+      $old = array();
+      $new = $results = drupal_map_assoc(array_keys($response['results']));
+      if (empty($settings->options['date_field'])) {
+        // ID-based method: Compare these results to the old ones.
+        $old = drupal_map_assoc(explode(',', $search->results));
+        $new = array_diff_key($results, $old);
+      }
+      if ($new) {
+        // We have new results: send them to the user.
+        // Only load those items that will be sent.
+        $sent_new = $new;
+        if (!empty($settings->options['mail']['notify']['max_results'])) {
+          $sent_new = array_slice($new, 0, $settings->options['mail']['notify']['max_results']);
+        }
+        $new_results = $sent_new + $new;
+        // Let other modules alter these results.
+        drupal_alter('search_api_saved_searches_new_results', $new_results, $search);
+        if ($new_results) {
+          // We have to slice again in case some items were moved around or
+          // removed by alter hooks.
+          $sent_new = $new_results;
+          if (!empty($settings->options['mail']['notify']['max_results'])) {
+            $sent_new = array_slice($new_results, 0, $settings->options['mail']['notify']['max_results']);
+          }
+          $return['result_count'] = count($new_results);
+          $return['results'] = $sent_new;
+        }
+      }
+      if (empty($settings->options['date_field']) && ($new || array_diff($old, $results))) {
+        // The results changed in some way: store the latest version.
+        $search->results = implode(',', $results);
+      }
     }
+    // Use time() instead of REQUEST_TIME to minimize the potential of sending
+    // duplicate results due to longer-running cron queue workers.
+    $search->last_execute = time();
+    $search->save();
+  }
+  catch (SearchApiException $e) {
+    $args = _drupal_decode_exception($e);
+    $args['@id'] = $search->id;
+    throw new SearchApiException(t('%type while trying to check for new results on saved search @id: !message in %function (line %line of %file).', $args));
   }
+
+  return $return;
 }
