Issue Description
RFC 8621 lets onSuccessDestroyEmail and onSuccessUpdateEmail be keyed by the id of an existing EmailSubmission, for example to cancel a scheduled send and delete or move its draft in one call. Stalwart passes such a key unchanged to the implicit Email/set, as if it were an Email id. Email/set only looks at the document number of an id, so it destroys or patches whichever Email has the same document number as the submission. The submission’s own Email is left alone.
Both counters start near 0 in every account, so on a real mailbox this almost always hits a live message. The implicit Email/set response echoes the submission id under destroyed, so the client cannot tell which message it lost. A destroy does not go through Trash.
Keys in the #creation form, for a submission created in the same call, work correctly.
A second problem in the same place: the implicit Email/set runs whenever anything in the EmailSubmission/set call changed, also for a key whose own update failed.
Expected Behavior
The implicit Email/set acts on the Email that the submission’s emailId points to, and only for submissions whose create, update or destroy succeeded.
Actual Behavior
Submission bw is held with HOLDFOR. Its Email is qaaaaaea (“held 50”). An unrelated Inbox message gyaaaabw (“filler 0”) has the same document number as the submission.
["Email/get", {"accountId": "l", "ids": ["bw"], "properties": ["subject"]}, "0"]
<- "list": [{"subject": "filler 0", "id": "bw"}]
["EmailSubmission/set", {"accountId": "l",
"update": {"bw": {"undoStatus": "canceled"}},
"onSuccessDestroyEmail": ["bw"]}, "0"]
<- ["EmailSubmission/set", {"accountId": "l", "newState": "sumba", "updated": {"bw": null}}, "0"],
["Email/set", {"accountId": "l", "oldState": "sueba", "newState": "suqba", "destroyed": ["bw"]}, "0"]
Afterwards “filler 0” is gone and qaaaaaea “held 50” still exists.
The other cases:
case result
onSuccessDestroyEmail, existing submission id unrelated Inbox message destroyed, own draft kept
onSuccessUpdateEmail, existing submission id, unrelated Inbox message moved to Drafts
patch: mailboxIds = Drafts, keywords = $draft and marked $draft
onSuccessUpdateEmail for a key whose update failed patch still applied (to the unrelated message)
with cannotUnsend, another submission changed
onSuccessUpdateEmail keyed "#s", created in the call correct: patches the submission's own draft
Reproduction Steps
- Have a few messages in the account, so that low document numbers are in use.
- Create a draft and submit it with a delay (HOLDFOR in the envelope), so the submission stays pending. Note the submission id.
- Call Email/get with the submission id as the only id. It returns some other message. That is the one that will be hit.
- Cancel the submission and pass its id in onSuccessDestroyEmail.
- The message from step 3 is destroyed. The draft from step 2 still exists.
2. ["EmailSubmission/set", {"accountId": "<acc>", "create": {"s": {"emailId": "<draft id>", "identityId": "<identity>",
"envelope": {"mailFrom": {"email": "<user>", "parameters": {"HOLDFOR": "600"}},
"rcptTo": [{"email": "<user>"}]}}}}, "0"]
4. ["EmailSubmission/set", {"accountId": "<acc>",
"update": {"<submission id>": {"undoStatus": "canceled"}},
"onSuccessDestroyEmail": ["<submission id>"]}, "0"]
Stalwart Version
v0.16.x
Installation Method
Built from source
Database Backend
RocksDB
Blob Storage
RocksDB
Search Engine
Internal
Directory Backend
Internal
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