diff --git a/include/api.tickets.php b/include/api.tickets.php
index 32b7d6feb9ec544f015426b3790c71b61f7af815..d70eda638a31f69bc9841d47d0714c127232a9ef 100644
--- a/include/api.tickets.php
+++ b/include/api.tickets.php
@@ -151,25 +151,29 @@ class TicketApiController extends ApiController {
         if (!$data)
             $data = $this->getEmailRequest();
 
+        $seen = false;
         if (($entry = ThreadEntry::lookupByEmailHeaders($data, $seen))
-            && ($thread = $entry->getThread())
-            && ($t = $thread->getObject())
-            && (!$t instanceof Ticket || (
-                   $vars['staffId']
-                || !$t->isClosed()
-                || $t->isReopenable()
-            ))
             && ($message = $entry->postEmail($data))
         ) {
             if ($message instanceof ThreadEntry) {
                 return $message->getThread()->getObject();
             }
-            else if ($message) {
+            else if ($seen) {
                 // Email has been processed previously
-                return $t;
+                return $entry->getThread()->getObject();
             }
         }
 
+        // Allow continuation of thread without initial message or note
+        elseif (($thread = Thread::lookupByEmailHeaders($data))
+            && ($message = $thread->postEmail($data))
+        ) {
+            return $thread->getObject();
+        }
+
+        // All emails which do not appear to be part of an existing thread
+        // will always create new "Tickets". All other objects will need to
+        // be created via the web interface or the API
         return $this->createTicket($data);
     }
 
diff --git a/include/class.mailer.php b/include/class.mailer.php
index a99fb7a6acefd2058ec53c2dbc5b6c11d9bd5ae3..e3b40c6b2483de759a8a251aed9344f813a45039 100644
--- a/include/class.mailer.php
+++ b/include/class.mailer.php
@@ -118,23 +118,24 @@ class Mailer {
      *      'thread' element, the threadId will be recorded in the TAG
      *
      * Returns:
-     * (string) - email message id, with leading and trailing <> chars. See
-     * the format below for the structure.
+     * (string) - email message id, without leading and trailing <> chars.
+     * See the Format below for the structure.
      *
      * Format:
-     * VA-B-C-D, with dash separators and A-D explained below:
+     * VA-B-C, with dash separators and A-C explained below:
      *
      * V: Version code of the generated Message-Id
-     * A: Predictable random code — used for loop detection
-     * B: Random data for unique identifier
-     *    Version Code: A (at char position 10)
-     * C: TAG: Base64(Pack(userid, entryId, type)), = chars discarded
-     * D: Signature:
-     *   '@' + Signed Tag value, last 10 chars from
-     *        HMAC(sha1, tag+rand, SECRET_SALT)
-     *   -or- Original From email address
+     * A: Predictable random code — used for loop detection (sysid)
+     * B: Random data for unique identifier (rand)
+     * C: TAG: Base64(Pack(userid, entryId, threadId, type, Signature)),
+     *    '=' chars discarded
+     * where Signature is:
+     *   Signed Tag value, last 5 chars from
+     *        HMAC(sha1, Tag + rand + sysid, SECRET_SALT),
+     *   where Tag is:
+     *     pack(userId, entryId, threadId, type)
      */
-    function getMessageId($recipient, $options=array(), $version='A') {
+    function getMessageId($recipient, $options=array(), $version='B') {
         $tag = '';
         $rand = Misc::randCode(9,
             // RFC822 specifies the LHS of the addr-spec can have any char
@@ -142,13 +143,14 @@ class Mailer {
             // section separator, and + is reserved for historical reasons
             'abcdefghiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_=');
         $sig = $this->getEmail()?$this->getEmail()->getEmail():'@osTicketMailer';
+        $sysid = static::getSystemMessageIdCode();
         if ($recipient instanceof EmailContact) {
             // Create a tag for the outbound email
             $entry = (isset($options['thread']) && $options['thread'] instanceof ThreadEntry)
                 ? $options['thread'] : false;
             $thread = $entry ? $entry->getThread()
                 : (isset($options['thread']) && $options['thread'] instanceof Thread
-                    ? $options['thread']->getId() : false);
+                    ? $options['thread'] : false);
             $tag = pack('VVVa',
                 $recipient->getId(),
                 $entry ? $entry->getId() : 0,
@@ -158,13 +160,12 @@ class Mailer {
                     : ($recipient instanceof Collaborator ? 'C'
                     : '?')))
             );
-            $tag = str_replace('=','',base64_encode($tag));
             // Sign the tag with the system secret salt
-            $sig = '@' . substr(hash_hmac('sha1', $tag.$rand, SECRET_SALT), -10);
+            $tag .= substr(hash_hmac('sha1', $tag.$rand.$sysid, SECRET_SALT, true), -5);
+            $tag = str_replace('=','',base64_encode($tag));
         }
-        return sprintf('<B%s-%s-%s-%s>',
-            static::getSystemMessageIdCode(),
-            $rand, $tag, $sig);
+        return sprintf('B%s-%s-%s-%s',
+            $sysid, $rand, $tag, $sig);
     }
 
     /**
@@ -210,36 +211,63 @@ class Mailer {
         if (count($parts) < 2)
             return $rv;
 
-        // Detect the MessageId version, which should be the tenth char of
-        // the second segment
-        $rv['version'] = @$parts[0][0];
-        $format = 'Vuid/VentryId/auserClass';
-        switch ($rv['version']) {
-        case 'B':
-            $format = 'Vuid/VentryId/VthreadId/auserClass';
-        case 'A':
-        default:
-            list($rv['code'], $rv['id'], $tag) = $parts;
-            // Drop the leading version code
-            $rv['code'] = substr($rv['code'], 1);
-            // Verify tag signature
-            $chksig = substr(hash_hmac('sha1', $tag.$rv['id'], SECRET_SALT), -10);
+        $decoders = array(
+        'A' => function($id, $tag) use ($sig) {
+            // Old format was VA-B-C-D@sig, where C was the packed tag and D
+            // was blank
+            $format = 'Vuid/VentryId/auserClass';
+            $chksig = substr(hash_hmac('sha1', $tag.$id, SECRET_SALT), -10);
             if ($tag && $sig == $chksig && ($tag = base64_decode($tag))) {
                 // Find user and ticket id
-                $rv += unpack($format, $tag);
-                // Attempt to make the user-id more specific
-                $classes = array(
-                    'S' => 'staffId', 'U' => 'userId'
-                );
-                if (isset($classes[$rv['userClass']]))
-                    $rv[$classes[$rv['userClass']]] = $rv['uid'];
+                return unpack($format, $tag);
             }
-            // Round-trip detection - the first section is the local
-            // system's message-id code
-            $rv['loopback'] = (0 === strcasecmp($rv['code'],
-                static::getSystemMessageIdCode()));
-            break;
-        }
+            return false;
+        },
+        'B' => function($id, $tag) {
+            $format = 'Vuid/VentryId/VthreadId/auserClass/a*sig';
+            if ($tag && ($tag = base64_decode($tag))) {
+                $info = unpack($format, $tag);
+                $sysid = static::getSystemMessageIdCode();
+                $shorttag = substr($tag, 0, 13);
+                $chksig = substr(hash_hmac('sha1', $shorttag.$id.$sysid,
+                    SECRET_SALT, true), -5);
+                if ($chksig == $info['sig']) {
+                    return $info;
+                }
+            }
+            return false;
+        },
+        );
+
+        // Detect the MessageId version, which should be the first char
+        $rv['version'] = @$parts[0][0];
+        if (!isset($decoders[$rv['version']]))
+            // invalid version code
+            return null;
+
+        // Drop the leading version code
+        list($rv['code'], $rv['id'], $tag) = $parts;
+        $rv['code'] = substr($rv['code'], 1);
+
+        // Verify tag signature and unpack the tag
+        $info = $decoders[$rv['version']]($rv['id'], $tag);
+        if ($info === false)
+            return $rv;
+
+        $rv += $info;
+
+        // Attempt to make the user-id more specific
+        $classes = array(
+            'S' => 'staffId', 'U' => 'userId'
+        );
+        if (isset($classes[$rv['userClass']]))
+            $rv[$classes[$rv['userClass']]] = $rv['uid'];
+
+        // Round-trip detection - the first section is the local
+        // system's message-id code
+        $rv['loopback'] = (0 === strcasecmp($rv['code'],
+            static::getSystemMessageIdCode()));
+
         return $rv;
     }
 
@@ -279,7 +307,7 @@ class Mailer {
             'To' => $to,
             'Subject' => $subject,
             'Date'=> date('D, d M Y H:i:s O'),
-            'Message-ID' => $messageId,
+            'Message-ID' => "<{$messageId}>",
             'X-Mailer' =>'osTicket Mailer',
         );
 
@@ -326,7 +354,8 @@ class Mailer {
         if (isset($options['thread'])
             && $options['thread'] instanceof ThreadEntry
         ) {
-            $headers += array('References' => $options['thread']->getEmailReferences());
+            if ($references = $options['thread']->getEmailReferences())
+                $headers += array('References' => $references);
             if ($irt = $options['thread']->getEmailMessageId()) {
                 // This is an response from an email, like and autoresponse.
                 // Web posts will not have a email message-id
@@ -367,17 +396,16 @@ 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'])) {
             $tag = '';
             if ($cfg && $cfg->stripQuotedReply()
                     && (!isset($options['reply-tag']) || $options['reply-tag']))
-                $tag = $cfg->getReplySeparator() . '<br/><br/>';
-            $message = "<div style=\"display:none\"
-                data-mid=\"$mid_token\">$tag</div>$message";
+                $tag = '<div>'.$cfg->getReplySeparator() . '<br/><br/></div>';
+            // Embed the data-mid in such a way that it should be included
+            // in a response
+            $message = "<div data-mid=\"$messageId\">{$tag}{$message}</div>";
             $txtbody = rtrim(Format::html2text($message, 90, false))
-                . ($mid_token ? "\nRef-Mid: $mid_token\n" : '');
+                . ($messageId ? "\nRef-Mid: $messageId\n" : '');
             $mime->setTXTBody($txtbody);
         }
         else {
diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php
index 04475abc01cc10a1135c304cdc8627192e05a0dd..4e0e3c43059aac9c100ea390ffd3622551d5aba6 100644
--- a/include/class.mailfetch.php
+++ b/include/class.mailfetch.php
@@ -722,13 +722,6 @@ class MailFetcher {
 
         $seen = false;
         if (($entry = ThreadEntry::lookupByEmailHeaders($vars, $seen))
-            && ($thread = $entry->getThread())
-            && ($t = $thread->getObject())
-            && (!$t instanceof Ticket || (
-                   $vars['staffId']
-                || !$t->isClosed()
-                || $t->isReopenable()
-            ))
             && ($message = $entry->postEmail($vars))
         ) {
             if (!$message instanceof ThreadEntry)
@@ -736,13 +729,23 @@ class MailFetcher {
                 return $message;
             // NOTE: This might not be a "ticket"
             $ticket = $message->getThread()->getObject();
-        } elseif ($seen) {
+        }
+        elseif ($seen) {
             // Already processed, but for some reason (like rejection), no
             // thread item was created. Ignore the email
             return true;
-        } elseif (($ticket=Ticket::create($vars, $errors, 'Email'))) {
+        }
+        // Allow continuation of thread without initial message or note
+        elseif (($thread = Thread::lookupByEmailHeaders($vars))
+            && ($message = $entry->postEmail($vars))
+        ) {
+            // NOTE: This might not be a "ticket"
+            $ticket = $thread->getObject();
+        }
+        elseif (($ticket=Ticket::create($vars, $errors, 'Email'))) {
             $message = $ticket->getLastMessage();
-        } else {
+        }
+        else {
             //Report success if the email was absolutely rejected.
             if(isset($errors['errno']) && $errors['errno'] == 403) {
                 // Never process this email again!
diff --git a/include/class.thread.php b/include/class.thread.php
index b8b5badebdb3f37ce0a30c7e86cb3ba51abdfe2c..0dba7135cf4c7b455334a91e3f3f1c3ef568ae7f 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -108,9 +108,19 @@ class Thread extends VerySimpleModel {
         // | Message (M)      | Response (R)      | From: Staff |
         // +------------------+-------------------+-------------+
 
-        if (!$object = $this->getObject())
+        if (!$object = $this->getObject()) {
             // How should someone find this thread?
             return false;
+        }
+        elseif ($object instanceof Ticket && (
+               !$mailinfo['staffId']
+            && $object->isClosed()
+            && !$object->isReopenable()
+        )) {
+            // Ticket is closed, not reopenable, and email was not submitted
+            // by an agent. Email cannot be submitted
+            return false;
+        }
 
         // Mail sent by this system will have a message-id format of
         // <code-random-mailbox@domain.tld>
@@ -241,6 +251,71 @@ class Thread extends VerySimpleModel {
         return $deleted;
     }
 
+    /**
+     * Function: lookupByEmailHeaders
+     *
+     * Attempt to locate a thread by the email headers. It should be
+     * considered a secondary lookup to ThreadEntry::lookupByEmailHeaders(),
+     * which should find an actual thread entry, which should be possible
+     * for all email communcation which is associated with a thread entry.
+     * The only time where this is useful is for threads which triggered
+     * email communication without a thread entry, for instance, like
+     * tickets created without an initial message.
+     */
+    function lookupByEmailHeaders(&$mailinfo) {
+        $possibles = array();
+        foreach (array('in-reply-to', 'references') as $header) {
+            $matches = array();
+            if (!isset($mailinfo[$header]) || !$mailinfo[$header])
+                continue;
+            // Header may have multiple entries (usually separated by
+            // spaces ( )
+            elseif (!preg_match_all('/<[^>@]+@[^>]+>/', $mailinfo[$header],
+                        $matches))
+                continue;
+
+            // The References header will have the most recent message-id
+            // (parent) on the far right.
+            // @see rfc 1036, section 2.2.5
+            // @see http://www.jwz.org/doc/threading.html
+            $possibles = array_merge($possibles, array_reverse($matches[0]));
+        }
+
+        // Add the message id if it is embedded in the body
+        $match = array();
+        if (preg_match('`(?:data-mid="|Ref-Mid: )([^"\s]*)(?:$|")`',
+                $mailinfo['message'], $match)
+            && !in_array($match[1], $possibles)
+        ) {
+            $possibles[] = $match[1];
+        }
+
+        foreach ($possibles as $mid) {
+            // Attempt to detect the ticket and user ids from the
+            // message-id header. If the message originated from
+            // osTicket, the Mailer class can break it apart. If it came
+            // from this help desk, the 'loopback' property will be set
+            // to true.
+            $mid_info = Mailer::decodeMessageId($mid);
+            if ($mid_info['loopback'] && isset($mid_info['uid'])
+                && @$mid_info['threadId']
+                && ($t = Thread::lookup($mid_info['threadId']))
+            ) {
+                if (@$mid_info['userId']) {
+                    $mailinfo['userId'] = $mid_info['userId'];
+                }
+                elseif (@$mid_info['staffId']) {
+                    $mailinfo['staffId'] = $mid_info['staffId'];
+                }
+                // ThreadEntry was positively identified
+                return $t;
+            }
+        }
+
+        return null;
+    }
+
+
     function delete() {
 
         //Self delete
@@ -286,7 +361,7 @@ class ThreadEntry extends VerySimpleModel {
         'select_related' => array('staff', 'user', 'email_info'),
         'joins' => array(
             'thread' => array(
-                'constraint' => array('thread_id' => 'ThreadModel.id'),
+                'constraint' => array('thread_id' => 'Thread.id'),
             ),
             'parent' => array(
                 'constraint' => array('pid' => 'ThreadEntry.id'),
@@ -730,6 +805,7 @@ class ThreadEntry extends VerySimpleModel {
             return $entry;
         }
 
+        $possibles = array();
         foreach (array('in-reply-to', 'references') as $header) {
             $matches = array();
             if (!isset($mailinfo[$header]) || !$mailinfo[$header])
@@ -744,59 +820,71 @@ class ThreadEntry extends VerySimpleModel {
             // (parent) on the far right.
             // @see rfc 1036, section 2.2.5
             // @see http://www.jwz.org/doc/threading.html
-            $thread = null;
-            foreach (array_reverse($matches[0]) as $mid) {
-                //Try to determine if it's a reply to a tagged email.
-                $ref = null;
-                if (strpos($mid, '+')) {
-                    list($left, $right) = explode('@',$mid);
-                    list($left, $ref) = explode('+', $left);
-                    $mid = "$left@$right";
+            $possibles = array_merge($possibles, array_reverse($matches[0]));
+        }
+
+        // Add the message id if it is embedded in the body
+        $match = array();
+        if (preg_match('`(?:data-mid="|Ref-Mid: )([^"\s]*)(?:$|")`',
+                $mailinfo['message'], $match)
+            && !in_array($match[1], $possibles)
+        ) {
+            $possibles[] = $match[1];
+        }
+
+        $thread = null;
+        foreach ($possibles as $mid) {
+            //Try to determine if it's a reply to a tagged email.
+            $ref = null;
+            if (strpos($mid, '+')) {
+                list($left, $right) = explode('@',$mid);
+                list($left, $ref) = explode('+', $left);
+                $mid = "$left@$right";
+            }
+            $entries = ThreadEntry::objects()
+                ->filter(array('email_info__mid' => $mid));
+            foreach ($entries as $t) {
+                // Capture the first match thread item
+                if (!$thread)
+                    $thread = $t;
+                // We found a match  - see if we can ID the user.
+                // XXX: Check access of ref is enough?
+                if ($ref && ($uid = $t->getUIDFromEmailReference($ref))) {
+                    if ($ref[0] =='s') //staff
+                        $mailinfo['staffId'] = $uid;
+                    else // user or collaborator.
+                        $mailinfo['userId'] = $uid;
+
+                    // Best possible case — found the thread and the
+                    // user
+                    return $t;
                 }
-                $possibles = ThreadEntry::objects()
-                    ->filter(array('email_info__mid' => $mid));
-                foreach ($possibles as $t) {
-                    // Capture the first match thread item
-                    if (!$thread)
-                        $thread = $t;
-                    // We found a match  - see if we can ID the user.
-                    // XXX: Check access of ref is enough?
-                    if ($ref && ($uid = $t->getUIDFromEmailReference($ref))) {
-                        if ($ref[0] =='s') //staff
-                            $mailinfo['staffId'] = $uid;
-                        else // user or collaborator.
-                            $mailinfo['userId'] = $uid;
-
-                        // Best possible case — found the thread and the
-                        // user
-                        return $t;
-                    }
+            }
+            // Attempt to detect the ticket and user ids from the
+            // message-id header. If the message originated from
+            // osTicket, the Mailer class can break it apart. If it came
+            // from this help desk, the 'loopback' property will be set
+            // to true.
+            $mid_info = Mailer::decodeMessageId($mid);
+            if ($mid_info['loopback'] && isset($mid_info['uid'])
+                && @$mid_info['entryId']
+                && ($t = ThreadEntry::lookup($mid_info['entryId']))
+                && ($t->thread_id == $mid_info['threadId'])
+            ) {
+                if (@$mid_info['userId']) {
+                    $mailinfo['userId'] = $mid_info['userId'];
                 }
-                // Attempt to detect the ticket and user ids from the
-                // message-id header. If the message originated from
-                // osTicket, the Mailer class can break it apart. If it came
-                // from this help desk, the 'loopback' property will be set
-                // to true.
-                $mid_info = Mailer::decodeMessageId($mid);
-                if ($mid_info['loopback'] && isset($mid_info['uid'])
-                    && @$mid_info['entryId']
-                    && ($t = ThreadEntry::lookup($mid_info['entryId']))
-                ) {
-                    if (@$mid_info['userId']) {
-                        $mailinfo['userId'] = $mid_info['userId'];
-                    }
-                    elseif (@$mid_info['staffId']) {
-                        $mailinfo['staffId'] = $mid_info['staffId'];
-                    }
-                    // ThreadEntry was positively identified
-                    return $t;
+                elseif (@$mid_info['staffId']) {
+                    $mailinfo['staffId'] = $mid_info['staffId'];
                 }
+                // ThreadEntry was positively identified
+                return $t;
             }
-            // Second best case — found a thread but couldn't identify the
-            // user from the header. Return the first thread entry matched
-            if ($thread)
-                return $thread;
         }
+        // Second best case — found a thread but couldn't identify the
+        // user from the header. Return the first thread entry matched
+        if ($thread)
+            return $thread;
 
         // Search for ticket by the [#123456] in the subject line
         // This is the last resort -  emails must match to avoid message
@@ -825,6 +913,8 @@ class ThreadEntry extends VerySimpleModel {
         }
 
         // Search for the message-id token in the body
+        // *DEPRECATED* the current algo on outgoing mail will use
+        // Mailer::getMessageId as the message id tagged here
         if (preg_match('`(?:data-mid="|Ref-Mid: )([^"\s]*)(?:$|")`',
                 $mailinfo['message'], $match))
             if ($thread = ThreadEntry::lookupByRefMessageId($match[1],
@@ -836,7 +926,9 @@ class ThreadEntry extends VerySimpleModel {
 
     /**
      * Find a thread entry from a message-id created from the
-     * ::asMessageId() method
+     * ::asMessageId() method.
+     *
+     * *DEPRECATED* use Mailer::decodeMessageId() instead
      */
     function lookupByRefMessageId($mid, $from) {
         $mid = trim($mid, '<>');
@@ -854,36 +946,7 @@ class ThreadEntry extends VerySimpleModel {
         if (!$thread)
             return false;
 
-        if (0 === strcasecmp($thread->asMessageId($from, $ver), $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 ::lookupByRefMessageId()
-     * 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->getThread()->getObject();
-        return sprintf('$%s$%s@%s',
-            base64_encode(pack('V', $this->getId())),
-            substr(md5($to . $ticket->getNumber() . $ticket->getId()), -10),
-            substr($domain, -10)
-        );
+        return $thread;
     }
 
     //new entry ... we're trusting the caller to check validity of the data.
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 41cc699305670f58ecbdabbaa4aa60abb98b50d7..a7a91190af8d205e386d644f2624a84359fe574f 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -2324,13 +2324,14 @@ implements RestrictedAccess, Threadable {
 
     // Threadable interface
     function postThreadEntry($type, $vars) {
+        $errors = array();
         switch ($type) {
         case 'M':
             return $this->postMessage($vars, $vars['origin']);
         case 'N':
-            return $this->postNote($vars);
+            return $this->postNote($vars, $errors);
         case 'R':
-            return $this->postReply($vars);
+            return $this->postReply($vars, $errors);
         }
     }
 
@@ -3262,7 +3263,7 @@ implements RestrictedAccess, Threadable {
                 $references[] = $response->getEmailMessageId();
             $options = array(
                 'references' => $references,
-                'thread' => $message,
+                'thread' => $message ?: $ticket->getThread(),
             );
             $email->send($ticket->getOwner(), $msg['subj'], $msg['body'], $attachments,
                 $options);