Issue Description
Two problems in Email/query full-text matching, both easy to hit from any client search box:
- A multi-word
textcondition only matches when all words are in the same field. A word from the subject plus a word from the body finds nothing, while the same two words as twotextconditions underANDfind the message. - The subject is indexed without its reply/forward prefix, but the query keeps it. Any
subjectortextquery that contains “Re:” or “Fwd:” (for example a user pasting a subject line) finds nothing.
Expected Behavior
RFC 8621, section 4.4.1:
text: “Looks for the text in Emails. The server MUST look up text in the From, To, Cc, Bcc, and Subject header fields of the message and SHOULD look inside any text/* … body parts”.- “Outside of a phrase, white space SHOULD be treated as dividing separate tokens that may be searched for separately but MUST all be present for the Email to match the filter.”
So "zq17925 quarterly" must match an Email whose subject has zq17925 and whose body has quarterly. And subject: "Re: Budget zq17925" should match the subject “Re: Budget zq17925 planning”.
Actual Behavior
Message used (imported with Email/import):
From: Probe <[email protected]>
To: [email protected]
Subject: Re: Budget zq17925 planning
Content-Type: text/plain; charset=utf-8
Please review the quarterly numbers before the meeting.
Filter (combined with inMailbox) |
Result |
|---|---|
{"text": "zq17925"} |
found |
{"text": "quarterly"} |
found |
{"text": "zq17925 quarterly"} |
not found |
{"operator": "AND", "conditions": [{"text": "zq17925"}, {"text": "quarterly"}]} |
found |
{"subject": "Budget zq17925 planning"} |
found |
{"subject": "Re: Budget zq17925"} |
not found |
{"text": "Re: Budget zq17925"} |
not found |
{"subject": "\"Re: Budget\""} |
not found |
{"subject": "Fwd: Offsite xk17925"} (subject “Fwd: Offsite xk17925”) |
not found |
{"subject": "Offsite xk17925"} |
found |
Reproduction Steps
curl -s -u "[email protected]:$PASS1" -H 'Content-Type: application/json' https://mail.example.com/jmap/ -d '{
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
"methodCalls": [["Email/query", {"accountId": "c", "filter": {"operator": "AND", "conditions": [
{"inMailbox": "a"}, {"text": "zq17925 quarterly"}]}}, "q"]]}'
{"methodResponses": [["Email/query", {"accountId": "c", "queryState": "...", "canCalculateChanges": true,
"position": 0, "ids": []}, "q"]]}
With {"text": "zq17925"} instead, ids contains the message. The index was given 12 seconds before querying; single-word queries on the same message succeed, so indexing had finished.
Stalwart Version
v0.16.x
Installation Method
Docker
Database Backend
RocksDB
Blob Storage
RocksDB
Search Engine
Internal
Directory Backend
Internal
Additional Context
Where it seems to come from:
crates/jmap/src/email/query.rs,EmailFilter::Text: the whole string goes into onehas_text()per field inside a singleOr(From, To, Cc, Bcc, Subject, Body, Attachment). Eachhas_text()requires all tokens in that one field. Splitting the string into tokens/phrases first and buildingAND(token1 in any field, token2 in any field, ...)would follow the RFC.crates/email/src/message/index/search.rs: the Subject is indexed asthread_name(subject), which strips “Re:”, “Fwd:” and similar. Thesubjectandtextquery strings are not passed throughthread_name(), so the “re”/“fwd” tokens can never match. Either index the full subject or strip the same prefixes from the query.
Stalwart v0.16.27, Docker image stalwartlabs/stalwart:v0.16-alpine, default setup from the bootstrap wizard.
I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.
on
I have searched this support forum (open and closed topics) and confirm this is not a duplicate.
on
I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.
on