diff --git a/include/class.format.php b/include/class.format.php
index e203415806b62f4555e3873c306bf7ae060fe33f..3377b4fe4938ac9d1be98c3ad592bab39bdd3647 100644
--- a/include/class.format.php
+++ b/include/class.format.php
@@ -218,7 +218,7 @@ class Format {
             'schemes' => 'href: aim, feed, file, ftp, gopher, http, https, irc, mailto, news, nntp, sftp, ssh, telnet; *:file, http, https; src: cid, http, https, data',
             'hook_tag' => function($e, $a=0) { return Format::__html_cleanup($e, $a); },
             'elements' => '*+iframe',
-            'spec' => 'iframe=-*,height,width,type,src(match="`^(https?:)?//(www\.)?(youtube|dailymotion|vimeo)\.com/`i"),frameborder;',
+            'spec' => 'iframe=-*,height,width,type,src(match="`^(https?:)?//(www\.)?(youtube|dailymotion|vimeo)\.com/`i"),frameborder; div=data-mid',
         );
 
         return Format::html($html, $config);
diff --git a/include/class.mailer.php b/include/class.mailer.php
index 65e98f500d47223d0881e6b6a91024e05ac5c090..bb4e445d05da25ef350d82cb2c13088f01c22431 100644
--- a/include/class.mailer.php
+++ b/include/class.mailer.php
@@ -100,8 +100,10 @@ class Mailer {
         $subject = preg_replace("/(\r\n|\r|\n)/s",'', trim($subject));
 
         /* Message ID - generated for each outgoing email */
-        $messageId = sprintf('<%s-%s>', Misc::randCode(16),
-                ($this->getEmail()?$this->getEmail()->getEmail():'@osTicketMailer'));
+        $messageId = sprintf('<%s-%s-%s>',
+            substr(md5('mail'.SECRET_SALT), -9),
+            Misc::randCode(9),
+            ($this->getEmail()?$this->getEmail()->getEmail():'@osTicketMailer'));
 
         $headers = array (
             'From' => $this->getFromAddress(),
@@ -152,10 +154,17 @@ class Mailer {
         // then assume that it needs html processing to create a valid text
         // body
         $isHtml = true;
+        $mid_token = (isset($options['thread']))
+            ? $options['thread']->asMessageId($to) : '';
         if (!(isset($options['text']) && $options['text'])) {
+            if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()))
+                $message = "<div style=\"display:none\" data-mid=\"$mid_token\">$tag</div>"
+                    .$message;
             // Make sure nothing unsafe has creeped into the message
             $message = Format::safe_html($message); //XXX??
-            $mime->setTXTBody(Format::html2text($message, 90, false));
+            $txtbody = rtrim(Format::html2text($message, 90, false))
+                . ($mid_token ? "\nRef-Mid: $mid_token\n" : '');
+            $mime->setTXTBody($txtbody);
         }
         else {
             $mime->setTXTBody($message);
diff --git a/include/class.thread.php b/include/class.thread.php
index 147712324f1e46fe4a4ab77ccedb8125dc3b8f9f..29b7a5bdf9ffcfdeb812126c2b065d9c905a3a2a 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -186,13 +186,11 @@ class Thread {
 
     function delete() {
 
-        /* XXX: Leave this out until TICKET_EMAIL_INFO_TABLE has a primary
-         *      key
-        $sql = 'DELETE mid.* FROM '.TICKET_EMAIL_INFO_TABLE.' mid
+        $sql = 'UPDATE '.TICKET_EMAIL_INFO_TABLE.' mid
             INNER JOIN '.TICKET_THREAD_TABLE.' thread ON (thread.id = mid.thread_id)
-            WHERE thread.ticket_id = '.db_input($this->getTicketId());
+            SET mid.headers = null WHERE thread.ticket_id = '
+            .db_input($this->getTicketId());
         db_query($sql);
-         */
 
         $res=db_query('DELETE FROM '.TICKET_THREAD_TABLE.' WHERE ticket_id='.db_input($this->getTicketId()));
         if(!$res || !db_affected_rows())
@@ -613,6 +611,8 @@ Class ThreadEntry {
      *      - body - (string) email message body (decoded)
      */
     function postEmail($mailinfo) {
+        global $ost;
+
         // +==================+===================+=============+
         // | Orig Thread-Type | Reply Thread-Type | Requires    |
         // +==================+===================+=============+
@@ -631,6 +631,28 @@ Class ThreadEntry {
             // Reporting success so the email can be moved or deleted.
             return true;
 
+        // Mail sent by this system will have a message-id format of
+        // <code-random-mailbox@domain.tld>
+        // where code is a predictable string based on the SECRET_SALT of
+        // this osTicket installation. If this incoming mail matches the
+        // code, then it very likely originated from this system and looped
+        @list($code) = explode('-', $mailinfo['mid'], 2);
+        if (0 === strcasecmp(ltrim($code, '<'), substr(md5('mail'.SECRET_SALT), -9))) {
+            // This mail was sent by this system. It was received due to
+            // some kind of mail delivery loop. It should not be considered
+            // a response to an existing thread entry
+            if ($ost) $ost->log(LOG_ERR, 'Email loop detected', sprintf(
+               'It appears as though &lt;%s&gt; is being used as a forwarded or
+                fetched email account and is also being used as a user /
+                system account. Please correct the loop or seek technical
+                assistance.', $mailinfo['email']),
+                // This is quite intentional -- don't continue the loop
+                false,
+                // Force the message, even if logging is disabled
+                true);
+            return true;
+        }
+
         $vars = array(
             'mid' =>    $mailinfo['mid'],
             'header' => $mailinfo['header'],
@@ -866,9 +888,67 @@ Class ThreadEntry {
             }
         }
 
+        // Search for the message-id token in the body
+        if (preg_match('`(?:data-mid="|Ref-Mid: )([^"\s]*)(?:$|")`',
+                $mailinfo['message'], $match))
+            if ($thread = ThreadEntry::lookupByMessageId($match[1]))
+                   return $thread;
+
         return null;
     }
 
+    /**
+     * Find a thread entry from a message-id created from the
+     * ::asMessageId() method
+     */
+    function lookupByMessageId($mid, $from) {
+        $mid = trim($mid, '<>');
+        list($ver, $ids, $mails) = explode('$', $mid, 3);
+
+        // Current version is <null>
+        if ($ver !== '')
+            return false;
+
+        $ids = @unpack('Vthread', base64_decode($ids));
+        if (!$ids || !$ids['thread'])
+            return false;
+
+        $thread = ThreadEntry::lookup($ids['thread']);
+        if (!$thread)
+            return false;
+
+        if (0 === strcasecmp($thread->asMessageId($from, $vers), $mid))
+            return $thread;
+    }
+
+    /**
+     * Get an email message-id that can be used to represent this thread
+     * entry. The same message-id can be passed to ::lookupByMessageId() to
+     * find this thread entry
+     *
+     * Formats:
+     * Initial (version <null>)
+     * <$:b32(thread-id)$:md5(to-addr.ticket-num.ticket-id)@:md5(url)>
+     *      thread-id - thread-id, little-endian INT, packed
+     *      :b32() - base32 encoded
+     *      to-addr - individual email recipient
+     *      ticket-num - external ticket number
+     *      ticket-id - internal ticket id
+     *      :md5() - last 10 hex chars of MD5 sum
+     *      url - helpdesk URL
+     */
+    function asMessageId($to, $version=false) {
+        global $ost;
+
+        $domain = md5($ost->getConfig()->getURL());
+        $ticket = $this->getTicket();
+        return sprintf('$%s$%s@%s',
+            base64_encode(pack('V', $this->getId())),
+            substr(md5($to . $ticket->getNumber() . $ticket->getId()), -10),
+            substr($domain, -10)
+        );
+    }
+
     //new entry ... we're trusting the caller to check validity of the data.
     function create($vars) {
         global $cfg;
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 5baf918db5976dcd60df97e447278d2942abc59c..b26237755f2df3802a407d662e0c6e125fb57e62 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -889,7 +889,8 @@ class Ticket {
 
         $options = array(
             'inreplyto'=>$message->getEmailMessageId(),
-            'references'=>$message->getEmailReferences());
+            'references'=>$message->getEmailReferences(),
+            'thread'=>$message);
 
         //Send auto response - if enabled.
         if($autorespond
@@ -903,9 +904,6 @@ class Ticket {
                           'signature' => ($dept && $dept->isPublic())?$dept->getSignature():'')
                     );
 
-            if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()))
-                $msg['body'] = "<p style=\"display:none\">$tag<p>".$msg['body'];
-
             $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body'],
                 null, $options);
         }
@@ -1094,13 +1092,10 @@ class Ticket {
                                 'recipient' => $user,
                                 'signature' => ($dept && $dept->isPublic())?$dept->getSignature():''));
 
-            //Reply separator tag.
-            if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()))
-                $msg['body'] = "<p style=\"display:none\">$tag<p>".$msg['body'];
-
             $options = array(
-                'inreplyto' => $message->getEmailMessageId(),
-                'references' => $message->getEmailReferencesForUser($user));
+                'inreplyto'=>$message->getEmailMessageId(),
+                'references' => $message->getEmailReferencesForUser($user),
+                'thread'=>$message);
             $email->sendAutoReply($user->getEmail(), $msg['subj'], $msg['body'],
                 null, $options);
         }
