Skip to main content

threadweave

The canonical JWZ email message-threading algorithm for Python — pure stdlib, zero runtime dependencies.

threadweave turns a flat list of email messages into conversation trees using Jamie Zawinski's threading algorithm, built on top of RFC 5322 §3.6.4 identification-field parsing (Message-ID, References, In-Reply-To). It is the algorithm every serious mail client uses to group a mailbox into threads.

It exists because the obviously-wrong approaches — grouping by subject alone, or naively chaining In-Reply-To — mis-thread real mail: subjects collide across unrelated conversations, references arrive out of order, roots go missing, and malformed headers form reference loops. JWZ handles all of these; threadweave implements it faithfully and loop-safely.

What it does

  • Builds the JWZ id-table of containers and links References chains without creating loops and without overriding a message's good existing parent.
  • Recovers missing roots as empty placeholder containers, then prunes empty containers correctly (nuking childless empties and splice-promoting the children of empty ones, with the special root-level single-child handling).
  • Optionally groups the root set by base subject (Re:/Fwd:/Fw: stripped) — a heuristic, off by default.
  • Terminates on adversarial input: self-references and mutual reference cycles never loop or crash.

Install

pip install threadweave

No runtime dependencies — only the standard library (re, hashlib, dataclasses, typing).

Quickstart

from threadweave import Message, thread_messages

threads = thread_messages([
    Message(message_id="a", subject="Deploy plan"),
    Message(message_id="b", in_reply_to="a", references=["a"], subject="Re: Deploy plan"),
    Message(message_id="c", references=["a", "b"], subject="Re: Deploy plan"),
])

assert len(threads) == 1            # one conversation
root = threads[0]                   # a Container
print(root.message.message_id)      # "a"
for node in root.iter_descendants():
    print(node.message.message_id)  # "b", then "c"

Group unrelated-but-same-subject roots when references are missing:

threads = thread_messages(messages, group_by_subject=True)

API

Symbol Purpose
Message Input dataclass: message_id, in_reply_to, references, subject, payload.
thread_messages(messages, *, group_by_subject=False) Run the JWZ algorithm; returns the root Container list.
Container Thread-tree node: message, parent, children, is_empty, add_child, iter_descendants.
normalize_message_id / extract_reference_ids RFC 5322 Message-ID / References parsing.
generate_email_fingerprint Deterministic SHA-256 identity for messages lacking a usable Message-ID.
normalize_subject / is_reply_subject Base-subject stripping and reply detection.

Container.iter_descendants and the internal linking are loop-safe: traversal visits each node at most once, so a cyclic reference graph can never hang.

One source, multi use (OSMU)

The RFC 5322 header primitives in threadweave/headers.py are extracted behaviour-preserving from a production control plane (naruon), where they normalize Message-ID/References headers for canonical email threading. The JWZ assembly here is a fresh canonical implementation built on top of those primitives — one source, usable both as a standalone dependency and as a git submodule.

Research grounding

See docs/research: Zawinski's threading algorithm (https://www.jwz.org/doc/threading.html) and RFC 5322 §3.6.4 (Identification Fields). Base-subject grouping is documented there as a heuristic fallback.

License

Apache-2.0. See LICENSE.

Metadata

Release files for threadweave 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for threadweave 0.1.0
File Size Uploaded
threadweave-0.1.0.tar.gz 15.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for threadweave 0.1.0
File Interpreter ABI Platform
threadweave-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 30.5 kB

Release files / threadweave-0.1.0.tar.gz

Download URL threadweave-0.1.0.tar.gz
Size 15.5 kB
Tags Source
SHA-256 checksum
How to use checksums
8ee7c01bf3855fa12ed0d28cbec4f4ba31c405801b535d2931c5870dfe467f98
BLAKE2b-256 checksum
How to use checksums
2b82878a0a39183e2e0e084c875d77160dd697f06f013d231ef5dbdf57865d8c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.13

Release files / threadweave-0.1.0-py3-none-any.whl

Download URL threadweave-0.1.0-py3-none-any.whl
Size 15.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
03c31fa21873a9493687d81eab4ec067bf169dade7cff077b80df46fd0db3aaf
BLAKE2b-256 checksum
How to use checksums
60e0ffbc0d61d68304602120998a5d660c8108464064bdedc814dc4be8410425
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.13

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page