# Conversation threads

A thread is a canonical record created by the bridge: one conversation, the mailbox it arrived on, the people in it, and every message in order. It is the unit the engine picks work by and the unit a person marks up, because `threads.priority`, `threads.starred` and `threads.deleted_at` all live on it rather than on individual messages.

- **Status:** Available
- **Audience:** both
- **Channels:** email
- **In the app:** #/inbox
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/email/threads/

## What a thread is, structurally

When `connect_core.bridge_*` canonicalises a message it either attaches it to an existing thread or opens a new one. The thread is what the Conversations screen lists, what an approval is shown against, what memory and guidance attach to, and what the engine reads when it decides what to work on next. A single message is rarely the useful unit — an answer depends on what came before it.

A thread also carries its mailbox. A reply is sent from the mailbox the thread belongs to, which is why the sending identity is not editable at the moment of approval and why changing a mailbox's role does not re-route conversations already under way.

## What joins a message to one

Mail carries its own threading signals, and they vary in strength. Knowing which ones your correspondents' clients actually send explains almost every surprise on this subject.

| Signal | Strength | Failure mode |
|---|---|---|
| The reference headers a reply carries | Strong — this is what threading is designed around | Some clients and web forms drop them entirely |
| The participants | Supporting | Two unrelated conversations with the same two people are not one conversation |
| The subject | Weak on its own | "Invoice" is not a conversation, and a changed subject is not a new one |
| Arrival time | Not a threading signal | Adjacent arrival means nothing about relatedness |

The protocol detail behind those headers belongs to MIME and to mail transport, and lives in the [technology reference](/docs/technology/) rather than here. What matters at this level is that a thread is inferred from what the sending client provided, so a correspondent whose software provides little will produce more threads than you expect.

## A person's marks, and what they change

**`threads.priority`** — Not decoration. The engine reads it when it picks work, so correcting a priority changes what happens next rather than re-sorting a list.
**`threads.starred`** — The person's own. Nothing in the engine acts on it; it is there so a human can keep their own place.
**`threads.deleted_at`** — Also the person's own — removing a conversation from view without destroying the record it belongs to.

The same three columns exist on `calls`, which is deliberate: triage is one idea across channels rather than a mail feature that phone later copied. [Triage and priority](/docs/email/triage/) covers how the ranking is used.

> **Careful** Blocking is not one of these, and looking for it as a thread setting is the usual mistake. A block is a `connect_memory` row against the contact, tagged `block:<channel>` in `tags` — never in the body, because `directives()` reads only the tag list. That is what makes it hold across channels and into future conversations rather than dying with this one. [Blocking a sender](/docs/email/blocking/) has the detail.

## What breaks a thread

- A correspondent's client sends a reply without reference headers — the commonest cause by far, and nothing on either side can retrofit them.
- Someone forwards the conversation instead of replying: a forward is a new message about the old one, not a continuation of it.
- A reply arrives from a different address. That is not really a thread problem — it is a person problem, handled by [contact resolution](/docs/email/contact-resolution/), which can still put both addresses on one person.
- A conversation moves to another channel. A thread is email; the continuity you want across channels is the relationship, not the thread — see [One person across phone, email and WhatsApp](/research/one-person-many-channels/).
- A ticketing system in the middle rewrites the mail. What arrives is then genuinely a different conversation as far as the headers are concerned.

A broken thread is a nuisance rather than a loss. Both halves are canonical records, both are attached to the same person once resolution has run, and both appear on that person's history. What is lost is the automatic ordering of one against the other, which is exactly the thing a person can supply and a header cannot.

## Questions

### Can I merge two threads?

Threading is inferred from what the messages carry, and this documentation does not record a merge action on threads. What already unifies the two is the person: both conversations appear on their history regardless of how the headers resolved.

### Does changing the subject start a new thread?

Not by itself if the reply still carries its reference headers — the subject is the weakest of the signals. A message with no reference headers and a new subject is a new conversation on every reasonable reading.

### Which mailbox does a reply on an old thread go from?

The mailbox the thread belongs to. That was settled when the conversation started and is not chosen per message, which is why [The primary mailbox](/docs/email/primary-mailbox/) only governs messages that start something.

## Related

- [From a provider message to a canonical one](https://connectbyjbrh.com/docs/email/message-canonicalisation/)
- [Resolving a sender to a person](https://connectbyjbrh.com/docs/email/contact-resolution/)
- [Triage and priority](https://connectbyjbrh.com/docs/email/triage/)
- [Blocking a sender](https://connectbyjbrh.com/docs/email/blocking/)
- [Why the machine proposes a merge and a person disposes](https://connectbyjbrh.com/research/one-person-many-channels/)

## What this page is based on

- Connect source pack — channels, §1: triage on canonical rows; `threads.priority` read by the engine; blocking as a tagged memory row
- Connect source pack — the data model: canonical `threads`/`messages`/`contacts` (`docs-source/sources/GENERAL.md` §3)
- Connect capability registry (`docs-source/facts.py`) — `block_directive`, `memory_tiers`
