Commit Graph
23 Commits
Author SHA1 Message Date
Linus Rath a43b55ec25 fix: say that a send failed because the device is offline and the message is still there 2026-10-31 03:52:14 +01:00
Linus Rath db8eb731f1 fix: run the placeholder, attachment and confirm checks before a quick reply goes out 2026-10-31 03:33:16 +01:00
Linus Rath 893f40a21d fix: send group mail through the group's identity on Send now and quick reply 2026-10-29 04:40:19 +01:00
Linus Rath 3eac700aa2 fix: keep the draft and say so when the server does not confirm a send 2026-10-29 03:04:14 +01:00
Linus Rath 4e57b19112 fix: keep attachments, sends and reply flags of an open draft out of a login switched to meanwhile 2026-10-29 02:59:42 +01:00
Linus Rath e81f00714c fix: undo and send now of a delayed send only reach the login it was sent from 2026-10-29 02:56:19 +01:00
Linus Rath f1db6b488a Merge branch 'main' into feat/stalwart-1.0 2026-10-29 01:30:03 +01:00
Linus Rath b048eaca03 fix: tint unread mail and draw the mail search, subject, quick reply and thread buttons like the webmail 2026-10-15 15:04:00 +02:00
Linus Rath 75299244cf refactor: draw the webmail's Tabler icons instead of Lucide 2026-10-15 14:13:02 +02:00
Linus Rath c8434b0610 fix: make small buttons easier to hit 2026-10-15 03:30:00 +02:00
Linus Rath bfa02f1177 feat: let the app rotate and work in landscape
The app was locked to portrait in app.config.js and the hand-kept
AndroidManifest.xml. Android 16 ignores that lock on screens 600 dp and
wider, so tablets and foldables rotated into layouts that were never
tested. This removes the lock in both places, keeps React state across
rotation and split-screen resizes (`smallestScreenSize` joins
`configChanges`), and fixes what broke in landscape:

- the thread pager sizes its pages from its own width and snaps back to
  the open message after a rotation instead of showing half a neighbour
- compose and the thread view share one keyboard tracker; a rotation
  with the keyboard open closes it, since Android sends no new keyboard
  frame and the old portrait one blanked the whole landscape composer
- in a short window with the keyboard up the composer hides the format
  bar and tightens its header so a few lines of text stay visible
- inputs with suggestions or context around them opt out of Android's
  full-screen landscape keyboard
- month views fit whole rows to the real height, keep the month across a
  remount, and the month dropdown re-measures and caps its height
