diff --git a/feedback.admin.inc b/feedback.admin.inc
index f6424ce..735829a 100644
--- a/feedback.admin.inc
+++ b/feedback.admin.inc
@@ -221,6 +221,11 @@ function feedback_entry_form_submit(&$form, &$form_state) {
   $entry->url = url($entry->location, array('absolute' => TRUE));
   $entry->status = $form_state['values']['status'];
   feedback_save($entry);
+
+  // Trigger rule if Rules is enabled
+  if (module_exists('rules')) {
+    rules_invoke_event('feedback_update', $entry);
+  }
   drupal_set_message(t('The entry has been updated.'));
 }
 
diff --git a/feedback.module b/feedback.module
index a4acfc0..ad1bc56 100644
--- a/feedback.module
+++ b/feedback.module
@@ -342,6 +342,10 @@ function feedback_form_submit($form, &$form_state) {
   $entry->location = $form_state['values']['location'];
   feedback_save($entry);
 
+  // Trigger rule if Rules is enabled
+  if (module_exists('rules')) {
+    rules_invoke_event('feedback_insert', $entry);
+  }
   drupal_set_message(t('Thanks for your feedback!'));
 }
 
diff --git a/feedback.rules.inc b/feedback.rules.inc
new file mode 100644
index 0000000..8a44488
--- /dev/null
+++ b/feedback.rules.inc
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Rules integration for feedback.
+ *
+ * @addtogroup rules
+ * @{
+ */
+
+/**
+ * Implements hook_rules_event_info().
+ */
+function feedback_rules_event_info() {
+  $defaults = array(
+    'group' => t('Feedback'),
+    'module' => 'feedback',
+    'access callback' => 'feedback_rules_integration_access',
+  );
+  return array(
+    'feedback_insert' => $defaults + array(
+      'label' => t('After saving new feedback'),
+      'variables' => array(
+        'feedback' => array('type' => 'feedback', 'label' => t('Feedback message')),
+      ),
+    ),
+    'feedback_update' => $defaults + array(
+      'label' => t('After saving existing feedback'),
+      'variables' => array(
+        'feedback' => array('type' => 'feedback', 'label' => t('Feedback message')),
+      ),
+    ),
+  );
+}
+
+/**
+ * Rules integration access callback.
+ */
+function feedback_rules_integration_access($type, $name) {
+  if ($type == 'event' || $type == 'condition') {
+    return entity_access('view', 'feedback');
+  }
+}
+
+/**
+ * @}
+ */
diff --git a/feedback.tokens.inc b/feedback.tokens.inc
new file mode 100644
index 0000000..1c062dc
--- /dev/null
+++ b/feedback.tokens.inc
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * @file
+ * Builds placeholder replacement tokens for feedback-related data.
+ */
+
+/**
+ * Implements hook_token_info().
+ */
+function feedback_token_info() {
+  $types = array(
+    'feedback' => array(
+      'name' => t('Feedback'),
+      'description' => t('Tokens for feedback posted on the site.'),
+      'needs-data' => 'feedback',
+    ),
+  );
+
+  $tokens = array(
+    'feedback' => array(
+      'url' => array(
+        'name' => t("URL"),
+        'description' => t("The URL where feedback was submitted."),
+      ),
+      'useragent' => array(
+        'name' => t("User agent"),
+        'description' => t("The user agent string."),
+      ),
+      'message' => array(
+        'name' => t("Message"),
+        'description' => t("The message submitted."),
+      ),
+    ),
+  );
+
+  return array(
+    'types' => $types,
+    'tokens' => $tokens,
+  );
+}
+
+/**
+ * Implements hook_tokens().
+ */
+function feedback_tokens($type, $tokens, array $data = array(), array $options = array()) {
+  $url_options = array('absolute' => TRUE);
+  if (isset($options['language'])) {
+    $url_options['language'] = $options['language'];
+  }
+  $sanitize = !empty($options['sanitize']);
+
+  $replacements = array();
+
+  if ($type == 'feedback' && !empty($data['feedback'])) {
+    $feedback = $data['feedback'];
+
+    foreach ($tokens as $name => $original) {
+      switch ($name) {
+        case 'uid':
+          $replacements[$original] = $feedback->uid;
+          break;
+
+        case 'url':
+          $replacements[$original] = url($feedback->url, $url_options);
+          break;
+
+        case 'useragent':
+          $replacements[$original] = $feedback->useragent;
+          break;
+
+        case 'message':
+          $replacements[$original] = $sanitize ? check_plain($feedback->message) : $feedback->message;
+          break;
+
+        // Default values for the chained tokens handled below.
+        case 'author':
+          if ($feedback->uid == 0) {
+            $name = variable_get('anonymous', t('Anonymous'));
+          }
+          else {
+            $account = user_load($feedback->uid == 0);
+            $name = $account->name;
+          }
+          $replacements[$original] = $sanitize ? filter_xss($name) : $name;
+          break;
+      }
+    }
+
+    if ($author_tokens = token_find_with_prefix($tokens, 'author')) {
+      $author = user_load($feedback->uid);
+      $replacements += token_generate('user', $author_tokens, array('user' => $author), $options);
+    }
+  }
+
+  return $replacements;
+}
