diff --git a/README.txt b/README.txt
index 7adb34a..36020f1 100644
--- a/README.txt
+++ b/README.txt
@@ -1,9 +1,9 @@
 Description
 -----------
 This module adds a webform content type to your Drupal site.
-A webform can be a questionnaire, contact or request form. These can be used 
+A webform can be a questionnaire, contact or request form. These can be used
 by visitor to make contact or to enable a more complex survey than polls
-provide. Submissions from a webform are saved in a database table and 
+provide. Submissions from a webform are saved in a database table and
 can optionally be mailed to e-mail addresses upon submission.
 
 Requirements
diff --git a/includes/webform.emails.inc b/includes/webform.emails.inc
index 61582b5..e468fae 100644
--- a/includes/webform.emails.inc
+++ b/includes/webform.emails.inc
@@ -284,7 +284,8 @@ function webform_email_edit_form($form, $form_state, $node, $email = array()) {
   );
 
   $form['template']['tokens'] = array(
-    '#markup' => theme('webform_token_help', array('groups' => 'all')),
+    '#theme' => 'token_tree',
+    '#token_types' => array('webform-submission'),
   );
 
   $form['template']['components'] = array(
diff --git a/webform.module b/webform.module
index 09397df..bbab42c 100644
--- a/webform.module
+++ b/webform.module
@@ -2650,6 +2650,18 @@ function _webform_filter_values($string, $node = NULL, $submission = NULL, $emai
     return $string;
   }
 
+  //First, let the token api have at it
+  $token_data = array(
+    'user' => $user,
+  );
+  if ($node) {
+    $token_data['node'] = $node;
+  }
+  if ($submission) {
+    $token_data['webform-submission'] = $submission;
+  }
+  $string = token_replace($string, $token_data);
+
   // Setup default token replacements.
   if (!isset($replacements)) {
     $replacements['unsafe'] = array();
@@ -2986,10 +2998,15 @@ function theme_webform_token_help($variables) {
     '#type' => 'fieldset',
     '#collapsible' => TRUE,
     '#collapsed' => TRUE,
-    '#children' => '<div>' . $output . '</div>',
     '#attributes' => array('class' => array('collapsible', 'collapsed')),
   );
-  return theme('fieldset', array('element' => $fieldset));
+
+  $fieldset['token_tree'] = array(
+    '#theme' => 'token_tree',
+    '#token_types' => array('node'),
+  );
+
+  return render($fieldset);
 }
 
 function _webform_safe_name($name) {
diff --git a/webform.tokens.inc b/webform.tokens.inc
new file mode 100644
index 0000000..50fd128
--- /dev/null
+++ b/webform.tokens.inc
@@ -0,0 +1,134 @@
+<?php
+
+/**
+ * @file
+ * Builds placeholder replacement tokens for webform-related data.
+ */
+
+/**
+ * Implements hook_token_info().
+ */
+function webform_token_info() {
+  // Webform submission tokens.
+  $info['types']['webform-submission'] = array(
+    'name' => t('Webform node'),
+    'description' => t('Tokens related to webform submissions.'),
+    'needs-data' => 'webform-submission',
+  );
+  $info['tokens']['webform-submission']['sid'] = array(
+    'name' => t('Submission ID'),
+    'description' => t('The unique indentifier for the webform submission.'),
+  );
+  $info['tokens']['webform-submission']['node'] = array(
+    'name' => t('Node'),
+    'description' => t('The webform content that the submission came from.'),
+    'type' => 'node',
+  );
+  $info['tokens']['webform-submission']['date'] = array(
+    'name' => t('Date submitted'),
+    'description' => t('The date the webform was submitted.'),
+    'type' => 'date',
+  );
+  $info['tokens']['webform-submission']['ip-address'] = array(
+    'name' => t('IP address'),
+    'description' => t('The IP address that was used when submitting the webform.'),
+  );
+  $info['tokens']['webform-submission']['user'] = array(
+    'name' => t('Submitter'),
+    'description' => t('The user that submitted the webform result.'),
+    'type' => 'user',
+  );
+  $info['tokens']['webform-submission']['url'] = array(
+    'name' => t('URL'),
+    'description' => t('Webform tokens related to URLs.'),
+    'type' => 'url',
+  );
+  $info['tokens']['webform-submission']['submission'] = array(
+    'name' => t('Webform submission component value'),
+    'description' => t('Webform tokens from submitted data. Replace the "?" with the "Field Key" of the component that contains the appropriate values.'),
+    'dynamic' => TRUE,
+  );
+  
+  return $info;
+}
+
+/**
+ * Implements hook_tokens().
+ */
+function webform_tokens($type, $tokens, array $data = array(), array $options = array()) {
+  $replacements = array();
+
+  $url_options = array('absolute' => TRUE);
+  if (isset($options['language'])) {
+    $url_options['language'] = $options['language'];
+    $language_code = $options['language']->language;
+  }
+  else {
+    $language_code = NULL;
+  }
+
+  $sanitize = !empty($options['sanitize']);
+
+  // Webform tokens  (caching globally)
+  if ($type == 'webform-submission' && !empty($data['webform-submission'])) {
+    $submission = $data['webform-submission'];
+
+    foreach ($tokens as $name => $original) {
+      switch ($name) {
+        case 'sid':
+          $replacements[$original] = $submission->sid;
+          break;
+        case 'node':
+          $node = node_load($submission->nid);
+          $replacements[$original] = $sanitize ? check_plain($node->title) : $node->title;
+          break;
+        case 'date':
+          $replacements[$original] = format_date($submission->submitted, 'medium', '', NULL, $language_code);
+          break;
+        case 'ip-address':
+          $replacements[$original] = $sanitize ? check_plain($submission->remote_addr) : $submission->remote_addr;
+          break;
+        case 'user':
+          $account = user_load($submission->uid);
+          $name = format_username($account);
+          $replacements[$original] = $sanitize ? check_plain($name) : $name;
+          break;
+        case 'url':
+          $replacements[$original] = url("node/{$submission->nid}/submission/{$submission->sid}", $url_options);
+          break;
+        case 'edit-url':
+          $replacements[$original] = url("node/{$submission->nid}/submission/{$submission->sid}/edit", $url_options);
+          break;
+        default: // Webform submission tokens (caching per webform node)
+          $split = explode(':', $name);
+          $skey = $split[1];
+          $node = node_load($submission->nid);
+          foreach ($node->webform['components'] as $ckey => $cvalue) {
+            if ($cvalue['name'] == $skey) {
+              $replacements[$original] = $submission->data[$ckey]['value'][0];
+            }
+            else {
+              continue;
+            }
+          }
+          break;
+      }
+    }
+
+    // Chained token relationships.
+    if (($node_tokens = token_find_with_prefix($tokens, 'node')) && $node = node_load($submission->nid)) {
+      $replacements = token_generate('node', $node_tokens, array('node' => $node), $options);
+    }
+    if ($date_tokens = token_find_with_prefix($tokens, 'date')) {
+      $replacements = token_generate('date', $date_tokens, array('date' => $submission->submitted), $options);
+    }
+    if (($user_tokens = token_find_with_prefix($tokens, 'user')) && $account = user_load($submission->uid)) {
+      $replacements = token_generate('user', $user_tokens, array('user' => $account), $options);
+    }
+    if ($url_tokens = token_find_with_prefix($tokens, 'url')) {
+      $replacements = token_generate('url', $url_tokens, array('path' => url("node/{$submission->nid}/submission/{$submission->sid}", $url_options)), $options);
+    }
+  }
+
+  return $replacements;
+}
