diff --git a/include/ajax.kbase.php b/include/ajax.kbase.php
index c6bbd3156ecba7893839e0e1bc8c1404a32f9266..2f5bc75bfa6e0a8c88c9ab7ab2b4a0961224eb23 100644
--- a/include/ajax.kbase.php
+++ b/include/ajax.kbase.php
@@ -18,7 +18,7 @@ if(!defined('INCLUDE_DIR')) die('!');
 
 class KbaseAjaxAPI extends AjaxController {
 
-    function cannedResp($id, $format='') {
+    function cannedResp($id, $format='text') {
         global $thisstaff, $cfg;
 
         include_once(INCLUDE_DIR.'class.canned.php');
@@ -26,39 +26,10 @@ class KbaseAjaxAPI extends AjaxController {
         if(!$id || !($canned=Canned::lookup($id)) || !$canned->isEnabled())
             Http::response(404, 'No such premade reply');
 
-        //Load ticket.
-        if($_GET['tid']) {
-            include_once(INCLUDE_DIR.'class.ticket.php');
-            $ticket = Ticket::lookup($_GET['tid']);
-        }
-
-        $resp = array();
-        switch($format) {
-            case 'json':
-                $resp['id'] = $canned->getId();
-                $resp['ticket'] = $canned->getTitle();
-                $resp['response'] = $ticket
-                    ? $ticket->replaceVars($canned->getResponseWithImages())
-                    : $canned->getResponseWithImages();
-                $resp['files'] = $canned->attachments->getSeparates();
-
-                if (!$cfg->isHtmlThreadEnabled()) {
-                    $resp['response'] = Format::html2text($resp['response'], 90);
-                    $resp['files'] += $canned->attachments->getInlines();
-                }
-
-                $response = $this->json_encode($resp);
-                break;
-
-            case 'txt':
-            default:
-                $response =$ticket?$ticket->replaceVars($canned->getResponse()):$canned->getResponse();
-
-                if (!$cfg->isHtmlThreadEnabled())
-                    $response = Format::html2text($response, 90);
-        }
+        if (!$cfg->isHtmlThreadEnabled())
+            $format .= '.plain';
 
-        return $response;
+        return $canned->getFormattedResponse($format);
     }
 
     function faq($id, $format='html') {
diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index 445733dc6f80fc0283e72740e742f79aabd19b24..eb2b8c9c50a057677e9b77a1569727f73d1b0cc0 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -657,5 +657,40 @@ class TicketsAjaxAPI extends AjaxController {
         Http::response(201, 'Successfully managed');
     }
 
+    function cannedResponse($tid, $cid, $format='text') {
+        global $thisstaff, $cfg;
+
+        if (!($ticket = Ticket::lookup($tid))
+                || !$ticket->checkStaffAccess($thisstaff))
+            Http::response(404, 'Unknown ticket #');
+
+
+        if ($cid && !is_numeric($cid)) {
+            if (!($response=$ticket->getThread()->getVar($cid)))
+                Http::response(404, 'Unknown ticket variable');
+
+            // Ticket thread variables are assumed to be quotes
+            $response = "<br/><blockquote>$response</blockquote><br/>";
+            //  Return text if html thread is not enabled
+            if (!$cfg->isHtmlThreadEnabled())
+                $response = Format::html2text($response, 90);
+
+            // XXX: assuming json format for now.
+            return Format::json_encode(array('response' => $response));
+        }
+
+        if (!$cfg->isHtmlThreadEnabled())
+            $format.='.plain';
+
+        $varReplacer = function (&$var) use($ticket) {
+            return $ticket->replaceVars($var);
+        };
+
+        include_once(INCLUDE_DIR.'class.canned.php');
+        if (!$cid || !($canned=Canned::lookup($cid)) || !$canned->isEnabled())
+            Http::response(404, 'No such premade reply');
+
+        return $canned->getFormattedResponse($format, $varReplacer);
+    }
 }
 ?>
diff --git a/include/class.canned.php b/include/class.canned.php
index 457c6d8f904eaaf476bc3744422989f17c940169..bfb5e82ce5e09a6eca8dab6f16aeda2e0485cc4f 100644
--- a/include/class.canned.php
+++ b/include/class.canned.php
@@ -86,6 +86,60 @@ class Canned {
         return $this->getResponse();
     }
 
+    function getHtml() {
+        return $this->getFormattedResponse('html');
+    }
+
+    function getPlainText() {
+        return $this->getFormattedResponse('text.plain');
+    }
+
+    function getFormattedResponse($format='text', $cb=null) {
+
+        $resp = array();
+        $html = true;
+        switch($format) {
+            case 'json.plain':
+                $html = false;
+                // fall-through
+            case 'json':
+                $resp['id'] = $this->getId();
+                $resp['title'] = $this->getTitle();
+                $resp['response'] = $this->getResponseWithImages();
+
+                // Callback to strip or replace variables!
+                if ($cb && is_callable($cb))
+                    $resp = $cb($resp);
+
+                $resp['files'] = $this->attachments->getSeparates();
+                // strip html
+                if (!$html) {
+                    $resp['response'] = Format::html2text($resp['response'], 90);
+                    $resp['files'] += $this->attachments->getInlines();
+                }
+                return Format::json_encode($resp);
+                break;
+            case 'html':
+            case 'text.html':
+                $response = $this->getResponseWithImages();
+                break;
+            case 'text.plain':
+                $html = false;
+            case 'text':
+            default:
+                $response = $this->getResponse();
+                if (!$html)
+                    $response = Format::html2text($response, 90);
+                break;
+        }
+
+        // Callback to strip or replace variables!
+        if ($response && $cb && is_callable($cb))
+            $response = $cb($response);
+
+        return $response;
+    }
+
     function getNotes() {
         return $this->ht['notes'];
     }
diff --git a/include/class.ticket.php b/include/class.ticket.php
index f0d4c770f251741bd386c28512599da2ed6aaa2b..d015287ac3bc3a4773821846a177302e82946596 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -1670,9 +1670,16 @@ class Ticket {
         foreach ($canned->attachments->getAll() as $file)
             $files[] = $file['id'];
 
+        if ($cfg->isHtmlThreadEnabled())
+            $response = new HtmlThreadBody(
+                    $this->replaceVars($canned->getHtml()));
+        else
+            $response = new HtmlThreadBody(
+                    $this->replaceVars($canned->getPlainText()));
+
         $info = array('msgId' => $msgId,
                       'poster' => 'SYSTEM (Canned Reply)',
-                      'response' => $this->replaceVars($canned->getResponse()),
+                      'response' => $response,
                       'cannedattachments' => $files);
 
         $errors = array();