diff --git a/include/ajax.filter.php b/include/ajax.filter.php
index 7c34b54428e4f1078ef2f210be1bc5c2f82ce3c4..5ef5ec82cc95fbec05d727dfdae8ba071ba32af3 100644
--- a/include/ajax.filter.php
+++ b/include/ajax.filter.php
@@ -9,7 +9,20 @@ class FilterAjaxAPI extends AjaxController {
             Http::response(404, 'No such filter action type');
 
         $form = $A->getConfigurationForm();
+        ?>
+        <div style="position:relative">
+            <div class="pull-right" style="position:absolute;top:2px;right:2px;">
+                <a href="#" title="<?php echo __('clear'); ?>" onclick="javascript:
+        if (!confirm(__('You sure?')))
+            return false;
+        $(this).closest('tr').fadeOut(400, function() { $(this).hide(); });
+        return false;"><i class="icon-trash"></i></a>
+            </div>
+        <?php
         include STAFFINC_DIR . 'templates/dynamic-form-simple.tmpl.php';
+        ?>
+        </div>
+        <?php
     }
 
 }
diff --git a/include/class.email.php b/include/class.email.php
index ab0727f7cbf4c5cf941ebbf1893fc6873f6eb79f..31c3dda775dd5f0b8aef0c6300e3a03f6f088add 100644
--- a/include/class.email.php
+++ b/include/class.email.php
@@ -59,6 +59,19 @@ class EmailModel extends VerySimpleModel {
     static function getPermissions() {
         return self::$perms;
     }
+
+    static function getAddresses($options=array()) {
+        $objects = static::objects();
+        if ($options['smtp'])
+            $objects = $objects->filter(array('smtp_active'=>true));
+
+        $addresses = array();
+        foreach ($objects->values_flat('email_id', 'email') as $row) {
+            list($id, $email) = $row;
+            $addresses[$id] = $email;
+        }
+        return $addresses;
+    }
 }
 
 RolePermission::register(/* @trans */ 'Miscellaneous', EmailModel::getPermissions());
