JMAP onSuccessDestroyEmail / onSuccessUpdateEmail keyed by an existing EmailSubmission id act on an unrelated Email

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

  1. Have a few messages in the account, so that low document numbers are in use.
  2. Create a draft and submit it with a delay (HOLDFOR in the envelope), so the submission stays pending. Note the submission id.
  3. Call Email/get with the submission id as the only id. It returns some other message. That is the one that will be hit.
  4. Cancel the submission and pass its id in onSuccessDestroyEmail.
  5. 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

Fixed in v0.16.26 (yet to be released).