Mail sent from a group or shared identity (team@example.org) is created
in that account, together with its submission, on Stalwart 0.16 and 1.0
alike. Undo send and "Cancel and edit" cancelled the submission there,
but then put the message back in Drafts, read it back and later removed
it through the user's own account. Email ids repeat across accounts - on
the test servers a new team message and a message in userb's inbox were
both `iaaaaac` - so an unrelated own message was moved to Drafts, opened
in the composer and destroyed on Discard, while the undone message stayed
in the group's Sent.
The account the message was sent from now travels with it:
- `restoreEmailToDraft` takes the account, the store looks up that
account's Drafts by role, and the restored draft is read from there
- the composer keeps the draft's account (`draftAccountId`); saving,
sending and discarding remove the old version in that account, and the
first save moves the draft to the own Drafts, where drafts are saved
- discarding goes through the login the draft was saved with, as the
send does (#461)
- "Edit draft" on a message in a shared or group folder, in the standard
and the Pro views, opens it with that folder's account, so a draft left
in the group's Drafts is not removed by id in the own account either
- S/MIME copies, and the unused `cancelScheduledEmail`, are deleted in
the submission's account
Stalwart 1.0 counts every EmailSubmission record of an account against
`maxSubmissions` (500 by default), whatever its status or age, and its
own purge of old records never runs. Bulwark never destroyed a
submission, so after about 500 JMAP sends an account could not send at
all: every send, read receipt and reschedule failed with a raw "There
are too many email submissions" error, and each failed attempt left one
more copy of the message in Drafts.
Bulwark now removes the records that can no longer be cancelled.
Destroying a record never stops its delivery, but a pending one could
no longer be cancelled, so pending records are never touched:
- an immediate send (no hold, no undo window) destroys its own record
right after the send, riding along with the old-draft cleanup; only
where `urn:stalwart:jmap` is advertised, and never decided by the
created `undoStatus`, which 0.16.10-0.16.16 report as final for held
sends
- a throttled background prune destroys canceled records and final
records whose `sendAt` has passed, going by `EmailSubmission/get`, in
every submission account; it runs at startup, after sends, after
undo and on EmailSubmission state changes
- a confirmed cancel (undo, cancel, reschedule) destroys the canceled
record in a request of its own
- an `overQuota` on a submission create prunes at once, on Stalwart
walks the change log for records older than the query's three-day
window, retries once, and otherwise throws `SubmissionQuotaError`,
shown as a translated explanation in the composer, quick reply, read
receipts and the scheduled-send actions
Failed sends now clean up after themselves and say so:
- a failed send removes the Drafts copy it created and keeps the
user's draft; the raw send paths remove their import the same way
- a read receipt that did not go out is taken back out of Sent, a
method-level error counts as a failure, `$MDNSent` is only set after
a sent receipt, and the automatic receipt no longer retries on every
refresh of the open message
- quick-reply failures, "Send now", "Reschedule" and a failed undo are
shown instead of only logged
- the scheduled list reads every page of `EmailSubmission/query`; it
stopped after the first because `total` needs `calculateTotal`
Sending mail through Bulwark could leave the delivered message stuck in Drafts
(keeping the $draft keyword) and never file a copy into Sent, with no error
shown, for accounts whose Drafts/Sent mailbox JMAP id is a purely-numeric
string (e.g. "0").
The post-send Drafts->Sent move is expressed as onSuccessUpdateEmail on
EmailSubmission/set using `mailboxIds/<id>` JSON-Pointer patches. Stalwart
up to 0.16.4 (observed on 0.15.5) rejects an Email/set PatchObject whose
pointer token is all digits -- e.g. `mailboxIds/0` -- with invalidProperties
"Invalid patch value", treating the token as a JSON-Pointer array index even
though mailboxIds is a JSON object (cf. RFC 6901 section 4; RFC 8620 section
1.2 warns servers against such interop-hostile ids). Because the move runs
only AFTER the EmailSubmission already succeeded, the message is delivered but
the filing update is silently rejected: the send code inspects only
`notCreated`, not the onSuccessUpdateEmail `notUpdated` result, so nothing
surfaces to the user.
Stalwart fixed the pointer parsing server-side in 0.16.5
(stalwartlabs/stalwart@175f34ea, jmap-tools 0.1.4 -> 0.1.5; a sibling symptom
was stalwartlabs/stalwart#2985). The client-side change is still worthwhile:
earlier Stalwart deployments remain in the wild, and a full-property
replacement both states the actual intent of the move and emits no per-id
pointer token that another server could mishandle.
Replace the per-id pointer patches at every post-send / undo-send move site
(send, scheduled send, raw-import send, reschedule, and restoreEmailToDraft)
with a full `mailboxIds` property replacement via a new mailboxIdsReplacement()
helper. This states the actual intent -- after the move the message should
belong to exactly the target mailbox -- and is immune to the pointer-token
bug. Every one of these sites moves a message that Bulwark itself placed
solely in Drafts (or, for undo, in Sent), so the replacement is
behaviour-equivalent. Note it is a replacement: a membership added to the
message by another client between creation and send is not preserved.
restoreEmailToDraft now always lands the message in Drafts only (previously,
when no Sent mailbox id was passed, it left the Sent copy in place); the demo
client is aligned with the same contract.
Add regression tests for the full-replacement shape, a numeric ("0") Drafts id,
and restoreEmailToDraft.
Follow-up (not included here): the send paths still ignore the implicit
Email/set `notUpdated` result of onSuccessUpdateEmail, so any other post-send
filing failure would remain silent.