Email Threading Overview
Email threading groups related messages into a conversation. This page covers the headers that mail clients thread on, the thread identifiers some servers assign, and where EmailEngine exposes them.
How Email Threading Works
Threading is done by the client, from four pieces of each message:
- Message-ID: the unique identifier of the message
- In-Reply-To: the
Message-IDof the message being replied to - References: the chain of
Message-IDs in the thread so far - Subject: expected to stay the same apart from
Re:andFwd:prefixes
RFC 5256
RFC 5256 defined server-side threading over a single folder. That works for mailing-list traffic, where the whole thread sits in one folder, and poorly for one-to-one conversations, where half the messages are in the Inbox and the other half in Sent Mail.
RFC 8474
RFC 8474 (the OBJECTID extension) gives each message a server-assigned thread ID that holds across folders. Support depends on the server, so client-side threading from headers remains the fallback for most accounts.
Threading Headers Explained
Message-ID
Every message has a unique Message-ID:
Message-ID: <56b3c6d2-f7c0-4272-8beb-e25fdb7c19f1@example.com>
Format: <unique-id@domain>
When you set it yourself:
- Wrap it in angle brackets
< > - Use a UUID or a comparably unique value
- Use the domain of the
Fromaddress, or your own service domain - Store it, because every follow-up needs it for
References
If you do not set messageId on submit, EmailEngine generates one and returns it in the submit response.
In-Reply-To
Names the message being replied to:
In-Reply-To: <56b3c6d2-f7c0-4272-8beb-e25fdb7c19f1@example.com>
This is the parent-child link between two messages. EmailEngine sets it for reply and reply-all submissions that use reference.
References
The full chain of Message-IDs in the conversation:
References: <original@example.com> <reply1@example.com> <reply2@example.com>
- Space-separated
- Each ID in angle brackets
- Oldest first, newest last
When you use reference, EmailEngine builds this header from the referenced message's Message-ID, In-Reply-To, and References, adding missing angle brackets and removing duplicates. It sets no upper bound on the chain, and it does not touch a references header you set yourself.
Threading Challenges
Split Across Folders
A conversation is rarely in one folder:
- Inbox: received messages
- Sent: your side of the conversation
- Other folders: messages moved by filters or by hand
Retrieving a whole thread therefore needs either a cross-folder search or one search per folder. See Searching threads.
Provider Differences
- Gmail: a thread ID on every message (
X-GM-THRIDover IMAP,threadIdin the Gmail API) - Microsoft 365: a conversation ID over the Graph API, nothing over IMAP
- Yahoo and AOL: a thread ID through the
OBJECTIDextension - Other IMAP servers: no thread ID unless the server implements
OBJECTID
See Provider support for the details.
Subject Line Sensitivity
Some clients include the subject in their threading decision. Changing the subject text between messages, beyond adding Re: or Fwd:, can split a thread even when the headers are correct.
EmailEngine's Threading Support
EmailEngine passes on the thread identifier whenever the backend provides one:
- Gmail API: the Gmail API's
threadId - Microsoft Graph API: the message's
conversationId - IMAP: the value the server returns when it supports
OBJECTID(RFC 8474) or Gmail'sX-GM-EXT-1extension, which covers Gmail over IMAP and OBJECTID servers such as Yahoo and AOL
IMAP servers without either extension give EmailEngine nothing to pass on. For those accounts a thread has to be reconstructed from Message-ID, In-Reply-To, and References.
Thread ID Format
The threadId property is a string whose format depends on the provider:
| Provider | Format | Example |
|---|---|---|
| Gmail API | Long numeric string | "1759349012996310407" |
| Gmail over IMAP (X-GM-EXT-1) | Long numeric string | "1759349012996310407" |
| Microsoft Graph API | Graph conversationId, a long base64 string | "AAQkAGI2THY2..." |
| IMAP with OBJECTID (Yahoo, AOL) | Short numeric string | "501" |
Treat every one of them as an opaque string. Provider support has a full Graph conversation ID in context.
A threadId is only meaningful within the account it came from. See Message IDs for how it relates to the other identifiers.
Where Thread IDs Appear
threadId is present, when the backend provides one, in:
- Message listing responses
- Message detail responses
- Message search responses
- Webhook payloads that carry a message, such as
messageNew
Message Lists Are Not Thread Lists
EmailEngine does not group a listing by thread. Messages in the same thread are listed as separate entries, whatever the backend.
To get the messages of one thread, search with threadId in the search object. The path query parameter is required:
curl -XPOST "https://emailengine.example.com/v1/account/example/search?path=INBOX" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"search": {
"threadId": "1759349012996310407"
}
}'
See Searching threads for which folder to search per provider.
Example webhook (abridged; a real messageNew payload carries the full message entry):
{
"account": "example",
"date": "2025-10-10T14:30:00.000Z",
"path": "INBOX",
"event": "messageNew",
"data": {
"id": "AAABkPHBeR0",
"threadId": "1759349012996310407",
"subject": "Project discussion",
"from": {
"address": "colleague@example.com"
}
}
}
See Also
- Provider support - Which backends assign a thread ID at all
- Searching threads - Retrieving every message in a conversation
- Sending threaded messages - Keeping a sequence you send in one thread
- Replies and forwards - Letting EmailEngine build the headers