diff --git a/css/thread.css b/css/thread.css
index 2001dde95a01ad6761be77a93eadd390544f494d..6fe012353f3137d0629f15d4ac73667694460c16 100644
--- a/css/thread.css
+++ b/css/thread.css
@@ -467,9 +467,6 @@ table.thread-entry {
 table.thread-entry th div span {
     vertical-align: middle;
 }
-table.thread-entry th div :not(.title) {
-    font-weight: 600;
-}
 table.thread-entry th div .title {
     font-weight: 400;
 }
diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index 96518633461427d866094de07ea98e176368ea45..eb7ff09bc8a547acbcbff29b615ad2975ca3dd73 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -19,6 +19,7 @@ if(!defined('INCLUDE_DIR')) die('403');
 include_once(INCLUDE_DIR.'class.ticket.php');
 require_once(INCLUDE_DIR.'class.ajax.php');
 require_once(INCLUDE_DIR.'class.note.php');
+include_once INCLUDE_DIR . 'class.thread_actions.php';
 
 class TicketsAjaxAPI extends AjaxController {
 
@@ -783,6 +784,25 @@ class TicketsAjaxAPI extends AjaxController {
         return self::_changeSelectedTicketsStatus($state, $info, $errors);
     }
 
+    function triggerThreadAction($ticket_id, $thread_id, $action) {
+        $thread = ThreadEntry::lookup($thread_id, $ticket_id);
+        if (!$thread)
+            Http::response(404, 'No such ticket thread entry');
+
+        $valid = false;
+        foreach ($thread->getActions() as $group=>$list) {
+            foreach ($list as $name=>$A) {
+                if ($A->getId() == $action) {
+                    $valid = true; break;
+                }
+            }
+        }
+        if (!$valid)
+            Http::response(400, 'Not a valid action for this thread');
+
+        $thread->triggerAction($action);
+    }
+
     private function _changeSelectedTicketsStatus($state, $info=array(), $errors=array()) {
 
         $count = $_REQUEST['count'] ?:
diff --git a/include/class.file.php b/include/class.file.php
index caafae2bf9cab7018b6aee5d4a8503d2f7d84295..82b998a783e969b70918399f7559d0dd78d69f7c 100644
--- a/include/class.file.php
+++ b/include/class.file.php
@@ -208,8 +208,10 @@ class AttachmentFile {
         if ($bk->sendRedirectUrl('inline'))
             return;
         $this->makeCacheable();
-        Http::download($this->getName(), $this->getType() ?: 'application/octet-stream',
-            null, 'inline');
+        $type = $this->getType() ?: 'application/octet-stream';
+        if (isset($_REQUEST['overridetype']))
+            $type = $_REQUEST['overridetype'];
+        Http::download($this->getName(), $type, null, 'inline');
         header('Content-Length: '.$this->getSize());
         $this->sendData(false);
         exit();
diff --git a/include/class.thread.php b/include/class.thread.php
index 55273469ce5624f7306149c6761cc0c508a5063b..724471ed761666687721b266ab1243f9b4dfd2da 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -245,6 +245,7 @@ class ThreadEntry {
 
     var $thread;
     var $attachments;
+    var $_actions;
 
     function ThreadEntry($id, $threadId=0, $type='') {
         $this->load($id, $threadId, $type);
@@ -799,7 +800,7 @@ class ThreadEntry {
     function lookupByEmailHeaders(&$mailinfo, &$seen=false) {
         // Search for messages using the References header, then the
         // in-reply-to header
-        $search = 'SELECT thread_entery_id, mid FROM '.THREAD_ENTRY_EMAIL_TABLE
+        $search = 'SELECT thread_entry_id, mid FROM '.THREAD_ENTRY_EMAIL_TABLE
                . ' WHERE mid=%s '
                . ' ORDER BY thread_entry_id DESC';
 
@@ -1079,6 +1080,65 @@ class ThreadEntry {
     static function add($vars) {
         return ($entry=self::create($vars)) ? $entry->getId() : 0;
     }
+
+    // Extensible thread entry actions ------------------------
+    /**
+     * getActions
+     *
+     * Retrieve a list of possible actions. This list is shown to the agent
+     * via drop-down list at the top-right of the thread entry when rendered
+     * in the UI.
+     */
+    function getActions() {
+        if (!isset($this->_actions)) {
+            $this->_actions = array();
+
+            foreach (self::$action_registry as $group=>$list) {
+                $T = array();
+                $this->_actions[__($group)] = &$T;
+                foreach ($list as $id=>$action) {
+                    $A = new $action($this);
+                    if ($A->isVisible()) {
+                        $T[$id] = $A;
+                    }
+                }
+                unset($T);
+            }
+        }
+        return $this->_actions;
+    }
+
+    function hasActions() {
+        foreach ($this->getActions() as $group => $list) {
+            if (count($list))
+                return true;
+        }
+        return false;
+    }
+
+    function triggerAction($name) {
+        foreach ($this->getActions() as $group=>$list) {
+            foreach ($list as $id=>$action) {
+                if (0 === strcasecmp($id, $name)) {
+                    if (!$action->isEnabled())
+                        return false;
+
+                    $action->trigger();
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    static $action_registry = array();
+
+    static function registerAction($group, $action) {
+        if (!isset(self::$action_registry[$group]))
+            self::$action_registry[$group] = array();
+
+        self::$action_registry[$group][$action::getId()] = $action;
+    }
 }
 
 
@@ -1253,7 +1313,7 @@ class HtmlThreadEntryBody extends ThreadEntryBody {
 
     function getSearchable() {
         // <br> -> \n
-        $body = preg_replace(array('`<br(\s*)?/?>`i', '`</div>`i'), "\n", $this->body);
+        $body = preg_replace(array('`<br(\s*)?/?>`i', '`</div>`i'), "\n", $this->body); # <?php
         $body = Format::htmldecode(Format::striptags($body));
         return Format::searchable($body);
     }
@@ -1558,4 +1618,80 @@ class TicketThread extends ObjectThread {
                     ));
     }
 }
+
+/**
+ * Class: ThreadEntryAction
+ *
+ * Defines a simple action to be performed on a thread entry item, such as
+ * viewing the raw email headers used to generate the message, resend the
+ * confirmation emails, etc.
+ */
+abstract class ThreadEntryAction {
+    static $name;               // Friendly, translatable name
+    static $id;                 // Unique identifier used for plumbing
+    static $icon = 'cog';
+
+    var $thread;
+
+    function getName() {
+        $class = get_class($this);
+        return __($class::$name);
+    }
+
+    static function getId() {
+        return static::$id;
+    }
+
+    function getIcon() {
+        $class = get_class($this);
+        return 'icon-' . $class::$icon;
+    }
+
+    function __construct(ThreadEntry $thread) {
+        $this->thread = $thread;
+    }
+
+    abstract function trigger();
+
+    function getTicket() {
+        return $this->thread->getTicket();
+    }
+
+    function isEnabled() {
+        return $this->isVisible();
+    }
+    function isVisible() {
+        return true;
+    }
+
+    /**
+     * getJsStub
+     *
+     * Retrieves a small JavaScript snippet to insert into the rendered page
+     * which should, via an AJAX callback, trigger this action to be
+     * performed. The URL for this sort of activity is already provided for
+     * you via the ::getAjaxUrl() method in this class.
+     */
+    abstract function getJsStub();
+
+    /**
+     * getAjaxUrl
+     *
+     * Generate a URL to be used as an AJAX callback. The URL can be used to
+     * trigger this thread entry action via the callback.
+     *
+     * Parameters:
+     * $dialog - (bool) used in conjunction with `$.dialog()` javascript
+     *      function which assumes the `ajax.php/` should be replace a leading
+     *      `#` in the url
+     */
+    function getAjaxUrl($dialog=false) {
+        return sprintf('%stickets/%d/thread/%d/%s',
+            $dialog ? '#' : 'ajax.php/',
+            $this->thread->getThread()->getObjectId(),
+            $this->thread->getId(),
+            static::getId()
+        );
+    }
+}
 ?>
diff --git a/include/class.thread_actions.php b/include/class.thread_actions.php
new file mode 100644
index 0000000000000000000000000000000000000000..ce6ac82469df253fe5aecb942759de7f4a814252
--- /dev/null
+++ b/include/class.thread_actions.php
@@ -0,0 +1,55 @@
+<?php
+/*********************************************************************
+    class.thread_actions.php
+
+    Actions for thread entries. This serves as a simple repository for
+    drop-down actions which can be triggered on the ticket-view page for an
+    object's thread.
+
+    Jared Hancock <jared@osticket.com>
+    Peter Rotich <peter@osticket.com>
+    Copyright (c)  2006-2014 osTicket
+    http://www.osticket.com
+
+    Released under the GNU General Public License WITHOUT ANY WARRANTY.
+    See LICENSE.TXT for details.
+
+    vim: expandtab sw=4 ts=4 sts=4:
+**********************************************************************/
+include_once(INCLUDE_DIR.'class.thread.php');
+
+class TEA_ShowEmailHeaders extends ThreadEntryAction {
+    static $id = 'view_headers';
+    static $name = /* trans */ 'View Email Headers';
+    static $icon = 'envelope';
+
+    function isEnabled() {
+        global $thisstaff;
+
+        return $thisstaff && $thisstaff->isAdmin();
+    }
+
+    function isVisible() {
+        return (bool) $this->thread->getEmailHeader();
+    }
+
+    function getJsStub() {
+        return sprintf("$.dialog('%s');",
+            $this->getAjaxUrl()
+        );
+    }
+
+    function trigger() {
+        switch ($_SERVER['REQUEST_METHOD']) {
+        case 'GET':
+            return $this->trigger__get();
+        }
+    }
+
+    private function trigger__get() {
+        $headers = $this->thread->getEmailHeader();
+
+        include STAFFINC_DIR . 'templates/thread-email-headers.tmpl.php';
+    }
+}
+ThreadEntry::registerAction(/* trans */ 'E-Mail', 'TEA_ShowEmailHeaders');
diff --git a/include/staff/templates/thread-email-headers.tmpl.php b/include/staff/templates/thread-email-headers.tmpl.php
new file mode 100644
index 0000000000000000000000000000000000000000..6e2f45809e0e9dc7d985b92eff1252fcb52ca7a0
--- /dev/null
+++ b/include/staff/templates/thread-email-headers.tmpl.php
@@ -0,0 +1,15 @@
+<h3><?php echo __('Raw Email Headers'); ?></h3>
+<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b>
+<hr/>
+
+<pre style="max-height: 300px; overflow-y: scroll">
+<?php echo $headers; ?>
+</pre>
+
+<hr>
+<p class="full-width">
+    <span class="buttons pull-right">
+        <input type="button" name="cancel" class="close"
+            value="<?php echo __('Close'); ?>">
+    </span>
+</p>
diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php
index 409eac800ab1399afc12f26c81faf90be9aec67d..ce64ae1156c060a3a5bb0fb113149dc376c67281 100644
--- a/include/staff/ticket-view.inc.php
+++ b/include/staff/ticket-view.inc.php
@@ -1,4 +1,6 @@
 <?php
+include_once INCLUDE_DIR . 'class.thread_actions.php';
+
 //Note that ticket obj is initiated in tickets.php.
 if(!defined('OSTSCPINC') || !$thisstaff || !is_object($ticket) || !$ticket->getId()) die('Invalid path');
 
@@ -394,7 +396,8 @@ $tcount+= $ticket->getNumNotes();
     /* -------- Messages & Responses & Notes (if inline)-------------*/
     $types = array('M', 'R', 'N');
     if(($thread=$ticket->getThreadEntries($types))) {
-       foreach($thread as $entry) { ?>
+       foreach($thread as $entry) {
+        $tentry = $ticket->getThreadEntry($entry['id']); ?>
         <table class="thread-entry <?php echo $threadTypes[$entry['type']]; ?>" cellspacing="0" cellpadding="1" width="940" border="0">
             <tr>
                 <th colspan="4" width="100%">
@@ -405,7 +408,29 @@ $tcount+= $ticket->getNumNotes();
                     <span style="display:inline-block;padding:0 1em" class="faded title"><?php
                         echo Format::truncate($entry['title'], 100); ?></span>
                     </span>
-                    <span class="pull-right" style="white-space:no-wrap;display:inline-block">
+<?php           if ($tentry->hasActions()) {
+                    $actions = $tentry->getActions(); ?>
+                    <div class="pull-right">
+                    <span class="action-button pull-right" data-dropdown="#entry-action-more-<?php echo $entry['id']; ?>">
+                        <i class="icon-caret-down"></i>
+                        <span ><i class="icon-cog"></i></span>
+                    </span>
+                    <div id="entry-action-more-<?php echo $entry['id']; ?>" class="action-dropdown anchor-right">
+                <ul class="title">
+<?php               foreach ($actions as $group => $list) {
+                        foreach ($list as $id => $action) { ?>
+                    <li>
+                    <a class="no-pjax" href="#" onclick="javascript:
+                            <?php echo str_replace('"', '\\"', $action->getJsStub()); ?>; return false;">
+                        <i class="<?php echo $action->getIcon(); ?>"></i> <?php
+                            echo $action->getName();
+                ?></a></li>
+<?php                   }
+                    } ?>
+                </ul>
+                    </div>
+<?php           } ?>
+                    <span style="vertical-align:middle">
                         <span style="vertical-align:middle;" class="textra"></span>
                         <span style="vertical-align:middle;"
                             class="tmeta faded title"><?php
@@ -420,7 +445,6 @@ $tcount+= $ticket->getNumNotes();
             <?php
             $urls = null;
             if($entry['attachments']
-                    && ($tentry = $ticket->getThreadEntry($entry['id']))
                     && ($urls = $tentry->getAttachmentUrls())
                     && ($links = $tentry->getAttachmentsLinks())) {?>
             <tr>
diff --git a/scp/ajax.php b/scp/ajax.php
index 1c313c18c4afab80c58cbb3579de2cf254a34c76..131243adeb18c39b487631bcb9c5aee7b50044c1 100644
--- a/scp/ajax.php
+++ b/scp/ajax.php
@@ -142,6 +142,7 @@ $dispatcher = patterns('',
         url_get('^(?P<tid>\d+)/canned-resp/(?P<cid>\w+).(?P<format>json|txt)', 'cannedResponse'),
         url_get('^(?P<tid>\d+)/status/(?P<status>\w+)(?:/(?P<sid>\d+))?$', 'changeTicketStatus'),
         url_post('^(?P<tid>\d+)/status$', 'setTicketStatus'),
+        url('^(?P<tid>\d+)/thread/(?P<thread_id>\d+)/(?P<action>\w+)$', 'triggerThreadAction'),
         url_get('^status/(?P<status>\w+)(?:/(?P<sid>\d+))?$', 'changeSelectedTicketsStatus'),
         url_post('^status/(?P<state>\w+)$', 'setSelectedTicketsStatus'),
         url_get('^(?P<tid>\d+)/tasks$', 'tasks'),