diff --git a/include/class.filter.php b/include/class.filter.php
index ad3f6c043b2cb55c0d40b9779d084ed899653d5e..7a2ced7e2d7012b0980ba0940ea5a6d2e03f7c38 100644
--- a/include/class.filter.php
+++ b/include/class.filter.php
@@ -305,12 +305,17 @@ class Filter {
      * If the matches() method returns TRUE, send the initial ticket to this
      * method to apply the filter actions defined
      */
-    function apply(&$ticket, $info=null) {
+    function apply(&$ticket, $vars) {
         foreach ($this->getActions() as $a) {
-            $a->apply($ticket, $info);
+            $a->setFilter($this);
+            $a->apply($ticket, $vars);
         }
     }
 
+    function getVars() {
+        return $this->vars;
+    }
+
     static function getSupportedMatches() {
         foreach (static::$match_types as $k=>&$v) {
             if (is_callable($v[0]))
@@ -537,13 +542,14 @@ class Filter {
         if (!is_array(@$vars['actions']))
             return;
 
-        foreach ($vars['actions'] as $v) {
+        foreach ($vars['actions'] as $sort=>$v) {
             $info = substr($v, 1);
             switch ($v[0]) {
             case 'N': # new filter action
                 $I = FilterAction::create(array(
                     'type'=>$info,
                     'filter_id'=>$id,
+                    'sort' => (int) $sort,
                 ));
                 $I->setConfiguration($errors, $vars);
                 $I->save();
@@ -551,6 +557,7 @@ class Filter {
             case 'I': # exiting filter action
                 if ($I = FilterAction::lookup($info)) {
                     $I->setConfiguration($errors, $vars);
+                    $I->sort = (int) $sort;
                     $I->save();
                 }
                 break;
diff --git a/include/class.filter_action.php b/include/class.filter_action.php
index 04dbcc16b4558782a005746adf5595ce5b245c2a..f2367bc77b1f63e5a1d5eeb11a6d7b35af3b2013 100644
--- a/include/class.filter_action.php
+++ b/include/class.filter_action.php
@@ -10,14 +10,23 @@ class FilterAction extends VerySimpleModel {
     );
 
     static $registry = array();
+    static $registry_group = array();
 
     var $_impl;
     var $_config;
+    var $_filter;
 
     function getId() {
         return $this->id;
     }
 
+    function setFilter($filter) {
+        $this->_filter = $filter;
+    }
+    function getFilter() {
+        return $this->_filter;
+    }
+
     function getConfiguration() {
         if (!$this->_config) {
             $this->_config = $this->get('configuration');
@@ -36,6 +45,8 @@ class FilterAction extends VerySimpleModel {
         $config = array();
         foreach ($this->getImpl()->getConfigurationForm($source ?: $_POST)
                 ->getFields() as $name=>$field) {
+            if (!$field->hasData())
+                continue;
             $config[$name] = $field->to_php($field->getClean());
             $errors = array_merge($errors, $field->errors());
         }
@@ -64,9 +75,14 @@ class FilterAction extends VerySimpleModel {
         return parent::save($refetch || $this->dirty);
     }
 
-    static function register($class, $type=false) {
-        // TODO: Check if $class implements TriggerAction
-        self::$registry[$type ?: $class::$type] = $class;
+    static function register($class, $group=false) {
+        if (!$class::$type)
+            throw new Exception('Filter actions must specify ::$type');
+        elseif (!is_subclass_of($class, 'TriggerAction'))
+            throw new Exception('Filter actions must extend from TriggerAction');
+
+        self::$registry[$class::$type] = $class;
+        self::$registry_group[$group ?: ''][$class::$type] = $class;
     }
 
     static function lookupByType($type, $thisObj=false) {
@@ -77,16 +93,26 @@ class FilterAction extends VerySimpleModel {
         return new $class($thisObj);
     }
 
-    static function allRegistered() {
+    static function allRegistered($group=false) {
         $types = array();
-        foreach (self::$registry as $type=>$class) {
-            $types[$type] = $class::getName();
+        foreach (self::$registry_group as $group=>$actions) {
+            $G = $group ? __($group) : '';
+            foreach ($actions as $type=>$class) {
+                $types[$G][$type] = __($class::getName());
+            }
         }
         return $types;
     }
 }
 
 abstract class TriggerAction {
+    static $type = false;
+    static $flags = 0;
+
+    const FLAG_MULTI_USE    = 0x0001;   // Action can be used multiple times
+
+    var $action;
+
     function __construct($action=false) {
         $this->action = $action;
     }
@@ -125,6 +151,10 @@ abstract class TriggerAction {
         return $this->_cform;
     }
 
+    function hasFlag($flag) {
+        return static::$flags & $flag > 0;
+    }
+
     static function getType() { return static::$type; }
     static function getName() { return __(static::$name); }
 
@@ -134,10 +164,10 @@ abstract class TriggerAction {
 
 class FA_RejectTicket extends TriggerAction {
     static $type = 'reject';
-    static $name = /* trans */ 'Reject Ticket';
+    static $name = /* @trans */ 'Reject Ticket';
 
     function apply(&$ticket, array $info) {
-        throw new RejectedException($filter, $ticket);
+        throw new RejectedException($this->action->getFilter(), $ticket);
     }
 
     function getConfigurationOptions() {
@@ -151,11 +181,11 @@ class FA_RejectTicket extends TriggerAction {
         );
     }
 }
-FilterAction::register('FA_RejectTicket');
+FilterAction::register('FA_RejectTicket', /* @trans */ 'Ticket');
 
 class FA_UseReplyTo extends TriggerAction {
     static $type = 'replyto';
-    static $name = /* trans */ 'Reply-To Email';
+    static $name = /* @trans */ 'Use Reply-To Email';
 
     function apply(&$ticket, array $info) {
         $config = $this->getConfiguration();
@@ -168,19 +198,19 @@ class FA_UseReplyTo extends TriggerAction {
 
     function getConfigurationOptions() {
         return array(
-            'enable' => new BooleanField(array(
+            '' => new FreeTextField(array(
                 'configuration' => array(
-                    'desc' => __('Use the Reply-To email header')
+                    'content' => __('<strong>Use</strong> the Reply-To email header')
                 )
             )),
         );
     }
 }
-FilterAction::register('FA_UseReplyTo');
+FilterAction::register('FA_UseReplyTo', /* @trans */ 'Communication');
 
 class FA_DisableAutoResponse extends TriggerAction {
     static $type = 'noresp';
-    static $name = /* trans */ "Ticket auto-response";
+    static $name = /* @trans */ "Disable autoresponse";
 
     function apply(&$ticket, array $info) {
         # TODO: Disable alerting
@@ -193,19 +223,19 @@ class FA_DisableAutoResponse extends TriggerAction {
 
     function getConfigurationOptions() {
         return array(
-            'enable' => new BooleanField(array(
+            '' => new FreeTextField(array(
                 'configuration' => array(
-                    'desc' => __('<strong>Disable</strong> auto-response')
+                    'content' => __('<strong>Disable</strong> new ticket auto-response')
                 ),
             )),
         );
     }
 }
-FilterAction::register('FA_DisableAutoResponse');
+FilterAction::register('FA_DisableAutoResponse', /* @trans */ 'Communication');
 
 class FA_AutoCannedResponse extends TriggerAction {
     static $type = 'canned';
-    static $name = /* trans */ "Canned Response";
+    static $name = /* @trans */ "Attach Canned Response";
 
     function apply(&$ticket, array $info) {
         $config = $this->getConfiguration();
@@ -232,11 +262,11 @@ class FA_AutoCannedResponse extends TriggerAction {
         );
     }
 }
-FilterAction::register('FA_AutoCannedResponse');
+FilterAction::register('FA_AutoCannedResponse', /* @trans */ 'Communication');
 
 class FA_RouteDepartment extends TriggerAction {
     static $type = 'dept';
-    static $name = /* trans */ 'Department';
+    static $name = /* @trans */ 'Set Department';
 
     function apply(&$ticket, array $info) {
         $config = $this->getConfiguration();
@@ -245,26 +275,19 @@ class FA_RouteDepartment extends TriggerAction {
     }
 
     function getConfigurationOptions() {
-        $sql='SELECT dept_id,dept_name FROM '.DEPT_TABLE.' dept ORDER by dept_name';
-        $choices = array();
-        if(($res=db_query($sql)) && db_num_rows($res)){
-            while(list($id,$name)=db_fetch_row($res)){
-                $choices[$id] = $name;
-            }
-        }
         return array(
             'dept_id' => new ChoiceField(array(
                 'configuration' => array('prompt' => __('Unchanged')),
-                'choices' => $choices,
+                'choices' => Dept::getDepartments(),
             )),
         );
     }
 }
-FilterAction::register('FA_RouteDepartment');
+FilterAction::register('FA_RouteDepartment', /* @trans */ 'Ticket');
 
 class FA_AssignPriority extends TriggerAction {
     static $type = 'pri';
-    static $name = /* trans */ "Priority";
+    static $name = /* @trans */ "Set Priority";
 
     function apply(&$ticket, array $info) {
         $config = $this->getConfiguration();
@@ -288,11 +311,11 @@ class FA_AssignPriority extends TriggerAction {
         );
     }
 }
-FilterAction::register('FA_AssignPriority');
+FilterAction::register('FA_AssignPriority', /* @trans */ 'Ticket');
 
 class FA_AssignSLA extends TriggerAction {
     static $type = 'sla';
-    static $name = /* trans */ 'SLA Plan';
+    static $name = /* @trans */ 'Set SLA Plan';
 
     function apply(&$ticket, array $info) {
         $config = $this->getConfiguration();
@@ -310,11 +333,11 @@ class FA_AssignSLA extends TriggerAction {
         );
     }
 }
-FilterAction::register('FA_AssignSLA');
+FilterAction::register('FA_AssignSLA', /* @trans */ 'Ticket');
 
 class FA_AssignTeam extends TriggerAction {
     static $type = 'team';
-    static $name = /* trans */ 'Assign Team';
+    static $name = /* @trans */ 'Assign Team';
 
     function apply(&$ticket, array $info) {
         $config = $this->getConfiguration();
@@ -323,15 +346,7 @@ class FA_AssignTeam extends TriggerAction {
     }
 
     function getConfigurationOptions() {
-        $sql='SELECT team_id, isenabled, name FROM '.TEAM_TABLE .' ORDER BY name';
-        $choices = array();
-        if(($res=db_query($sql)) && db_num_rows($res)){
-            while (list($id, $isenabled, $name) = db_fetch_row($res)){
-                if (!$isenabled)
-                    $name .= ' '.__('(disabled)');
-                $choices[$id] = $name;
-            }
-        }
+        $choices = Team::getTeams();
         return array(
             'team_id' => new ChoiceField(array(
                 'configuration' => array('prompt' => __('Unchanged')),
@@ -340,11 +355,11 @@ class FA_AssignTeam extends TriggerAction {
         );
     }
 }
-FilterAction::register('FA_AssignTeam');
+FilterAction::register('FA_AssignTeam', /* @trans */ 'Ticket');
 
 class FA_AssignAgent extends TriggerAction {
     static $type = 'agent';
-    static $name = /* trans */ 'Assign Agent';
+    static $name = /* @trans */ 'Assign Agent';
 
     function apply(&$ticket, array $info) {
         $config = $this->getConfiguration();
@@ -362,11 +377,11 @@ class FA_AssignAgent extends TriggerAction {
         );
     }
 }
-FilterAction::register('FA_AssignAgent');
+FilterAction::register('FA_AssignAgent', /* @trans */ 'Ticket');
 
 class FA_AssignTopic extends TriggerAction {
     static $type = 'topic';
-    static $name = /* trans */ 'Help Topic';
+    static $name = /* @trans */ 'Set Help Topic';
 
     function apply(&$ticket, array $info) {
         $config = $this->getConfiguration();
@@ -384,11 +399,11 @@ class FA_AssignTopic extends TriggerAction {
         );
     }
 }
-FilterAction::register('FA_AssignTopic');
+FilterAction::register('FA_AssignTopic', /* @trans */ 'Ticket');
 
 class FA_SetStatus extends TriggerAction {
     static $type = 'status';
-    static $name = /* trans */ 'Ticket Status';
+    static $name = /* @trans */ 'Set Ticket Status';
 
     function apply(&$ticket, array $info) {
         $config = $this->getConfiguration();
@@ -413,11 +428,12 @@ class FA_SetStatus extends TriggerAction {
         );
     }
 }
-FilterAction::register('FA_SetStatus');
+FilterAction::register('FA_SetStatus', /* @trans */ 'Ticket');
 
 class FA_SendEmail extends TriggerAction {
     static $type = 'email';
-    static $name = /* trans */ 'Send an Email';
+    static $name = /* @trans */ 'Send an Email';
+    static $flags = TriggerAction::FLAG_MULTI_USE;
 
     function apply(&$ticket, array $info) {
         global $ost;
@@ -428,21 +444,50 @@ class FA_SendEmail extends TriggerAction {
         $info = $ost->replaceTemplateVariables(
             $info, array('ticket' => $ticket)
         );
-        $mailer = new Mailer();
-        $mailer->send($config['recipients'], $info['subject'], $info['message']);
+
+        // Honor FROM address settings
+        if (!$config['from'] || !($mailer = Email::lookup($config['from'])))
+            $mailer = new Mailer();
+
+        // Honor %{user} variable
+        $to = $config['recipients'];
+        $replacer = new VariableReplacer();
+        $replacer->assign(array(
+            'user' => sprintf('%s <%s>', $ticket['name'], $ticket['email'])
+        ));
+        $to = $replacer->replaceVars($to);
+
+        $mailer->send($to, $info['subject'], $info['message']);
     }
 
     function getConfigurationOptions() {
+        $choices = array('' => __('Default System Email'));
+        $choices += EmailModel::getAddresses();
+
         return array(
             'recipients' => new TextboxField(array(
                 'label' => __('Recipients'), 'required' => true,
                 'configuration' => array(
-                    'size' => 80
+                    'size' => 80, 'length' => 1000,
                 ),
                 'validators' => function($self, $value) {
-                    if (!($q=Validator::is_email($value, true)))
+                    if (!($mails = Mail_RFC822::parseAddressList($value)) || PEAR::isError($mails))
                         $self->addError('Unable to parse address list. '
                             .'Use commas to separate addresses.');
+
+                    $valid = array('user',);
+                    foreach ($mails as $M) {
+                        // Check placeholders like '%{user}'
+                        $P = array();
+                        if (preg_match('`%\{([^}]+)\}`', $M->mailbox, $P)) {
+                            if (!in_array($P[1], $valid))
+                                $self->addError(sprintf('%s: Not a valid variable', $P[0]));
+                        }
+                        elseif ($M->host == 'localhost' || !$M->mailbox) {
+                            $self->addError(sprintf(__('%s: Not a valid email address'),
+                                $M->mailbox . '@' . $M->host));
+                        }
+                    }
                 }
             )),
             'subject' => new TextboxField(array(
@@ -457,7 +502,12 @@ class FA_SendEmail extends TriggerAction {
                     'html' => true,
                 ),
             )),
+            'from' => new ChoiceField(array(
+                'label' => __('From Email'),
+                'choices' => $choices,
+                'default' => '',
+            )),
         );
     }
 }
-FilterAction::register('FA_SendEmail');
+FilterAction::register('FA_SendEmail', /* @trans */ 'Communication');
diff --git a/include/client/open.inc.php b/include/client/open.inc.php
index 71529516575d3d8b1dbea2d99ac4b7898dea07b4..e29f2b651be929de68e8d50f759e7bef41c28c33 100644
--- a/include/client/open.inc.php
+++ b/include/client/open.inc.php
@@ -82,6 +82,15 @@ if ($info['topicId'] && ($topic=Topic::lookup($info['topicId']))) {
     </tbody>
     <tbody id="dynamic-form">
         <?php foreach ($forms as $form) {
+            $hasFields = false;
+            foreach ($form->getFields() as $f) {
+                if ($f->isVisibleToUsers()) {
+                    $hasFields = true;
+                    break;
+                }
+            }
+            if (!$hasFields)
+                continue;
             include(CLIENTINC_DIR . 'templates/dynamic-form.tmpl.php');
         } ?>
     </tbody>
diff --git a/include/staff/filter.inc.php b/include/staff/filter.inc.php
index 592289355651dd91621112dcd086033aad453fa2..61e563bc077c5ec1a4eeef94191e6408ab981b67 100644
--- a/include/staff/filter.inc.php
+++ b/include/staff/filter.inc.php
@@ -167,18 +167,23 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
         } ?>
         <tr>
             <th colspan="2">
-                <em><strong><?php echo __('Filter Actions');?></strong>: <?php
-                echo __('Can be overwridden by other filters depending on processing order.');?>&nbsp;</em>
+                <em><strong><?php echo __('Filter Actions');?></strong>:
+                <div><?php
+                    echo __('Can be overwridden by other filters depending on processing order.');
+                ?><br/><?php
+                    echo __('Actions are executed in the order declared below');
+                ?></em>
             </th>
         </tr>
     </tbody>
-    <tbody id="dynamic-actions">
+    <tbody id="dynamic-actions" class="sortable-rows">
 <?php
 $existing = array();
 if ($filter) { foreach ($filter->getActions() as $A) {
     $existing[] = $A->type;
 ?>
-        <tr><td><?php echo $A->getImpl()->getName(); ?>:</td>
+        <tr style="background-color:white"><td><i class="icon-bolt icon-large icon-muted"></i>
+            <?php echo $A->getImpl()->getName(); ?>:</td>
             <td><div style="position:relative"><?php
                 $form = $A->getImpl()->getConfigurationForm($_POST ?: false);
                 // XXX: Drop this when the ORM supports proper caching
@@ -202,23 +207,34 @@ if ($filter) { foreach ($filter->getActions() as $A) {
     </tbody>
     <tbody>
         <tr>
-            <td><strong>
+            <td><strong><i class="icon-plus-sign"></i>
                 <?php echo __('Add'); ?>:
             </strong></td>
             <td>
                 <select name="new-action" id="new-action-select"
                     onchange="javascript: $('#new-action-btn').trigger('click');">
                     <option value=""><?php echo __('— Select an Action —'); ?></option>
-<?php foreach (FilterAction::allRegistered() as $type=>$name) {
-    if (in_array($type, $existing))
-        continue;
+<?php
+$current_group = '';
+foreach (FilterAction::allRegistered() as $group=>$actions) {
+    if ($group && $current_group != $group) {
+        if ($current_group) echo '</optgroup>';
+        $current_group = $group;
+        ?><optgroup label="<?php echo Format::htmlchars($group); ?>"><?php
+    }
+    foreach ($actions as $type=>$name) {
 ?>
-                    <option data-title="<?php echo $name; ?>" value="<?php echo $type; ?>"><?php echo $name; ?></option>
-<?php } ?>
+                <option data-title="<?php echo $name; ?>" value="<?php echo $type; ?>"
+                    data-multi-use="<?php echo $mu = FilterAction::lookupByType($type)->hasFlag(TriggerAction::FLAG_MULTI_USE); ?> " <?php
+                    if (in_array($type, $existing) && !$mu) echo 'disabled="disabled"';
+                ?>><?php echo $name; ?></option>
+<?php }
+} ?>
                 </select>
                 <input id="new-action-btn" type="button" value="<?php echo __('Add'); ?>"
                 onclick="javascript:
-        var selected = $('#new-action-select').find(':selected');
+        var dropdown = $('#new-action-select'), selected = dropdown.find(':selected');
+        dropdown.val('');
         $('#dynamic-actions')
           .append($('<tr></tr>')
             .append($('<td></td>')
@@ -226,7 +242,7 @@ if ($filter) { foreach ($filter->getActions() as $A) {
             ).append($('<td></td>')
               .append($('<em></em>').text(__('Loading ...')))
               .load('ajax.php/filter/action/' + selected.val() + '/config', function() {
-                selected.prop('disabled', true);
+                if (!selected.data('multiUse')) selected.prop('disabled', true);
               })
             )
           ).append(
@@ -254,3 +270,12 @@ if ($filter) { foreach ($filter->getActions() as $A) {
     <input type="button" name="cancel" value="<?php echo __('Cancel');?>" onclick='window.location.href="filters.php"'>
 </p>
 </form>
+<script type="text/javascript">
+   var fixHelper = function(e, ui) {
+      ui.children().each(function() {
+          $(this).width($(this).width());
+      });
+      return ui;
+   };
+   $('#dynamic-actions').sortable({helper: fixHelper, opacity: 0.5});
+</script>
diff --git a/include/staff/templates/dynamic-form-simple.tmpl.php b/include/staff/templates/dynamic-form-simple.tmpl.php
index 896805c45c48f6ace493b9786f0abd00e804cbc7..1c31b8fed135211e77fe932922e44f989cabaeda 100644
--- a/include/staff/templates/dynamic-form-simple.tmpl.php
+++ b/include/staff/templates/dynamic-form-simple.tmpl.php
@@ -30,4 +30,3 @@
             </div>
         <?php }
         ?>
-    </form>
diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php
index 50750cdd0df2c3a7c5d64ea88c83e86bba5c67a7..e9edb4643ade0c9aa8dbf48f3b3c498c641a4ddc 100644
--- a/include/staff/ticket-open.inc.php
+++ b/include/staff/ticket-open.inc.php
@@ -263,6 +263,15 @@ if ($_POST)
         <tbody id="dynamic-form">
         <?php
             foreach ($forms as $form) {
+                $hasFields = false;
+                foreach ($form->getFields() as $f) {
+                    if ($f->isVisibleToStaff()) {
+                        $hasFields = true;
+                        break;
+                    }
+                }
+                if (!$hasFields)
+                    continue;
                 print $form->getForm()->getMedia();
                 include(STAFFINC_DIR .  'templates/dynamic-form.tmpl.php');
             }
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 01dc05cf9506c278ec4b73b0bd552147e6172e01..ebb60c9ba34d2b21761a3fdb59f148524e7ef8cc 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -1490,12 +1490,12 @@ time {
     border:none;
 }
 
-.dialog .custom-field .field-label {
+.custom-field .field-label {
     margin-left: 3px;
     margin-right: 3px;
 }
-.dialog .custom-field + .custom-field {
-    margin-top: 8px;
+.custom-field + .custom-field {
+    margin-top: 5px;
 }
 .dialog label.fixed-size {
     width:100px;
@@ -2137,3 +2137,7 @@ td.indented {
 #topic-forms tbody + tbody td.handle {
   padding-top: 15px;
 }
+
+#dynamic-actions > tr > td {
+    padding: 5px;
+}