- the week grid's header follows the remounted list after a rotation
- the event sheet's actions stay on screen in landscape
- every RN Modal allows all orientations, or iOS forces portrait
2026-10-15 01:45:23 +02:00
arnauda-gh 5237d9004d fix(viewer): adapt screen and auto-scroll quick reply box when keyboard appears 2026-10-09 15:00:51 +02:00
Linus Rath 7326fec611 fix: stop reporting mail as sent in the app when the server refused its recipients
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.
2026-09-26 00:51:59 +02:00
Linus Rath fe0facfea5 fix: keep sending working in the app past Stalwart 1.0's submission limit
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`.
2026-09-25 19:46:15 +02:00
Linus Rath 13cba64854 fix: translate the viewer's leftovers and label its icon buttons
The message details panel built its received line, attachment summary
and spam score by gluing strings together; each is now one ICU message
(the attachment count is a proper plural). The SPF/DKIM/DMARC results
use the webmail's translated result names, and an invitation's date is
formatted in the app language instead of always in English.

The quick reply box wrote its own "date, name <email>:" quote header;
it now uses the composer's translated "On {date}, {from} wrote:" line
so both kinds of reply look the same.

The back button, the subject and header star buttons, the sender
avatar and the More/Tag sheet close buttons get screen reader labels,
and the screens i18n test covers the viewer files.
2026-09-24 21:44:46 +02:00
Linus Rath e3501f939d fix: offer undo for replies sent from the viewer
A quick reply held by the undo-send delay could not be undone: the
undo bar lived only on the mail list, and the quick reply never
recorded the held send for it.

- The undo bar is now hosted on every stack screen through the
  navigator's `screenLayout`, next to the toasts, and only the focused
  screen's renders, so it shows wherever the send started and never
  twice. The list's own instance is gone; archive, delete, move and
  spam undo use the same bar as before. The composer gets none, since
  its Undo reopens the composer.
- The quick reply records a held send through the same store action as
  the composer, including the account it went out of, so Undo and
  Send now reach a shared account's submission and Undo restores the
  reply into that account's Drafts.
- The quick reply dismisses the keyboard after sending, which would
  otherwise cover the bar.
2026-09-24 21:16:59 +02:00
Linus Rath 3faac98627 fix: sign quick replies with the sending identity's signature
Quick replies went out with no signature at all. They are now signed
the way the composer signs a plain-text reply: between the reply and
the quote or at the end, following the signature position setting, with
the "-- " separator when it is on, and with the primary identity's
signature when the sending alias has none of its own.

The quick reply also resolves its sending identity like the composer
(webmail 4fc7b8b5): recipients in To, Cc, Bcc order, and a reply to our
own message from the identity that sent it. It still never takes the
catch-all From rewrite, having no From row to show it.
2026-09-24 19:10:45 +02:00
Linus Rath 9e95273c14 feat: tell the user when a viewer action fails and when mail is sent
The viewer fired its store actions with `void` and went back to the
list, so a refused delete, archive, move, spam report, star, tag or
read change failed silently, and nothing confirmed a sent message.

- each viewer action now reports a failure as an error toast, and a
  refused star, tag or read change is taken back on screen
- the viewer mounts its own toast host, since the list's sits under it
- an immediate send from the composer or the quick reply box shows
  "Email sent"; a send held for the undo delay keeps its undo bar
  (webmail `ec67ab99`, `b03a0c1d`)
2026-09-24 19:06:55 +02:00
Linus Rath 5c7301b3f1 fix: stop viewer actions from hitting another account's message with the same id
Stalwart ids are only unique per account. When its opener named no
account, the viewer used the account of the folder open in the list,
and it resolved Trash, Junk and move targets from that folder as well.
Deleting a team message from the Unified Inbox therefore sent
`Email/set` to the user's own account and trashed an unrelated message
with the same id, and a message opened from a notification, deep link
or contact while a shared folder was open went to the shared account.

- the list names the open folder's account when it opens the viewer,
  and the viewer takes the account from its opener only, fixed at open
- Trash, Junk, Inbox and move targets come from the message's account,
  and delete and move take the message out of its own folder
- the store's delete and move take the viewed message: they only touch
  the list's row when the list shows that account, and refuse folders
  of any other account
- another account's message is marked read on the server directly
  instead of through the open folder's list
- quick reply, unsubscribe and read receipts file into the message's
  account's folders
- undo no longer slots another account's message into the open list
- Archive is only offered for mail of the open folder's account, since
  it still resolves the archive folder from there
2026-09-24 19:06:54 +02:00
Linus Rath 4e7ae2fa30 fix: only offer scheduled-send times the server accepts
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.
2026-09-24 19:00:08 +02:00
Linus Rath e28e8b4d9c fix: turn scheduled send and the undo-send delay back on for Stalwart #57
RFC 8621 puts the submission limits (`maxDelayedSend`,
`submissionExtensions`) and the mail limits
(`maxSizeAttachmentsPerEmail`) under each account's
`accountCapabilities`. Stalwart advertises an empty object for them at
session level, which is the only place the client looked, so scheduled
send was refused, the undo-send delay silently fell back to an instant
send, the Scheduled view listed nothing and the per-message attachment
cap was never enforced.

The client now reads these from the account that sends (the account's
own submission capability, else the session's primary submission
account, like the webmail), falling back to the session-level object
for servers that only advertise it there. Quick reply, which can send
from a shared account, now checks that account before holding the
message for the undo-send delay.
2026-09-24 18:43:56 +02:00
Linus Rath a222e89611 refactor: merge the quick reply's email-body imports 2026-08-29 18:40:42 +02:00
Linus Rath d2ed27fd3b feat: conversation view with collapsible cards, header details, auth chips, unsubscribe and read-receipt banners, attachment preview and zip, embedded-message and TNEF unwrap in the reader (#480, #830, #891) 2026-08-29 18:38:53 +02:00