diff --git a/search_api_saved_searches.module b/search_api_saved_searches.module
index b9ac346..efd7966 100644
--- a/search_api_saved_searches.module
+++ b/search_api_saved_searches.module
@@ -363,7 +363,199 @@ 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(
+        '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();
+}
+
+/**
+ * Worker function to check if there are any new saved search results.
+ */
+function search_api_saved_searches_rules_get_saved_search_new_items($search_id) {
+  $return = array(
+    'result_count' => 0,
+    'results' => array(),
+  );
+
+  if (empty($search_id)) {
+    return $return;
+  }
+
+  $search = search_api_saved_search_load($search_id);
+  $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']);
+          }
+          $num_results = count($new_results);
+          $return['result_count'] = $num_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;
+}
+
 
 /**
  * Implements hook_search_api_index_update().
@@ -1172,17 +1364,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;
   }
