Message-ID, In-Reply-To, References: How a Mail Thread Holds Together

« Blog · By

That replies get filed under the original message seems self-evident. It is not. A thread is not something stored anywhere — it is reassembled every time you open the mailbox, from three headers carried in every message. When threads fall apart, one of those three is almost always missing.

The three headers

HeaderContents
Message-IDThis message's unique identifier. Assigned by the sending server, once, worldwide.
In-Reply-ToThe identifier of exactly the message being replied to. A single value.
ReferencesThe identifiers of every message in the thread so far, in the order they were created.

A Message-ID looks like this:

Message-ID: <1a4f9c02-7e3b-4d51-9f18-2c6a0b7d4e88@company.com>

The part after the @ is the domain of the system that created it; in front of it stands a value that must never repeat. The angle brackets belong to it — they are part of the format, not decoration.

How the thread grows

1 · customer enquiry 2 · reply 3 · follow-up question 4 · reply Message-ID: <a> In-Reply-To: — References: — Message-ID: <b> In-Reply-To: <a> References: <a> Message-ID: <c> In-Reply-To: <b> References: <a> <b> Message-ID: <d> In-Reply-To: <c> References: <a> <b> <c> References grows with every reply. The client builds the tree from it.
In-Reply-To names the immediate predecessor, References the whole path back to the start. That is why a thread survives even when a message in the middle is missing.

The difference between the two fields is where it gets interesting. In-Reply-To alone would be enough for a chain — but only as long as every message is present. Lose one and the chain breaks, leaving the rest dangling.

References solves exactly that: because every message carries the complete path back to the beginning, a client can still find the common origin when three messages in between never arrived. That is why a forwarded reply files itself correctly for the new recipient even though they never saw the start.

The other route: by subject

Some clients — Gmail did this for a long time — group by subject and participants instead. That has one practical advantage: it works with senders that do not set the headers. And two drawbacks everyone knows without knowing the reason:

  • Change the subject because the topic has moved on, and you split the thread.
  • Two unrelated messages with the subject "Invoice" end up in the same thread although they have nothing to do with each other.

Neither happens when grouping is done by identifier. A subject may change as often as it likes, as long as References is there.

Where it breaks in practice

Almost always with automatically generated messages, and almost always for one of these reasons:

  • No reply references set. The ticket system replies to the customer but sets neither In-Reply-To nor References. The reply appears to the customer as a new, unconnected message. This is by far the most common case.
  • Subject tags instead of headers. That is precisely why so many subject lines carry a [#48213]: the system recognises its own cases by it, because it does not rely on the headers. For the human in front of it, that is noise.
  • Changing identifier on resend. If the same message is delivered twice and gets a new Message-ID each time, no recipient can detect the duplicate. Conversely: two different messages sharing one identifier are treated as one by some clients — the second disappears silently.
  • Truncated References. Some gateways shorten long headers. After that the origin of the thread can no longer be found.

What a sending system should do

The rules are short:

  1. Give every message its own, permanently stored Message-ID. Anyone wanting to reply to it later will need it.
  2. When replying, set In-Reply-To to the identifier of the message being answered and build References from that message's References plus its Message-ID.
  3. If no References exists, the new one consists of the predecessor's Message-ID alone.
  4. Keep the identifier when resending the same message. Assign a new one for a new message.

That is two more fields in the send call. The difference for the recipient is considerable: instead of six unconnected messages, one case sits in the mailbox.

Checking for yourself

All three fields sit in the message header and can be displayed in any client — in Thunderbird via the source view, in Gmail via "Show original". When a thread falls apart, a glance there answers the question in ten seconds: does the reply carry an In-Reply-To, and is the value in it the Message-ID of the message that came before? If not, the mail client is not the problem.