Gmail groups related emails into conversations. Reply to a thread from three different email clients, forward it twice, and all seven messages collapse into one conversation. This is email threading: the problem of grouping messages that belong to the same conversation, despite arriving out of order, from different clients, with inconsistent subject lines.

Threading Identifiers#

Email headers carry threading signals:

Message-ID: a unique identifier for each message, set by the sending client. Example: <abc123@mail.gmail.com>.

In-Reply-To: the Message-ID of the message being replied to. Set by the client when replying.

References: the full chain of Message-IDs from the start of the thread to the current message.

A simple threading algorithm: when a message arrives, check its In-Reply-To header. If a message with that Message-ID exists in the mailbox, add the new message to that thread. If no match, start a new thread.

The References header handles deeper chains: even if the immediate parent message is missing (deleted, filtered to spam), the full reference chain lets you find the thread root.

graph TD A[Message arrives: In-Reply-To: abc123] --> B[Lookup message-id abc123 in thread index] B --> C{Thread found?} C --> |Yes| D[Append to existing thread: thread_id=T42] C --> |No| E[Check References header for any known message-id] E --> F{Any reference found?} F --> |Yes| G[Append to that thread] F --> |No| H[Create new thread] style A fill:#000000,stroke:#00ff00,stroke-width:2px,color:#fff style B fill:#000000,stroke:#00ff00,stroke-width:2px,color:#fff style C fill:#000000,stroke:#00ff00,stroke-width:2px,color:#fff style D fill:#000000,stroke:#00ff00,stroke-width:2px,color:#fff style E fill:#000000,stroke:#00ff00,stroke-width:2px,color:#fff style F fill:#000000,stroke:#00ff00,stroke-width:2px,color:#fff style G fill:#000000,stroke:#00ff00,stroke-width:2px,color:#fff style H fill:#000000,stroke:#00ff00,stroke-width:2px,color:#fff

Subject-Based Fallback#

Many clients don’t set In-Reply-To correctly. Mailing lists rewrite Message-ID. Fallback: normalize the subject line (strip Re:, Fwd:, leading/trailing whitespace, case-fold) and use it as a secondary grouping key within a time window. Messages with the same normalized subject within 30 days go into the same thread candidate set, then a secondary check on participants filters obvious mismatches.

Thread Storage#

Each thread has a thread_id. Messages have a thread_id foreign key. The thread record stores: participant list, latest message timestamp, unread count, and labels. Queries like “show all unread threads in inbox” hit the threads table directly, not the messages table.

Thread metadata is mutable: every new message updates latest_timestamp and unread_count. These are write-hot rows. Use optimistic concurrency: version the thread row and retry on conflict.

At Oracle#

Salesforce Service Cloud handles email-to-case: customer emails create or update support cases. Threading was the core problem: a customer replying to a closed case should reopen it, not create a duplicate. We used the In-Reply-To header to look up the original case. When clients stripped headers (common with Outlook), we fell back to subject-line matching with a 90-day window. Subject fallback had a 3% false-positive rate, creating the occasional mis-threaded case. Acceptable given the alternative of duplicate cases.

What I’m Learning#

Email threading is a good example of designing for imperfect input. The ideal path uses In-Reply-To and References. The real path handles clients that don’t set them correctly. The fallback degrades gracefully rather than failing.

Have you dealt with email threading edge cases, and which client caused the most problems?