@@ -1157,7 +1152,8 @@ class Ticket {
             $sentlist=array();
             $options = array(
                 'inreplyto'=>$note->getEmailMessageId(),
-                'references'=>$note->getEmailReferences());
+                'references'=>$note->getEmailReferences(),
+                'thread'=>$note);
             foreach( $recipients as $k=>$staff) {
                 if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue;
                 $alert = $this->replaceVars($msg, array('recipient' => $staff));
@@ -1408,7 +1404,8 @@ class Ticket {
             $sentlist=array();
             $options = array(
                 'inreplyto'=>$note->getEmailMessageId(),
-                'references'=>$note->getEmailReferences());
+                'references'=>$note->getEmailReferences(),
+                'thread'=>$note);
             foreach( $recipients as $k=>$staff) {
                 if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue;
                 $alert = $this->replaceVars($msg, array('recipient' => $staff));
@@ -1595,7 +1592,8 @@ class Ticket {
                 );
         $options = array(
                 'inreplyto' => $message->getEmailMessageId(),
-                'references' => $message->getEmailReferences());
+                'references' => $message->getEmailReferences(),
+                'thread'=>$message);
         //If enabled...send alert to staff (New Message Alert)
         if($cfg->alertONNewMessage()
                 && ($email = $cfg->getAlertEmail())
@@ -1670,13 +1668,11 @@ class Ticket {
             $msg = $this->replaceVars($msg->asArray(),
                 array('response' => $response, 'signature' => $signature));
 
-            if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()))
-                $msg['body'] = "<p style=\"display:none\">$tag<p>".$msg['body'];
-
             $attachments =($cfg->emailAttachments() && $files)?$response->getAttachments():array();
             $options = array(
                 'inreplyto'=>$response->getEmailMessageId(),
-                'references'=>$response->getEmailReferences());
+                'references'=>$response->getEmailReferences(),
+                'thread'=>$response);
             $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body'], $attachments,
                 $options);
         }
