diff --git a/search_api_saved_searches.admin.inc b/search_api_saved_searches.admin.inc
index 315f1a9..90da7ed 100644
--- a/search_api_saved_searches.admin.inc
+++ b/search_api_saved_searches.admin.inc
@@ -83,6 +83,7 @@ function search_api_saved_searches_index_edit(array $form, array &$form_state, S
 --  [site:name] team"),
   );
   $settings->options['mail']['notify'] += array(
+    'send' => TRUE,
     'title' => t('New results for your saved search at [site:name]'),
     'body' => t('[user:name],
 
@@ -341,11 +342,23 @@ function search_api_saved_searches_index_edit(array $form, array &$form_state, S
     '#required' => TRUE,
   );
 
+  $form['options']['mail']['notify_send'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Send mail notifications for new results'),
+    '#description' => t('Depending on the notification interval, send regular mail updates with new results for saved searches. Disable this option if you want to use some other mechanism to notify the user of new results (or if you do not want any notifications at all).'),
+    '#default_value' => $options['mail']['notify']['send'],
+    '#parents' => array('options', 'mail', 'notify', 'send'),
+  );
   $form['options']['mail']['notify'] = array(
     '#type' => 'fieldset',
     '#title' => t('Notification mails'),
     '#collapsible' => TRUE,
     '#collapsed' => $settings->enabled,
+    '#states' => array(
+      'visible' => array(
+        ':input[name="options[mail][notify][send]"]' => array('checked' => TRUE),
+      ),
+    ),
   );
   $form['options']['mail']['notify']['title'] = array(
     '#type' => 'textfield',
diff --git a/search_api_saved_searches.module b/search_api_saved_searches.module
index b9ac346..3b39533 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;
   }
@@ -1194,12 +1314,17 @@ function search_api_saved_searches_cron() {
   // by settings is necessary since the mails can differ between settings.
   $user_searches = array();
   foreach ($searches as $search) {
-    $user_searches[$search->mail . ' ' . $search->settings_id][] = $search->id;
-    // Set the last execution timestamp now, so the interval doesn't move and we
-    // don't get problems if the next cron run occurs before the queue is
-    // completely executed.
-    $search->last_queued = REQUEST_TIME;
-    $search->save();
+    // Check whether notifications are enabled for this search.
+    $settings = search_api_saved_searches_settings_load($search->settings_id);
+    $options = $settings->options;
+    if (!isset($options['mail']['notify']['send']) || $options['mail']['notify']['send']) {
+      $user_searches[$search->mail . ' ' . $search->settings_id][] = $search->id;
+      // Set the last execution timestamp now, so the interval doesn't move and we
+      // don't get problems if the next cron run occurs before the queue is
+      // completely executed.
+      $search->last_queued = REQUEST_TIME;
+      $search->save();
+    }
   }
   foreach ($user_searches as $searches) {
     $queue->createItem($searches);
@@ -1260,81 +1385,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;
 }
