Issue Description
Each SpamTrainingSample keeps its message through a temporary blob link keyed by blob hash, account and until, without the sample id. until is the current day’s midnight plus holdSamplesFor, so two samples of the same message created on the same day, for example when a message is moved to Junk and back, share one link.
When training removes the duplicate sample, it clears that link. It is also the link of the sample that is kept, so once the message is deleted and its blob purged, the kept sample has no content. Deleting a sample explicitly through the API clears the same shared link.
Code (v0.16.24):
- crates/store/src/write/key.rs L293-296: the key of BlobLink::Temporary is hash, account_id and until.
- crates/email/src/message/ingest.rs L931 and L948-951: until = midnight + hold_samples_for, and the sample’s link is BlobLink::Temporary { until } on the message blob.
- crates/spam-filter/src/modules/classifier.rs L250-256 and L884-891: a duplicate sample is removed with its BlobLink::Temporary { until } cleared.
- crates/jmap/src/registry/mapping/spam_sample.rs L202-207: explicit deletion clears the same link.
Expected Behavior
Removing one sample leaves the blob of any other sample of the same message available for its retention period.
Actual Behavior
After the duplicate is removed, the remaining sample has no blob link. When the message is deleted and its blob purged, the remaining sample’s content returns 404.
Reproduction Steps
- Deliver a message to a mailbox with the spam classifier enabled.
- On the same day, move it to Junk and back to Inbox: two SpamTrainingSample objects are created with the same expiresAt.
- Run the spam filter training task: one sample remains.
- Before that sample’s expiresAt, delete the message, with no other copy referencing its blob, and run the blob purge: the sample still exists, and downloading its blob returns 404.
- Control: with a single move, the sample’s blob survives the message’s deletion.
Stalwart Version
v0.16.x
Installation Method
Binary (Linux)
Database Backend
RocksDB
Blob Storage
RocksDB
Search Engine
Internal
Directory Backend
Internal
Additional Context
One option could be a temporary link specific to each sample, which would keep expiry and automatic cleanup unchanged.
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