Searches using meilisearch are not field-scoped

Issue Description

When using the Meilisearch backend, searching stalwart for a message scoped to a specific field returns messages matching the query in any field.

Expected Behavior

Searches scoped to a specific field only return matches on that field.

Actual Behavior

Searching on "from": "[email protected]" returns every message where [email protected] appears in any searchable field (from, to, cc, subject, body, etc.), instead of only the messages where [email protected] is in the from field.

Reproduction Steps

  1. Configure stalwart with meilisearch backend.
  2. Send a message from [email protected] to a stalwart mailbox.
  3. Send a message from [email protected] to the same stalwart mailbox, with [email protected] in another part of the message (to, cc, subject, body, etc).
  4. Search for messages "from": "[email protected]" in the mailbox using curl/jmap.
  5. Both messages appear in the result set, instead of just the first.

Stalwart Version

v0.16.x

Installation Method

Binary (Linux)

Database Backend

RocksDB

Blob Storage

Filesystem

Search Engine

Meilisearch

Directory Backend

Internal

Additional Context

Meilisearch recognizes an attributesToSearchOn in its query parameters, but stalwart never seems to set this anywhere in crates/store/src/backend/meili/search.rs.

Instead, build_query seems to drop the target field for text conditions, merging from/to/cc/subject/body into one shared q. query() then sends that q without any attributesToSearchOn, so it searches all searchable attributes.

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

Let me look at the relevant source code to understand the issue.

Your analysis is correct and confirmed by the source code. The bug is real and clearly visible in the implementation.

Here is exactly what happens in crates/store/src/backend/meili/search.rs:

In build_query (lines ~185-208): when a SearchFilter::Operator targets a field where field.is_text() is true and the operator is Equal or Contains, the field name is silently discarded. The value is inserted into queries: AHashSet<String> with no record of which field it came from.

In query (lines ~82-85): the collected queries are joined into a single q string and sent to Meilisearch with no attributesToSearchOn parameter. Meilisearch therefore searches across all searchable attributes, which is why from: "[email protected]" returns hits where that address appears in to, cc, subject, body, etc.

The fix, as you identified, is to track which field(s) each text term belongs to and include attributesToSearchOn in the Meilisearch request body per term group. Because attributesToSearchOn is a single array per request, queries mixing text conditions on different fields would need to be split into multiple Meilisearch requests (or restructured so each distinct field-scoped text filter becomes a separate sub-query whose results are intersected).

This is a genuine bug in the Meilisearch backend’s field-scoped text search handling. A human maintainer will need to assess the fix approach (single-request with attributesToSearchOn, or multiple requests + intersection).

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

This will be fixed in v0.16.12 but also read about the known Meilisearch limitations at Meilisearch | Stalwart