@@ -1727,7 +1723,8 @@ class Ticket {
                 'poster' => $thisstaff);
         $options = array(
                 'inreplyto' => $response->getEmailMessageId(),
-                'references' => $response->getEmailReferences());
+                'references' => $response->getEmailReferences(),
+                'thread'=>$response);
 
         if(($email=$dept->getEmail())
                 && ($tpl = $dept->getTemplate())
@@ -1736,8 +1733,6 @@ class Ticket {
             $msg = $this->replaceVars($msg->asArray(),
                     $variables + array('recipient' => $this->getOwner()));
 
-            if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()))
-                $msg['body'] = "<p style=\"display:none\">$tag<p>".$msg['body'];
             $attachments = $cfg->emailAttachments()?$response->getAttachments():array();
             $email->send($this->getEmail(), $msg['subj'], $msg['body'], $attachments,
                 $options);
@@ -1852,7 +1847,8 @@ class Ticket {
 
             $options = array(
                 'inreplyto'=>$note->getEmailMessageId(),
-                'references'=>$note->getEmailReferences());
+                'references'=>$note->getEmailReferences(),
+                'thread'=>$note);
             $sentlist=array();
             foreach( $recipients as $k=>$staff) {
                 if(!is_object($staff)
@@ -2512,13 +2508,13 @@ class Ticket {
                         )
                     );
 
-            if($cfg->stripQuotedReply() && ($tag=trim($cfg->getReplySeparator())))
-                $msg['body'] = "<p style=\"display:none\">$tag<p>".$msg['body'];
-
             $references = $ticket->getLastMessage()->getEmailMessageId();
             if (isset($response))
                 $references = array($response->getEmailMessageId(), $references);
-            $options = array('references' => $references);
+            $options = array(
+                'references' => $references,
+                'thread'=>$this->getLastMessage()
+            );
             $email->send($ticket->getEmail(), $msg['subj'], $msg['body'], $attachments,
                 $options);
         }