Stalwart runs the SMTP RCPT stage while it creates a submission and
records a refused recipient as `delivered: "no"` with its reply: a relay
it doesn't allow ("550 5.1.2 Relay not allowed.") or a used-up throttle
("452 4.4.5 Rate limit exceeded, try again later."), on 0.16 and 1.0
alike. The create still succeeds and onSuccessUpdateEmail files the
message in Sent. The app read only the new record's id and sendAt, said
"Email sent", and on Stalwart destroyed the record right after, the only
trace of the refusal. The message was lost without a bounce. Port of
the webmail's e0b0dfe0.
- the send reads the new submission's delivery status in the same
request (`EmailSubmission/get` of `#sub-1`, its creation id; the
create's own answer has none), also with the retry after a full
submission quota, before the record is destroyed
- every recipient refused: the send fails with the server's reply per
address, the copy filed in Sent is removed and the previous draft
kept, so the composer or the quick reply keeps the text and the
address can be fixed; held sends (undo send, scheduled) are refused
when they are created, so this covers them too
- some recipients refused: the others get the message, and a warning
names the refused ones with their replies
- "Send now" and "Reschedule" check the replacement the same way and
keep the original, which still goes out at its old time, when every
recipient of the replacement is refused; a partial refusal there gets
the warning as well
- the submission prune leaves a shared account that refuses its records
with `forbidden` alone while the app runs, for that login, with a
debug log instead of a warning, and the scheduled list skips it:
0.16.23 lists an account that only shared an address book or a
calendar with the submission capability
`RecipientsRefusedError` and `RefusedRecipient` are added to
`jmap-result.ts`. Five new strings in all 27 languages, four of them
with the webmail's translations.
"Send now" and "Reschedule" at a full submission quota (Stalwart 1.0)
made room and then created the replacement again. Making room can take
a while - a prune, a walk through the change log - and the original
could go out meanwhile, so the replacement sent it a second time, with
a HOLDFOR that still counted from before the wait. The original is now
read again first: nothing is resubmitted unless it is still pending
("This message was already sent" or "no longer scheduled" otherwise,
and the undo bar lets go of it), a failed read changes nothing, and the
hold is worked out again from the time the user picked. Port of the
webmail's 65eb4b10.
- the Stalwart explanation of a full quota (Bulwark removed what it
could, an administrator has to raise the limit) needs
`urn:stalwart:jmap` on the submission account; another server's
`overQuota` gets a generic sentence with its own description
- when making room finds nothing that can go, the account is not
searched again for five minutes; each send, reschedule and automatic
read receipt at a full quota used to run the prune, the change-log
walk and the gets again
- unsubscribing by mail at a full quota explains the quota instead of
only saying "Unable to unsubscribe"
- push on Stalwart 1.0 (`principals:owner` on the account) takes the
emailpush capability as the answer to which accounts notify: a group
with only mail no longer counts as unreachable, and a refusal
remembered under 0.16 no longer keeps a group out of the map
- a push re-registration the user asked for at the server's
subscription limit removes the old record first and tries once more;
the old record counted against the limit, so it could never be
replaced
- "Mark folder as read" on a folder and its subfolders goes on past a
folder the server refuses and reports the refusal at the end; the
folders and the list refresh either way
- marking, starring or tagging a selection writes every account before
reporting a refusal and shows only the messages the server updated;
one refusing account or message used to leave the rest unwritten,
and what was written looked unchanged
- a new photo for a contact opened from the cache is not saved when the
card's media can't be read from the server either; it would have
replaced the card's other media (a logo, more photos)
Stalwart refuses a push subscription's whole emailPush map as a
`forbidden` SetError when it names a mailbox shared by ACL (0.16.16 and
later, and 1.0), and 1.0 stops listing emailpush on such accounts. The
app narrowed the map on any `forbidden`, including a refused method
call, and put every account with folders in the first attempt, so 1.0
cost one refused call per install. Worse, a failed listing counted as
"the server has no subscription" and dropped the key of a working
registration, and any failed refresh - a network error, a timeout, rate
limiting - started a replacement.
- `jmap-result` gains `JMAPSetError` (a `JMAPMethodError` with the same
name, carrying `properties`) and `isJMAPSetError`. `assertSetResult`
throws it, so every refused object can be told apart from a refused
call; existing `instanceof` checks and the outbox's name-based error
classification are unchanged.
- The emailPush map holds the primary account and the accounts whose
`accountCapabilities` list `urn:ietf:params:jmap:emailpush`, which
leaves ACL shares out up front on 1.0. On 0.16.16-0.16.23 the refused
map still narrows to the accounts that look owned, then to the
primary account, but only on a `forbidden` SetError.
- A subscription ends only with a verified replacement, a user disable
or the server's verdict: a failed listing aborts the run, and only a
SetError on the refresh starts a replacement. Other failures leave
the subscription and its key alone and the next run tries again.
- `forbidden` and `overQuota` read as plain text in settings and the
onboarding prompt, and settings says that mailboxes others share
don't send background notifications. New strings in all 27 languages.
Stalwart 1.0 returns every photo, logo and sound in a contact card as a
blobId without a uri, including photos saved as data: URIs. The app read
only `uri`, so sender avatars, the contact list, the detail screen, the
form preview and vCard export showed nothing. Worse, the form rebuilt
`media` from the card on every save and sent it without the photo, or
as `null`, so editing a phone number deleted the photo on the server.
- `ContactMedia` carries `blobId`, `label`, `pref` and `contexts`, and
`uri` is optional. The photo shown is the photo entry with the lowest
`pref`, as a URI (0.16) or a blob (1.0).
- Blob photos load from the download URL with the Authorization
header, like file thumbnails. At most half of the server's
`maxConcurrentRequests` load at once, a failed one is tried once more
and then left alone for two minutes, and one that loaded skips the
queue from then on.
- The form leaves `media` out of the save unless the photo changed. A
changed photo is built on the card as the server has it now, so a
stale blobId can't make 1.0 refuse the update, and a new photo keeps
the key, `pref`, `label` and `contexts` of the one it replaces.
- A new photo is uploaded into the user's own account and sent as a
blobId where the blob capability lists ContactCard (1.0). Otherwise
it goes as a data: URI, and a refusal like 0.16's "blobIds in media
is not supported." retries once with the data: URI and is remembered
for the account.
- vCard export fetches blob photos with batched Blob/get requests and
writes them inline. It also writes photos whose data: URI names no
media type, which it dropped before. Duplicate and moving a card to
another account send the bytes instead of the source's blobIds.
- An update never clears `id`, `uid` or `addressBookIds`, and vCard
export writes MEMBER values that are already URIs as they are instead
of `urn:uuid:urn:uuid:…`.
The form's state and patch building moved to `src/lib/contact-form.ts`
so the save path can be tested.
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. The app never removed a record, so
after about 500 sends from an account every send, quick reply, read
receipt and "Send now" failed with Stalwart's raw "There are too many
email submissions, please delete some" error. This ports the webmail
fix (48382170).
The app 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 a record EmailSubmission/get reports as
pending is never touched:
- an immediate send (no undo delay, no schedule) and a read receipt
destroy their own record right after the send, riding along with the
old-draft cleanup when there is one; 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 (`src/api/submissions.ts`) destroys
canceled records and final records whose sendAt has passed, going by
EmailSubmission/get, in every submission account; it runs at app
start, on return to the foreground and after sends and undo
- a confirmed cancel (undo, cancel, reschedule, "Send now") destroys
the canceled record in a request of its own
- an `overQuota` on a submission create prunes at once, on Stalwart
walks `EmailSubmission/changes` from "n" 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, the undo bar and the scheduled
list; the copy the failed attempt made is removed as before
0.16 never enforces the limit, and destroy never cancels on any
version, so the same cleanup runs there unchanged.
The undo bar now also says when an undo or "Send now" failed instead
of only logging it. The scheduled list reads every page of
EmailSubmission/query with calculateTotal; it asked for 200 records,
oldest first, so the mail scheduled furthest ahead dropped off once the
three-day window held more than that.
`SubmissionQuotaError` is added to `jmap-result.ts` as a subclass of
`JMAPMethodError`.
Stalwart 1.0 refuses a CalendarEvent/get that asks for
recurrenceOverrides together with utcStart or utcEnd, and every event
read of the app did. The calendar showed a raw error over stale events,
and tasks, reminders, "all events" edits, contact activity, the
invitation banner's UID look-up and iCal subscriptions stopped working.
Subscriptions and imports also lost their UID check, so every sync tried
to create the whole feed again and ended in refusals.
- read a stored event's overrides with a second get in the same
request; server-expanded occurrences get them from their base event
as before
- read only ids, UIDs and calendars for the import and subscription
UID look-ups
- fail a read when either get answers with an error, so the calendar
keeps the events it has and shows the error
- keep the JMAP error type on calendar method errors (JMAPMethodError,
plus a shared jmapErrorType helper in jmap-result)
0.16 accepts the split reads and returns the same events.
Stalwart advertises `maxDelayedSend` as a fixed 30 days, but its MTA
rejects any hold beyond its `futureRelease` limit, 7 days by default.
Now that scheduled send works on Stalwart, a time 8 to 30 days ahead
passed the checks and then failed with the raw "501 5.5.4 Requested
hold time exceeds maximum of 604800 seconds" reply.
Ported from the webmail:
- on Stalwart the advertised 30 days count as 7
- a submission or reschedule refused for its hold time throws
`ScheduleTooLateError`, and the limit it names replaces the advertised
one for that server
- the composer and the Scheduled screen show "Too far ahead" for it
instead of the SMTP reply
The composer's and the Scheduled screen's presets now only list times
within the limit, and the undo-send hold, from the composer and from
quick reply, is capped at it.