Compare commits

..
Author SHA1 Message Date
Linus Rath ec94ec5305 test: tap continue again when the sign-in link alert is still up in the ios flows 2026-10-31 23:46:19 +01:00
Linus Rath f27bbcb345 test: split the ios settings flow in two so each half stays under the flow time limit 2026-10-31 23:46:18 +01:00
Linus Rath 8d2117c39c test: find the selection more button by its full label in the ios move flow 2026-10-31 23:46:18 +01:00
Linus Rath 0b1698d078 test: retype the settings search up to three times when the simulator garbles it 2026-10-31 22:25:53 +01:00
Linus Rath b1d1b5f94d test: open the more menu for star when the tablet selection bar has no room in the ios move flow 2026-10-31 22:25:53 +01:00
Linus Rath 5666b11ecf test: expect the device-neutral wording in the sign-in code error 2026-10-31 20:52:14 +01:00
Linus Rath 76c256dd8e fix: say this device instead of this phone in two sign-in fallbacks 2026-10-31 20:48:55 +01:00
Linus Rath 9caed413d7 Merge branch 'cu-fixsettings' into catch-up-2026-11 2026-10-31 20:47:32 +01:00
Linus Rath 10f0066ed1 fix: name the server when two accounts with one address sync to this device 2026-10-31 20:46:09 +01:00
Linus Rath 3c95bba0d2 fix: give the folder name its own line next to the docked file preview 2026-10-31 20:46:09 +01:00
Linus Rath 34c65765ba fix: call a sharee without a name unknown person in every share sheet 2026-10-31 20:46:09 +01:00
Linus Rath a832f6ca3b fix: say this device instead of this phone, also on a tablet 2026-10-31 20:46:08 +01:00
Linus Rath 2c40bc6709 fix: forget an account's saved sender identities when it is signed out 2026-10-31 20:36:40 +01:00
Linus Rath 5d91243451 Merge branch 'cu-fixmail' into catch-up-2026-11 2026-10-31 20:34:10 +01:00
Linus Rath 2ebe5fc728 fix: open the soft keyboard for the reply body on android 2026-10-31 20:33:11 +01:00
Linus Rath 6a1c40e7eb fix: make the remove cross on a participant chip easier to tap 2026-10-31 20:31:23 +01:00
Linus Rath 81b2a7c485 fix: float the notification prompt in the list column on a tablet, clear of the reader 2026-10-31 20:31:02 +01:00
Linus Rath 3165b40127 fix: toggle a settings switch by tapping anywhere on its row 2026-10-31 20:29:26 +01:00
Linus Rath 1286e6cb82 fix: list the address books after an account switch and mark only the own default 2026-10-31 20:27:02 +01:00
Linus Rath 681e2546f0 fix: lay the global search sheet out from the top 2026-10-31 20:26:34 +01:00
Linus Rath 718fd00c6d fix: hide the pgp/mime control parts of an encrypted message from its attachments 2026-10-31 20:25:33 +01:00
Linus Rath cb0f50198e fix: keep the selection toolbar inside the list column on a tablet 2026-10-31 20:23:05 +01:00
Linus Rath 421fa6592a fix: draw the app text in the chosen reading font in release builds 2026-10-31 20:23:02 +01:00
Linus Rath d20532af91 fix: keep search everything reachable once mail search results load 2026-10-31 20:18:15 +01:00
Linus Rath 67cfe0a97d fix: decrypt and verify signed pgp mail on the device 2026-10-31 20:18:00 +01:00
Linus Rath db1fac4085 fix: read the year on the calendar's month button also in the current year 2026-10-31 20:17:11 +01:00
Linus Rath 93de202b38 fix: let a composer opened offline send from the last known identities 2026-10-31 20:15:59 +01:00
Linus Rath 305b836001 fix: close an untouched all-day event without asking to discard changes 2026-10-31 20:14:38 +01:00
Linus Rath ba67e67339 fix: show a saved reply draft in its conversation without a refresh 2026-10-31 20:12:56 +01:00
Linus Rath 818c45c280 fix: say cancelled when the organizer cancels an event 2026-10-31 20:12:12 +01:00
Linus Rath 3b3a2fc670 fix: show the server where two accounts with one address would read the same 2026-10-31 20:08:58 +01:00
Linus Rath d88d3dfbaf fix: scroll the new tag form above the keyboard in settings 2026-10-31 20:04:47 +01:00
Linus Rath bac51fbc46 fix: tell screen readers which tags the picker has checked 2026-10-31 20:01:07 +01:00
Linus Rath 7c39730616 fix: name the recipient on the composer chip remove button 2026-10-31 19:58:47 +01:00
Linus Rath 62e4806353 fix: save a settings sheet on the first tap while the keyboard is open 2026-10-31 19:57:03 +01:00
Linus Rath b9dcbdea52 test: tap the move menu row by its real label in the e2e move flow 2026-10-31 19:15:52 +01:00
Linus Rath 889e1c02f6 docs: write up the native catch-up with webmail 1.19.1 and stalwart 1.0.1 2026-10-31 19:12:28 +01:00
Linus Rath 835508c0c0 docs: tick the parity rows the catch-up closed 2026-10-31 19:06:32 +01:00
Linus Rath 633574203f feat: translate the key offer and already-sent messages 2026-10-31 18:57:09 +01:00
Linus Rath b188c2dba0 Merge branch 'cu-reviewfix' into catch-up-2026-11 2026-10-31 18:54:03 +01:00
Linus Rath 93c6307027 fix: stop saving pgp keys from messages on their own and offer to save a key only for the sender's address 2026-10-31 18:52:00 +01:00
Linus Rath 2c5d95184e fix: wait for the saved session check instead of loading the account beside it when the network comes back at start 2026-10-31 18:40:49 +01:00
Linus Rath 75213f6a55 fix: send a message queued or retried while the outbox is already sending 2026-10-31 18:32:21 +01:00
Linus Rath ac875ffe1d fix: don't move a queued message to drafts when an earlier attempt already sent it 2026-10-31 18:27:57 +01:00
Linus Rath 1c33f8daef fix: tell the user when a queued message gives up after unconfirmed attempts 2026-10-31 18:24:12 +01:00
Linus Rath e2d4915edc fix: keep a queued message without a drafts folder from passing as sent when its submission failed 2026-10-31 18:24:11 +01:00
Linus Rath b1a0eaf805 fix: turn sender favicons off on upgrade unless they were switched on by choice 2026-10-31 18:15:50 +01:00
Linus Rath 2e90f43181 test: catch the informal "passe an" in german strings 2026-10-31 18:13:53 +01:00
Linus Rath 3c0d7ad505 Merge branch 'cu-i18n' into catch-up-2026-11 2026-10-31 18:11:36 +01:00
Linus Rath a5b40d5a3a feat: translate the remaining app strings into Simplified and Traditional Chinese 2026-10-31 18:10:45 +01:00
Linus Rath 2662ad404e feat: translate the remaining app strings into Russian, Slovak, Thai, Turkish and Ukrainian 2026-10-31 18:10:44 +01:00
Linus Rath d2d6023084 feat: translate the remaining app strings into Norwegian, Dutch, Polish, Portuguese and Romanian 2026-10-31 18:10:43 +01:00
Linus Rath f21f6a126b feat: translate the remaining app strings into Italian, Japanese, Korean, Latvian and Mongolian 2026-10-31 18:10:40 +01:00
Linus Rath 17ed1b2309 feat: translate the remaining app strings into Estonian, Persian, French, Hebrew and Hungarian 2026-10-31 18:10:37 +01:00
Linus Rath cef88b9384 feat: translate the remaining app strings into Arabic, Catalan, Czech, Danish and Spanish 2026-10-31 18:10:35 +01:00
Linus Rath 1194bf284f fix: keep ios push renewal from reporting an extension the server refused 2026-10-31 18:10:20 +01:00
Linus Rath d2d44e0dec fix: stop the app from crashing at launch on android 14 and later 2026-10-31 18:06:42 +01:00
Linus Rath 1aba571ac3 fix: keep a signed or encrypted reply-to name with a comma as one address 2026-10-31 18:05:48 +01:00
Linus Rath 8345be43ef feat: translate the remaining app strings into German 2026-10-31 17:34:49 +01:00
Linus Rath 43bf8da760 test: decrypt pkcs#1 v1.5 in the node stand-in on node releases that refuse it 2026-10-31 17:32:40 +01:00
Linus Rath 9192311df8 fix: drop the composer's second policy import left by the merge 2026-10-31 17:26:57 +01:00
Linus Rath 5ead7a8164 chore: declare the end-to-end encryption for app store export compliance #58 2026-10-31 17:23:19 +01:00
Linus Rath e5027c45aa Merge branch 'cu-pgp' into catch-up-2026-11 2026-10-31 17:20:36 +01:00
Linus Rath 7a06ab03c0 fix: list a signed message's attachments once in the reader #58 2026-10-31 17:19:34 +01:00
Linus Rath be98acdc8b test: send and read pgp and s/mime mail between two accounts on live stalwart #58 2026-10-31 17:13:25 +01:00
Linus Rath b0997c0a13 feat: sign and encrypt mail with pgp or s/mime from the composer options #58 2026-10-31 17:10:01 +01:00
Linus Rath 8aa1d8e03c feat: read pgp and s/mime mail and manage the keys under settings, encryption #58 2026-10-31 17:10:01 +01:00
Linus Rath d9d7a46667 Merge branch 'cu-e2e' into catch-up-2026-11 2026-10-31 16:56:15 +01:00
Linus Rath 69f277e1ce test: move to the webmail password field with safari's next key in the ios sign-in flows 2026-10-31 16:53:40 +01:00
Linus Rath aabfe3f6a7 test: accept the camera pre-prompt wording in the ios scanner flow 2026-10-31 16:52:57 +01:00
Linus Rath da41b3f060 test: scroll sign out clear of the tab bar before tapping it in the ios flows 2026-10-31 16:52:29 +01:00
Linus Rath 6a52715704 test: search settings pages by words that keep their row above the keyboard in the ios settings flow 2026-10-31 16:52:07 +01:00
Linus Rath b6d364f732 test: expect delete to move the folder to the trash without a question in the ios files flow 2026-10-31 16:51:43 +01:00
Linus Rath 1b6384edec test: tap allow on the ios notification prompt once and start the calendar flow's event today 2026-10-31 16:51:23 +01:00
Linus Rath 1769a4aa62 test: customize the default reminders before adding one in the ios calendar flow 2026-10-31 16:50:46 +01:00
Linus Rath 897d4ae32d test: confirm cancel send under the new back-to-drafts message in the ios flow 2026-10-31 16:50:21 +01:00
Linus Rath b75b4faa69 test: expect the upload and attach from files options in the ios compose flow 2026-10-31 16:50:06 +01:00
Linus Rath 8fae4e6398 test: swipe the message row, not its avatar, in the ios swipe flow 2026-10-31 16:49:50 +01:00
Linus Rath 2acce326b7 test: open and select messages by their list row, not the avatar checkbox, in the ios flows 2026-10-31 16:49:40 +01:00
Linus Rath 05bc0f567b fix: give the widget and the notification extension their own copy of the shared keychain code so pod install succeeds 2026-10-31 16:49:09 +01:00
Linus Rath 4c818d64ea chore: provide webcrypto from react-native-quick-crypto instead of webcrypto-liner #58 2026-10-31 16:33:18 +01:00
Linus Rath 22893d327f test: follow the mention target setting in the composer wiring check 2026-10-31 16:30:59 +01:00
Linus Rath 48e427bfa1 Merge branch 'cu-followup3' into catch-up-2026-11 2026-10-31 16:25:05 +01:00
Linus Rath 48a0590a93 fix: describe the client certificate the iOS way on iPhone and iPad 2026-10-31 16:24:09 +01:00
Linus Rath d254d7b5ab fix: block remote content when the webmail allows no third-party traffic and hide address matching without custom From 2026-10-31 16:22:11 +01:00
Linus Rath c66e6209ed fix: keep accessibility presets off settings the organisation locks or limits 2026-10-31 16:13:07 +01:00
Linus Rath bc47c6a1af fix: follow the organisation policy for delegates and recovering deleted mail 2026-10-31 16:10:35 +01:00
Linus Rath ab226b03b4 feat: let @ mentions be turned off and choose where a mentioned contact is added 2026-10-31 16:05:14 +01:00
Linus Rath 051f6bf2d5 chore: add the openpgp and pkijs crypto libraries for pgp and s/mime #58 2026-10-31 16:03:54 +01:00
Linus Rath cfca6b9c24 feat: use the reading fonts for the app's own text on ios 2026-10-31 16:03:54 +01:00
Linus Rath 0ae33f3a07 feat: add a setting to block screenshots and hide the app in recent apps 2026-10-31 16:03:54 +01:00
Linus Rath f9a3b76174 chore: move to the latest expo sdk 54 patch release and refresh the lockfile 2026-10-31 16:02:02 +01:00
Linus Rath b2cd83b188 chore: update zustand, jszip and vitest to their latest patch releases 2026-10-31 16:02:02 +01:00
Linus Rath 38257f81c6 chore: update react navigation to 7.5 and drop its vulnerable url and id helpers 2026-10-31 16:02:01 +01:00
Linus Rath 368c90ce3e fix: stop crafted .eml headers from stalling the parser by moving to postal-mime 4.2 2026-10-31 16:02:01 +01:00
Linus Rath fe3f860e73 feat: add the organisation's signature and disclaimer to new mail and hide templates when they are off 2026-10-31 16:01:16 +01:00
Linus Rath 7e60d32449 fix: let screen readers reach the folder share sheet's loading state 2026-10-31 15:57:10 +01:00
Linus Rath 13dcbdc086 fix: hide every archive action when the organisation turns archiving off 2026-10-31 15:52:37 +01:00
Linus Rath 0865c1d6e7 Merge branch 'cu-tagsx' into catch-up-2026-11 2026-10-31 15:51:50 +01:00
Linus Rath e4295dab22 test: read the search-the-server row from the shared suggestion list 2026-10-31 15:51:50 +01:00
Linus Rath b43472dda4 fix: skip saved accounts without a server when picking an account id 2026-10-31 15:51:50 +01:00
Linus Rath 6b7e752c4b feat: show the sender's BIMI logo in the list, the unified inbox and the reader 2026-10-31 15:50:02 +01:00
Linus Rath f15b7bb3cf feat: search mail, contacts, events and files of every account at once 2026-10-31 15:50:02 +01:00
Linus Rath ff01246c92 Merge branch 'cu-policy2' into catch-up-2026-11 2026-10-31 15:46:19 +01:00
Linus Rath 83ddd13726 feat: show the sender's BIMI logo from the webmail when the message passes DMARC 2026-10-31 15:44:35 +01:00
Linus Rath 609d36b0d3 Merge branch 'cu-compx' into catch-up-2026-11 2026-10-31 15:42:13 +01:00
Linus Rath 44366848d1 test: clean up the live send tests without a subject search 2026-10-31 15:41:17 +01:00
Linus Rath f27e2cebb4 feat: lock or hide the settings the organisation pins or hides 2026-10-31 15:40:05 +01:00
Linus Rath 409ada9428 fix: keep delivery notifications and encrypted delivery when a scheduled send is moved 2026-10-31 15:39:13 +01:00
Linus Rath efb92593b2 feat: take archive out of the swipe and quick action settings when the organisation turns it off 2026-10-31 15:34:06 +01:00
Linus Rath 0ec809eb49 feat: keep mail sent offline in an Outbox and send it once back online 2026-10-31 15:34:02 +01:00
Linus Rath df1e211780 feat: hide settings export and import when the organisation turns them off 2026-10-31 15:31:27 +01:00
Linus Rath 62a6c22f10 Merge branch 'cu-native' into catch-up-2026-11 2026-10-31 15:31:09 +01:00
Linus Rath 1d654bbeaf feat: leave out the settings pages the organisation turns off 2026-10-31 15:30:15 +01:00
Linus Rath fbe5457604 feat: add an iOS inbox widget 2026-10-31 15:29:52 +01:00
Linus Rath 417f1843a2 feat: share a mail folder with other people from its menu 2026-10-31 15:29:35 +01:00
Linus Rath 83981e8b8d feat: hide or lock the external content option as the organisation sets it 2026-10-31 15:26:45 +01:00
Linus Rath 8ba2f27af2 feat: hide calendar tasks in settings when the organisation turns them off 2026-10-31 15:25:37 +01:00
Linus Rath a69bc071d1 feat: hide the themes the organisation turns off and keep a locked theme in place 2026-10-31 15:24:16 +01:00
Linus Rath ba150060cf feat: use TLS client certificates on iOS #3 2026-10-31 15:22:32 +01:00
Linus Rath b7b3dd4c4d feat: show the colour mode as set by the organisation when its policy locks it 2026-10-31 15:22:15 +01:00
Linus Rath 585c70a5db feat: nest tags under other tags and choose which tags the sidebar shows 2026-10-31 15:20:57 +01:00
Linus Rath c704c7e6af fix: keep iOS push alive when the app isn't opened for a week 2026-10-31 15:17:14 +01:00
Linus Rath e1ad5c0945 feat: pick recipients full screen in phone landscape, where suggestions had no room 2026-10-31 15:16:07 +01:00
Linus Rath 2b3e84a5a0 Merge branch 'cu-policy' into catch-up-2026-11 2026-10-31 15:13:58 +01:00
Linus Rath 9d9777738e feat: apply the organisation policy of the webmail an account signed in through 2026-10-31 15:13:02 +01:00
Linus Rath 6a27c59672 feat: play the Involved sound for new mail 2026-10-31 15:10:44 +01:00
Linus Rath 8389db8fcb feat: suggest recipients and contacts when typing @ in the message 2026-10-31 15:08:21 +01:00
Linus Rath 21dd5a2405 feat: draw calendar colours in the colour-blind-safe palette when that setting is on 2026-10-31 15:05:48 +01:00
Linus Rath 6e5980e4e0 Merge branch 'cu-readx' into catch-up-2026-11 2026-10-31 14:59:37 +01:00
Linus Rath 97b71e22f5 feat: offer plain language with simpler English and German wording 2026-10-31 14:58:04 +01:00
Linus Rath 649d3adc0f feat: let the composer set message priority and ask for delivery notifications 2026-10-31 14:57:54 +01:00
Linus Rath ab2e5a60f8 feat: redeem a one-time code from the webmail instead of reading the sign-in from the link 2026-10-31 14:53:57 +01:00
Linus Rath 10fb7ab758 fix: keep a saturday week start after a restart 2026-10-31 14:52:40 +01:00
Linus Rath c6501ec886 feat: draw tag colours in a colour-blind-safe palette when that setting is on 2026-10-31 14:52:39 +01:00
Linus Rath 66b4305e6d Merge branch 'cu-calx' into catch-up-2026-11 2026-10-31 14:51:48 +01:00
Linus Rath 7b249ab9d9 fix: keep a new default-port account apart from a kept host-only one 2026-10-31 14:50:25 +01:00
Linus Rath b4f1082915 feat: play the Involved sound for calendar reminders 2026-10-31 14:49:56 +01:00
Linus Rath 4724c28f53 feat: offer the reading fonts, including OpenDyslexic, for the app and plain-text mail 2026-10-31 14:49:21 +01:00
Linus Rath 77914b0a46 test: follow the drawer's unread label helper and the sheet inset hook 2026-10-31 14:46:40 +01:00
Linus Rath f1297b6be8 feat: show calendar months, days and dates in the Jalali calendar for Persian 2026-10-31 14:45:24 +01:00
Linus Rath 36cc49b706 Merge branch 'cu-iosperf' into catch-up-2026-11 2026-10-31 14:37:35 +01:00
Linus Rath 3704c66637 Merge remote-tracking branch 'origin/ios-perf' into cu-iosperf
# Conflicts:
#	src/api/jmap-client.ts
2026-10-31 14:36:41 +01:00
Linus Rath 23b2bf9732 feat: recolour HTML mail for dark mode instead of inverting it 2026-10-31 14:34:01 +01:00
Linus Rath 172d0f63e8 fix: let TalkBack read the unified inbox row as its name and unread count 2026-10-31 14:31:54 +01:00
Linus Rath ce950430ea fix: keep the new id token when push or the unified inbox refreshes a login 2026-10-31 14:28:59 +01:00
Linus Rath d663a5e282 feat: show when someone invites you to, changes or cancels a calendar event 2026-10-31 14:28:18 +01:00
Linus Rath 268b1105b8 fix: keep the unified views from reading an old account on another port through the live one 2026-10-31 14:27:41 +01:00
Linus Rath 9669e07e95 fix: let a notification's delete skip the unlock when the mail goes to trash #85 2026-10-31 14:25:21 +01:00
Linus Rath b803da5df5 fix: stop showing a delegate's server id under Unknown person 2026-10-31 14:23:19 +01:00
Linus Rath e6a89ad07a feat: show invited participants' free/busy in the event editor 2026-10-31 14:22:59 +01:00
Linus Rath 423e2780d5 fix: name a shared address book or calendar once when its name already has the account 2026-10-31 14:22:16 +01:00
Linus Rath 58c8054339 fix: show how many emails were moved instead of {count} 2026-10-31 14:18:40 +01:00
Linus Rath 350b0e8e74 fix: stop TalkBack reading a button as busy after it finishes loading 2026-10-31 14:14:42 +01:00
Linus Rath 9b0bba7c15 fix: keep folder, calendar and share sheets and compose prompts above the taskbar and keyboard 2026-10-31 14:12:58 +01:00
Linus Rath 5ecf6f3256 Merge branch 'pr-66' into catch-up-2026-11
# Conflicts:
#	locales/rn/en.json
#	src/components/SidebarDrawer.tsx
2026-10-31 14:07:30 +01:00
Linus Rath f606866dd1 Merge branch 'cu-mail' into catch-up-2026-11 2026-10-31 14:04:54 +01:00
Linus Rath 14ec671c4e Merge branch 'cu-accounts' into catch-up-2026-11 2026-10-31 14:04:53 +01:00
Linus Rath 73823292e2 Merge branch 'cu-sheets' into catch-up-2026-11 2026-10-31 14:04:53 +01:00
Linus Rath e69bfc502b fix: lay out the reader's pages at the pane's width so a tablet pane is not left empty 2026-10-31 13:56:46 +01:00
Linus Rath 5a301d62dc feat: sign out of the identity provider too when signing out of a browser sign-in 2026-10-31 13:55:47 +01:00
Linus Rath e0184cfb46 fix: keep the notification prompt clear of the reader's bottom bar on tablets 2026-10-31 13:54:48 +01:00
Linus Rath 56c8b98bb6 fix: show localized system folder names in the filter rule folder picker 2026-10-31 13:54:39 +01:00
Linus Rath ce21f273a3 fix: scale the reader's header and toolbar with the text size setting 2026-10-31 13:52:38 +01:00
Linus Rath f2513de025 fix: say a message deleted elsewhere is gone instead of failing to load it 2026-10-31 13:52:38 +01:00
Linus Rath 9bc7b4a5b8 fix: keep the reader's read state when the conversation loads after marking it read 2026-10-31 13:52:38 +01:00
Linus Rath 461e4da0b2 fix: open the keyboard in the body when replying on Android 2026-10-31 13:52:38 +01:00
Linus Rath 66cdc07d31 fix: let the search filter sheet scroll above the keyboard and clear fully 2026-10-31 13:52:38 +01:00
Linus Rath ff878fc6bf fix: show the sender on search hits from other folders when searching from Sent 2026-10-31 13:52:38 +01:00
Linus Rath ccf93ed667 fix: stop announcing the select all checkbox as mixed with nothing selected 2026-10-31 13:52:38 +01:00
Linus Rath 9f64f8abb3 fix: hide reply and forward on drafts in a conversation 2026-10-31 13:52:38 +01:00
Linus Rath 9763036c70 feat: offer undo after changing tags in the tag sheet and the reader 2026-10-31 13:52:37 +01:00
Linus Rath ecf814b66e fix: make undo after a star take back the star, not the tag set before it 2026-10-31 13:52:37 +01:00
Linus Rath d1c44f52ba fix: count down the seconds left on the undo-send bar 2026-10-31 13:52:37 +01:00
Linus Rath 7eb7289472 fix: stop a settings search for "recover" from offering Reading 2026-10-31 13:52:07 +01:00
Linus Rath b17d4d4069 fix: say in Composing that "Plain text only" also shows received mail as plain text 2026-10-31 13:49:05 +01:00
Linus Rath 47b922cab0 Merge branch 'cu-calcon' into catch-up-2026-11 2026-10-31 13:48:41 +01:00
Linus Rath 2fe8366938 fix: let the notification Delete skip the unlock when the mail goes to Trash #85 2026-10-31 13:47:05 +01:00
Linus Rath 9ce89e2672 fix: say "1 email selected" in the tag picker header 2026-10-31 13:47:05 +01:00
Linus Rath d6b9b342e9 fix: name the tag or person on each row's delete and remove-access button 2026-10-31 13:44:54 +01:00
Linus Rath 72b41eabde fix: look up a typed address in the share sheet and say why the server won't find it 2026-10-31 13:43:58 +01:00
Linus Rath 149e5bcee5 fix: ask to sign in again when a password change ends the session 2026-10-31 13:42:37 +01:00
Linus Rath 236e9117a8 fix: stop showing a share's principal id as its address when the server can't name the person 2026-10-31 13:42:08 +01:00
Linus Rath e7fd665bb4 fix: tell screen readers which undo-send delay, sort order and language is picked 2026-10-31 13:41:43 +01:00
Linus Rath d00605492a fix: keep an account whose saved sign-in stops working and ask to sign in again 2026-10-31 13:39:44 +01:00
Linus Rath 9781b30478 fix: show a scheduled out of office as scheduled, mark its placeholders and stop Save reading as busy 2026-10-31 13:38:46 +01:00
Linus Rath fe6781745f fix: open the app from a widget tap without asking which build to use 2026-10-31 13:37:10 +01:00
Linus Rath bd5c54d654 fix: stop repeating the account in a shared address book's name 2026-10-31 13:33:58 +01:00
Linus Rath 8e0b447d8f fix: name the month button by its month and the participant remove button by the person 2026-10-31 13:32:21 +01:00
Linus Rath e9816809f2 fix: name the filter rule fields by their labels for screen readers 2026-10-31 13:31:10 +01:00
Linus Rath 11fa0365f9 fix: clear the new event slot marker when the editor closes or the tab is left 2026-10-31 13:29:19 +01:00
Linus Rath 65a8ac3469 fix: show the file name in the tablet split view's preview header 2026-10-31 13:28:30 +01:00
Linus Rath 826d5ad2be fix: accept a calendar hex colour with or without # and show it with # after typing 2026-10-31 13:26:36 +01:00
Linus Rath dcca32a780 fix: give the Files recent and starred menus a Cancel and let Back close them 2026-10-31 13:25:53 +01:00
Linus Rath f9a36b2892 fix: open a notification or link for another account over that account's inbox 2026-10-31 13:24:56 +01:00
Linus Rath 8626cd11ef fix: name the calendar description field plainly instead of the desktop help text 2026-10-31 13:24:22 +01:00
Linus Rath e4eda0a840 fix: label the new folder parent picker "Parent folder" instead of "Inside {name}" 2026-10-31 13:22:56 +01:00
Linus Rath 75513969ef fix: keep all-day events all-day on Stalwart 0.16 2026-10-31 13:21:11 +01:00
Linus Rath 688e8bef3b fix: keep two accounts on the same host apart by port 2026-10-31 13:21:01 +01:00
Linus Rath 980243469c fix: keep sheet and dialog fields and buttons above the keyboard 2026-10-31 13:19:56 +01:00
Linus Rath 9ec58be4fb fix: add a typed participant once and show suggestions above the keyboard 2026-10-31 13:17:18 +01:00
Linus Rath 426f740359 fix: keep bottom sheet buttons above the tablet taskbar 2026-10-31 13:15:21 +01:00
Linus Rath db717dcd51 fix: stop the app from crashing when switching to an account left in Trash 2026-10-31 13:08:27 +01:00
Linus Rath 1e21540c6a docs: record the catch-up decisions 2026-10-31 13:06:12 +01:00
Linus Rath 7139390a85 Merge branch 'cu-calendar2' into catch-up-2026-11
# Conflicts:
#	docs/parity/10-webmail-1.10-to-1.18.md
#	docs/parity/11-webmail-1.19.md
2026-10-31 05:58:19 +01:00
Linus Rath 62867f3f3d docs: tick the rows the second calendar pass fixed 2026-10-31 05:57:18 +01:00
Linus Rath 6452b47ca2 test: follow the event editor's save into the offer to save without invitations 2026-10-31 05:56:22 +01:00
Linus Rath 50b91d67d5 test: check address book sharing and the contacts and files account lists on live servers 2026-10-31 05:54:25 +01:00
Linus Rath 7ba00be60e fix: give the contact, event and files fields a name for screen readers 2026-10-31 05:51:52 +01:00
Linus Rath 01578ac2fb feat: show calendar events in light tinted colours on request 2026-10-31 05:48:55 +01:00
Linus Rath a4e5f81f37 Merge branch 'cu-mailbits' into catch-up-2026-11 2026-10-31 05:45:17 +01:00
Linus Rath 90ce7c1b3c feat: size the week all-day area from the days in view and fold it to three rows (bulwarkmail/webmail#1122) 2026-10-31 05:44:28 +01:00
Linus Rath 9fd55d94d3 docs: note the trash link and list row fixes 2026-10-31 05:44:18 +01:00
Linus Rath 7467764e43 feat: lead from the trash to recover deleted items when the archive has some 2026-10-31 05:43:55 +01:00
Linus Rath b35d15cada fix: show own IDN domains as written in the mail list rows (bulwarkmail/webmail#1100) 2026-10-31 05:40:27 +01:00
Linus Rath ecf1f3ecd9 fix: forward out-of-office mail to a unicode domain in its ascii form (bulwarkmail/webmail#1100) 2026-10-31 05:38:43 +01:00
Linus Rath 254d192709 test: check the sharing and recovery panes against the settings search results 2026-10-31 05:37:59 +01:00
Linus Rath 96b163a117 feat: share an address book with other people 2026-10-31 05:36:52 +01:00
Linus Rath bc9680b014 docs: note the card mount and the consent step 2026-10-31 05:36:00 +01:00
Linus Rath cec68f0d91 Merge branch 'cu-followup' into catch-up-2026-11
# Conflicts:
#	docs/parity/11-webmail-1.19.md
2026-10-31 05:34:13 +01:00
Linus Rath beae63e144 docs: tick the rows the follow-up pass fixed 2026-10-31 05:33:10 +01:00
Linus Rath 287b0e58ac refactor: draw the booking and parcel card icons from the generated icon set 2026-10-31 05:31:34 +01:00
Linus Rath 3048525d9b Merge branch 'cu-recovery' into catch-up-2026-11 2026-10-31 05:30:58 +01:00
Linus Rath dae154431f docs: tick the recover deleted and delegate rows 2026-10-31 05:30:09 +01:00
Linus Rath 8a5bb72823 feat: give a delegate access to mail, calendars and contacts in one step 2026-10-31 05:29:50 +01:00
Linus Rath d684b00e9b feat: open a files link on the folder and file it names 2026-10-31 05:29:46 +01:00
Linus Rath 98b962a8d4 fix: name the composer, quick reply, folder and template search fields for screen readers 2026-10-31 05:29:38 +01:00
Linus Rath 6c1cb0f9ef docs: tick the search rows 2026-10-31 05:29:35 +01:00
Linus Rath 12b798c0c8 test: check search highlights and the size filter on stalwart 0.16 and 1.0 2026-10-31 05:29:01 +01:00
Linus Rath cad9296a84 feat: list starred folders and recent files at the top of files 2026-10-31 05:28:20 +01:00
Linus Rath a7a3757378 feat: suggest directory people and search the sent folder for recipients in the composer 2026-10-31 05:27:34 +01:00
Linus Rath 3c3274e4b7 fix: name the search and filter fields for screen readers 2026-10-31 05:27:19 +01:00
Linus Rath aac8a40367 feat: filter search results by message size 2026-10-31 05:26:03 +01:00
Linus Rath e3fc67e771 feat: step to the previous or next file in the files preview 2026-10-31 05:24:31 +01:00
Linus Rath 113e96bdec feat: recover mail deleted for good from the server's archive (bulwarkmail/webmail#1319) 2026-10-31 05:23:28 +01:00
Linus Rath 1342e2a0f8 feat: show where a search matched in each result 2026-10-31 05:22:58 +01:00
Linus Rath 8ac1997149 feat: delete filter rules, tags and trusted senders with undo and warn about the forward limit (bulwarkmail/webmail#1208) 2026-10-31 05:21:32 +01:00
Linus Rath ecefd1cbcc feat: open new-contact and edit-contact links in the contact form 2026-10-31 05:16:34 +01:00
Linus Rath 4932764a66 fix: send to addresses on internationalized domains (bulwarkmail/webmail#1100) 2026-10-31 05:14:55 +01:00
Linus Rath fe45537037 feat: open a calendar link on the view and day it names 2026-10-31 05:13:57 +01:00
Linus Rath 7a15c0c6ac fix: list contacts and files only for shared accounts that support them (bulwarkmail/webmail#1239) 2026-10-31 05:10:32 +01:00
Linus Rath 97173d4af0 fix: lead with the address when two accounts share a name in the sidebar account switcher 2026-10-31 05:10:14 +01:00
Linus Rath bb45198f23 feat: add a switch for the quick reply box under composing settings 2026-10-31 05:09:12 +01:00
Linus Rath 6da985a225 fix: keep sender favicons off and style sheets out of notification previews (bulwarkmail/webmail#1136) 2026-10-31 05:08:11 +01:00
Linus Rath 90e4e230af feat: show the booking, parcel and order cards above the message body 2026-10-31 05:03:28 +01:00
Linus Rath f643df8164 Merge branch 'cu-reader' into catch-up-2026-11
# Conflicts:
#	docs/native-sync-2026-11.md
#	docs/parity/11-webmail-1.19.md
#	locales/rn/en.json
#	src/stores/settings-store.ts
2026-10-31 05:02:08 +01:00
Linus Rath 3769b24b3a test: tap Allow on stalwart 1.0.1's consent page in the ios sign-in flows 2026-10-31 04:59:38 +01:00
Linus Rath 53f95945b1 docs: tick the parity rows the reader pass fixed 2026-10-31 04:59:33 +01:00
Linus Rath 30e0631582 Merge branch 'cu-cards' into catch-up-2026-11 2026-10-31 04:57:58 +01:00
Linus Rath 470f12d232 Merge branch 'cu-platform' into catch-up-2026-11
# Conflicts:
#	locales/rn/en.json
#	src/stores/settings-store.ts
2026-10-31 04:57:53 +01:00
Linus Rath 693c1c8c73 chore: harvest the reader strings 2026-10-31 04:56:57 +01:00
Linus Rath beff87440f fix: name each sort order with its own string so it gets translated 2026-10-31 04:56:54 +01:00
Linus Rath c6ad3fe1d2 chore: harvest the reader strings 2026-10-31 04:56:09 +01:00
Linus Rath d9a759b78e Merge branch 'cu-calendar' into catch-up-2026-11 2026-10-31 04:55:30 +01:00
Linus Rath e5c9433a0e fix: offer every folder as a tree in the search folder picker instead of the first twelve 2026-10-31 04:55:30 +01:00
Linus Rath 446a2eef38 docs: tick the booking cards parity row 2026-10-31 04:55:12 +01:00
Linus Rath 88d19db6ef docs: tick the parity rows the platform pass fixed 2026-10-31 04:54:57 +01:00
Linus Rath 5a5f708d82 docs: tick the parity rows the calendar pass fixed 2026-10-31 04:54:34 +01:00
Linus Rath c90a35c403 chore: harvest the card strings 2026-10-31 04:54:16 +01:00
Linus Rath 982a30effc feat: show booking cards for Apple Wallet passes attached to verified mail 2026-10-31 04:53:55 +01:00
Linus Rath 56e4a1e051 feat: let the quick reply under a conversation be turned off (bulwarkmail/webmail#1370) 2026-10-31 04:53:48 +01:00
Linus Rath 01dd7461f5 fix: keep toasts working where the accessibility bridge is missing 2026-10-31 04:52:44 +01:00
Linus Rath 542fff8872 chore: harvest the calendar strings 2026-10-31 04:52:02 +01:00
Linus Rath c7cb3f60b2 fix: spell the custom calendar colour strings like the rest of the app 2026-10-31 04:52:02 +01:00
Linus Rath fac54b415f fix: show the via badge only when the identity's name adds something to the sender 2026-10-31 04:51:26 +01:00
Linus Rath a0f586f9c2 feat: duplicate a folder in Files with everything in it 2026-10-31 04:51:01 +01:00
Linus Rath ed490b7188 fix: open the folder a mail folder link names instead of only the mail tab 2026-10-31 04:50:08 +01:00
Linus Rath 2055df8c21 feat: add, update and remove bookings from mail in the calendar 2026-10-31 04:49:41 +01:00
Linus Rath b943246373 chore: harvest the platform strings 2026-10-31 04:48:24 +01:00
Linus Rath e0f0db5d67 test: pin the error answer the push lookup fallback relies on (bulwarkmail/webmail#1041) 2026-10-31 04:48:14 +01:00
Linus Rath aa25d08dcc fix: start another folder, tag or search at the top of its list (bulwarkmail/webmail#1138) 2026-10-31 04:48:04 +01:00
Linus Rath 327b2cae1b fix: check push again when the app comes back to settings (bulwarkmail/webmail#1148) 2026-10-31 04:47:14 +01:00
Linus Rath 9067908fef fix: open the inbox on a cold start instead of the folder the app was left in 2026-10-31 04:46:53 +01:00
Linus Rath 18c6b03fc1 feat: filter contacts by company, job title, location, email domain, birthday month and details 2026-10-31 04:45:41 +01:00
Linus Rath 1821fda728 feat: add an inbox-only switch for push notifications (bulwarkmail/webmail#983) 2026-10-31 04:45:07 +01:00
Linus Rath 4470a4ba82 fix: drop the paperclip from messages with only inline images and keep list rows from growing when chips arrive 2026-10-31 04:45:01 +01:00
Linus Rath 219aef046a fix: drop a leading style sheet and padding from message previews (bulwarkmail/webmail#1136) 2026-10-31 04:42:37 +01:00
Linus Rath 65ffef7108 feat: delete a contact category with an undo 2026-10-31 04:41:51 +01:00
Linus Rath 04a962a69e fix: sort merged lists by date sent instead of falling back to the received date (bulwarkmail/webmail#1219) 2026-10-31 04:41:02 +01:00
Linus Rath 43bb5e474a fix: leave a server's own all mailbox out of all mail and unread (bulwarkmail/webmail#1241) 2026-10-31 04:41:02 +01:00
Linus Rath cd677e7787 feat: show a card with times and tracking above booking, shipping and order mail 2026-10-31 04:40:16 +01:00
Linus Rath 6715df7f44 feat: select a message by tapping its avatar in the list #91 2026-10-31 04:39:14 +01:00
Linus Rath 67732348c2 fix: offer filters and auto-reply only for shared accounts that support them (bulwarkmail/webmail#1239) 2026-10-31 04:38:56 +01:00
Linus Rath e988dc2928 feat: suggest contact groups and recent recipients when inviting people to an event 2026-10-31 04:38:20 +01:00
Linus Rath eb6bf6d38e fix: leave trash and spam out of tag views and tag counts, and count tags with Keyword/get where the server can (bulwarkmail/webmail#1156) 2026-10-31 04:37:42 +01:00
Linus Rath af86339bf1 fix: count storage use against the soft limit when the server sets one (bulwarkmail/webmail#1242) 2026-10-31 04:35:41 +01:00
Linus Rath baa3ff5956 feat: copy an event's title and add a note to it from the event details 2026-10-31 04:35:30 +01:00
Linus Rath f5875f87b1 feat: sign in with addresses on internationalized domains (bulwarkmail/webmail#1100) 2026-10-31 04:31:46 +01:00
Linus Rath 574db42652 feat: change the colour of the birthdays calendar 2026-10-31 04:31:14 +01:00
Linus Rath a6066a092c feat: read schema.org booking, parcel and order markup from verified mail 2026-10-31 04:31:01 +01:00
Linus Rath 5f2ef4c1c3 feat: read booking markup from mail HTML without a browser DOM 2026-10-31 04:30:14 +01:00
Linus Rath b6995bb638 fix: move a task into or within a shared calendar (bulwarkmail/webmail#1204) 2026-10-31 04:29:28 +01:00
Linus Rath 37e72c129f fix: explain a refused token exchange on a two-factor sign-in 2026-10-31 04:27:27 +01:00
Linus Rath e3a34eb112 fix: tell screen readers which folder is open and how many are unread in it 2026-10-31 04:25:47 +01:00
Linus Rath 9b5692a8ab feat: offer to save an event without invitations when the server refuses to send them 2026-10-31 04:25:10 +01:00
Linus Rath 7ecac4ab43 fix: ask before the identity editor drops unsaved changes 2026-10-31 04:25:06 +01:00
Linus Rath 1cbe28b18c feat: open the sort orders from the list header's sort button 2026-10-31 04:24:00 +01:00
Linus Rath c8f417dd8f fix: hide preview lines in the density sample when previews are off 2026-10-31 04:23:15 +01:00
Linus Rath d3f6d6aea3 feat: pick any calendar colour with a hex field and more shades #90 2026-10-31 04:22:48 +01:00
Linus Rath 505fbc59ad fix: tell two accounts with the same name apart in settings 2026-10-31 04:21:37 +01:00
Linus Rath da1da0ec6b feat: offer undo after marking read or unread, starring or tagging from the list 2026-10-31 04:21:00 +01:00
Linus Rath 64eb97f4c3 fix: keep a shared calendar's own colour instead of a random one (bulwarkmail/webmail#1414) 2026-10-31 04:19:56 +01:00
Linus Rath 294023e2b2 fix: read month days with their date and event count and events with title, date and time to screen readers 2026-10-31 04:18:17 +01:00
Linus Rath b205c13a6e fix: show the two-factor code prompt as a hint, not a red error (bulwarkmail/webmail#1422) 2026-10-31 04:18:10 +01:00
Linus Rath e09cd395a2 feat: make a new tag from the tag picker and put it on the message right away 2026-10-31 04:16:56 +01:00
Linus Rath 9bd4674c9b chore: harvest the platform strings 2026-10-31 04:15:56 +01:00
Linus Rath c9b3bdfea0 feat: add an accessibility page with presets, larger text sizes and a notification duration 2026-10-31 04:15:55 +01:00
Linus Rath 9a4515f905 fix: let search results from other folders move into the open folder (bulwarkmail/webmail#1452) 2026-10-31 04:13:42 +01:00
Linus Rath 6c6991ace8 fix: keep system autofill out of the event participant field 2026-10-31 04:13:13 +01:00
Linus Rath b95f2c296d feat: delete a filter rule at once with an undo in the filter store 2026-10-31 04:11:36 +01:00
Linus Rath 4849cf233e feat: show typed, coloured file icons on the reader's attachment chips 2026-10-31 04:11:17 +01:00
Linus Rath 73a6adf27c docs: tick the parity rows the compose pass fixed 2026-10-31 04:09:43 +01:00
Linus Rath 6d701c4b06 feat: stop animations and pause animated images in mail when motion is reduced 2026-10-31 04:09:39 +01:00
Linus Rath 813be0c10f test: follow the text part options in the composer wiring check 2026-10-31 04:08:58 +01:00
Linus Rath 2951672548 chore: harvest the compose strings 2026-10-31 04:08:15 +01:00
Linus Rath 357a03c062 docs: correct the composer's note on sending from a shared account 2026-10-31 04:07:49 +01:00
Linus Rath 10ad3a40c7 test: check the composed body and scheduled preset times on both Stalwarts 2026-10-31 04:07:41 +01:00
Linus Rath 6c3046c8dc fix: keep the text typed in the Sieve editor when the script refreshes (bulwarkmail/webmail#1162) 2026-10-31 04:07:12 +01:00
Linus Rath dcbfc907fa feat: warn about the server's forward limit and an auto-reply too long for it (bulwarkmail/webmail#1155, bulwarkmail/webmail#1208) 2026-10-31 04:05:57 +01:00
Linus Rath 43973608d6 fix: show plain-text mail in the dark theme's own colours instead of a fixed black 2026-10-31 04:05:45 +01:00
Linus Rath 25f9b810f2 fix: address the user formally in the german phone strings 2026-10-31 04:05:39 +01:00
Linus Rath 303dea01e9 feat: ask for a description when an image goes into a message 2026-10-31 04:04:35 +01:00
Linus Rath e8bc54ebaa feat: add thai and estonian 2026-10-31 04:03:57 +01:00
Linus Rath 6f66fa065a fix: draw avatar initials in black or white, whichever reads better on the colour 2026-10-31 04:03:46 +01:00
Linus Rath a4d3891140 fix: give user folders in the move sheet a folder icon even when their name looks like a system folder (bulwarkmail/webmail#1235) 2026-10-31 04:01:54 +01:00
Linus Rath d23af55e4b chore: sync the webmail translations 2026-10-31 04:01:44 +01:00
Linus Rath 2c1f266fba feat: open the template manager from the composer's template picker 2026-10-31 04:00:30 +01:00
Linus Rath 7772da8463 fix: leave bounce and receipt report parts out of the list's attachment chips 2026-10-31 04:00:21 +01:00
Linus Rath 352973793f fix: hide reply on drafts and scheduled messages in a conversation and quick reply to the newest real message (bulwarkmail/webmail#1404) 2026-10-31 03:59:16 +01:00
Linus Rath 9fd8099369 feat: fold the quoted history of a reply behind a toggle in the composer (bulwarkmail/webmail#1405) 2026-10-31 03:57:26 +01:00
Linus Rath f6e6f8f570 fix: show a new mail notification when the push lookup fails (bulwarkmail/webmail#1041) 2026-10-31 03:56:49 +01:00
Linus Rath 6e697baa46 fix: keep the recurrence id of a daily series in another zone on the device's DST day 2026-10-31 03:54:52 +01:00
Linus Rath 9794dc03f7 feat: ask before deleting or archiving when the confirm settings are on 2026-10-31 03:54:33 +01:00
Linus Rath a3650ebf34 test: give the mail text size test a valid message spacing 2026-10-31 03:54:33 +01:00
Linus Rath 853cb5d052 docs: note that stalwart 1.0.1 keeps drafts and sent mail unencrypted at rest 2026-10-31 03:53:25 +01:00
Linus Rath 1bef47199c fix: sign in when stalwart 1.0.1 asks for consent instead of reporting a wrong password 2026-10-31 03:53:25 +01:00
Linus Rath 482e9a2014 fix: leave bounce and read receipt reports out of a forward 2026-10-31 03:53:02 +01:00
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 7b551fc3e0 feat: scale the mail body with the app text size and the system font size 2026-10-31 03:51:04 +01:00
Linus Rath ae2eb46b29 fix: keep an escaped quote in a pasted display name from splitting off a recipient 2026-10-31 03:49:55 +01:00
Linus Rath d812caf025 fix: read toasts, the undo bar and the offline banner out on ios 2026-10-31 03:49:09 +01:00
Linus Rath 8bb7b60728 fix: open a reply with the caret in the body and keep system autofill out of the address fields 2026-10-31 03:48:53 +01:00
Linus Rath 10e9f81dc1 fix: ask before the event and task editors drop typed changes on close or back 2026-10-31 03:48:23 +01:00
Linus Rath 7dc457780d feat: read mail as plain text when plain text only is on, or per message from the menu 2026-10-31 03:48:14 +01:00
Linus Rath 8fe5798bf5 fix: show scheduled times rounded to the minute and offer this morning, this afternoon and Monday presets (bulwarkmail/webmail#1390) 2026-10-31 03:46:43 +01:00
Linus Rath ff5a8c91d6 fix: give the text fields in settings and sign-in a name for screen readers 2026-10-31 03:45:35 +01:00
Linus Rath a19afe9b48 feat: offer to remove a cancelled meeting from the calendar in the cancellation mail (bulwarkmail/webmail#1425) 2026-10-31 03:44:02 +01:00
Linus Rath 1cc4ccca8e fix: ask about a missing subject once, close an autosaved draft without asking and add Discard 2026-10-31 03:43:26 +01:00
Linus Rath a930d17786 fix: drop direction-override characters from attachment names so a sender can't fake the file type 2026-10-31 03:43:18 +01:00
Linus Rath 1152299583 fix: raise grey text to 4.5:1 contrast in every built-in theme 2026-10-31 03:42:24 +01:00
Linus Rath 0647cbf171 fix: mark quotes, number lists and keep the signature delimiter in the text part of sent mail 2026-10-31 03:40:56 +01:00
Linus Rath 3243714db9 fix: stop a mail full of blockquotes from freezing the reader 2026-10-31 03:40:34 +01:00
Linus Rath 55d008a2e8 fix: let the user cancel a sign-in that hangs 2026-10-31 03:39:39 +01:00
Linus Rath 8e10e73e29 fix: keep sender favicons off by default so sender domains don't go to duckduckgo 2026-10-31 03:38:44 +01:00
Linus Rath 0b0fc021fb fix: show a warning shield instead of the sender photo when the sender check fails (bulwarkmail/webmail#1439) 2026-10-31 03:37:46 +01:00
Linus Rath 60d0e98fc7 fix: keep a hand-picked sender's own signature and swap it after typing under it 2026-10-31 03:35:30 +01:00
Linus Rath a2344ea0ae fix: keep a new calendar's name and colour until the next refresh 2026-10-31 03:34:36 +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 49d4372918 fix: count a dkim pass the receiver reports by header.i only (bulwarkmail/webmail#1420) 2026-10-31 03:30:38 +01:00
Linus Rath 0e69c86c51 chore: add the confirm before send, delete and archive settings 2026-10-31 03:24:30 +01:00
Linus Rath 948cad814f docs: inventory the native catch-up with webmail 1.19.1 and stalwart 1.0.1 2026-10-31 03:20:58 +01:00
Linus Rath 7a42a15f8a feat: sign in through the bulwark-mobile client on stalwart 1.0 (bulwarkmail/webmail#1459) 2026-10-31 00:57:06 +01:00
Linus Rath 9d547b9d16 fix: decode escaped colons in imported vcard notes 2026-10-29 19:52:28 +01:00
Linus Rath cd6724f754 chore: update version number to 0.1.87 2026-10-29 12:10:41 +01:00
Linus Rath e6e5e521cf docs: report the native sync with webmail 1.18.0 and Stalwart 1.0.0 2026-10-29 05:27:41 +01:00
Linus Rath fe33dfc53f docs: tick the webmail 1.10 to 1.18 parity items this pass fixed 2026-10-29 05:26:33 +01:00
Linus Rath 595df26e40 Merge branch 'fx/compose2' into feat/stalwart-1.0 2026-10-29 05:03:02 +01:00
Linus Rath c817f978a4 fix: reload Scheduled after a switch of accounts and refuse actions on the other account's rows 2026-10-29 05:01:12 +01:00
Linus Rath 51b3208b3b fix: drop a cancelled scheduled send from the list when it can't go back to Drafts, and say it stayed in Sent 2026-10-29 04:59:32 +01:00
Linus Rath 7594e45937 fix: stop the placeholder check from flagging text inside link addresses 2026-10-29 04:55:40 +01:00
Linus Rath 39e82f99e4 fix: save the attachment itself to Files on Stalwart 0.16, under a name the folder has free 2026-10-29 04:54:11 +01:00
Linus Rath 67407c1119 Merge branch 'fx/cal2' into feat/stalwart-1.0 2026-10-29 04:51:16 +01:00
Linus Rath b767df9fad fix: save a calendar's default reminders when another calendar uses the ids of the ones kept 2026-10-29 04:50:15 +01:00
Linus Rath 6133bcd951 fix: trust an invitation that passed dmarc even when a forwarder broke its first signature 2026-10-29 04:50:15 +01:00
Linus Rath 47f2971899 fix: keep a subscription's events when its feed arrives cut off or drops most of them at once 2026-10-29 04:50:15 +01:00
Linus Rath 98dc13fe9c fix: stop a series edit or delete after an account switch instead of finishing it in the next account 2026-10-29 04:50:15 +01:00
Linus Rath c8600e859a fix: attach a quoted inline image under a plain image type, not the part's raw header 2026-10-29 04:49:31 +01:00
Linus Rath fdf8eece62 fix: keep a rescheduled send from going out twice or not at all when the server's answer is lost 2026-10-29 04:47:20 +01:00
Linus Rath 4e3da668f4 fix: keep the auto-reply preview from navigating to a data: page 2026-10-29 04:45:35 +01:00
Linus Rath ddec876e36 Merge branch 'fx/read2' into feat/stalwart-1.0 2026-10-29 04:44:36 +01:00
Linus Rath 8adef1e13f fix: never count a helo spf pass as verifying the sender 2026-10-29 04:41:02 +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 67cdb4e792 fix: leave a message unread when the server refuses to move it to trash across accounts 2026-10-29 04:38:26 +01:00
Linus Rath 918571c3c6 fix: send a mailto unsubscribe from the own account and only to one decoded address 2026-10-29 04:36:56 +01:00
Linus Rath e9a360d750 Merge branch 'fx/contacts2' into feat/stalwart-1.0 2026-10-29 04:36:23 +01:00
Linus Rath d201ca24f8 test: check if/elsif/else scripts and UID re-imports live on Stalwart 0.16 and 1.0 2026-10-29 04:34:56 +01:00
Linus Rath 3c156673d5 fix: ask before a contact import replaces a card with the same UID 2026-10-29 04:34:17 +01:00
Linus Rath ba67205052 fix: stop re-rendering every message row when the shared tags are re-read unchanged 2026-10-29 04:33:54 +01:00
Linus Rath 7b7236f05d fix: keep a rule made from a message out of a login switched to while the sheet was open 2026-10-29 04:33:16 +01:00
Linus Rath af1f8197d9 fix: keep a login switch during a vacation save out of the other login's filters 2026-10-29 04:32:26 +01:00
Linus Rath 2b5257bf6b fix: batch the default search across accounts into shared requests again 2026-10-29 04:32:20 +01:00
Linus Rath 54aa88425a fix: say so when the filters stay off after turning on the auto-reply 2026-10-29 04:31:18 +01:00
Linus Rath fc9c164d3d fix: keep if/elsif/else chains whole when filters are saved on the phone 2026-10-29 04:29:26 +01:00
Linus Rath 440a2e7ac7 fix: scrub only real stylesheets and keep their css escapes when blocking remote content 2026-10-29 04:29:23 +01:00
Linus Rath 47a1d24999 fix: keep a quoted stylesheet from ending the composer's editor script 2026-10-29 04:25:57 +01:00
Linus Rath e7e40c309e test: give the pairing hand-off tests the redirect-safe fetch the sign-in code now goes through 2026-10-29 04:22:20 +01:00
Linus Rath 07679cbf8c fix: keep a hostile inline image type out of the reader and the composer 2026-10-29 04:22:05 +01:00
Linus Rath 79ee9c34ca Merge branch 'fx/compose' into feat/stalwart-1.0 2026-10-29 04:20:07 +01:00
Linus Rath 91175a3382 Merge branch 'fx/cal' into feat/stalwart-1.0 2026-10-29 04:16:46 +01:00
Linus Rath 6414b0d6a9 fix: send from the own account when a group's identities can't be read, and clear a draft save stopped by an account switch 2026-10-29 04:16:08 +01:00
Linus Rath 1ffce863d9 test: check rescheduling, Cancel send, Files blobs and metadata templates live on Stalwart 0.16 and 1.0 2026-10-29 04:14:29 +01:00
Linus Rath 25425cb940 chore: add the event search texts the catalog lacked 2026-10-29 04:13:00 +01:00
Linus Rath b50270d8f3 feat: search events in the calendar 2026-10-29 04:12:48 +01:00
Linus Rath fac318dc75 feat: keep templates in the account on Stalwart 1.0 and offer team templates in the picker 2026-10-29 04:11:28 +01:00
Linus Rath 7dc9ee24db Merge branch 'fx/read' into feat/stalwart-1.0 2026-10-29 04:10:53 +01:00
Linus Rath 64ce86b372 Merge branch 'fx/contacts' into feat/stalwart-1.0 2026-10-29 04:08:31 +01:00
Linus Rath 9b87100a15 feat: set a calendar's default reminders and start new events with them 2026-10-29 04:02:40 +01:00
Linus Rath 1bce37bb56 fix: read only the receiving server's own authentication results when trusting an invitation 2026-10-29 03:56:54 +01:00
Linus Rath 8e07b7d891 feat: attach files from Files and save attachments to Files 2026-10-29 03:51:33 +01:00
Linus Rath 96c7c0455d fix: give user folders a folder icon even when their name looks like a system folder #80 2026-10-29 03:50:08 +01:00
Linus Rath ada20598d4 fix: keep only the refused rows of a delete or move across accounts and never fail a delete over its read flag 2026-10-29 03:48:04 +01:00
Linus Rath 61654d7c40 fix: keep a sign-in code from following a redirect to another host 2026-10-29 03:44:57 +01:00
Linus Rath 01983a3810 fix: keep the event editor's bottom fields above the keyboard #81 2026-10-29 03:44:43 +01:00
Linus Rath 81061c0d0e test: check contacts and filters live on Stalwart 0.16 and 1.0 2026-10-29 03:44:42 +01:00
Linus Rath 7a1031a990 fix: show the location and meeting link text in dark mode #82 2026-10-29 03:44:01 +01:00
Linus Rath 16bbb97ed2 Merge branch 'fx/auth3' into feat/stalwart-1.0 2026-10-29 03:43:39 +01:00
Linus Rath b7b9c7e076 feat: show the shared tags of group mailboxes, read from their metadata on Stalwart 1.0 2026-10-29 03:43:27 +01:00
Linus Rath 639aa4aa42 fix: drop a signed-out account's calendar feeds, and clear recent searches when an eviction removes the last account 2026-10-29 03:42:07 +01:00
Linus Rath d546d50923 feat: create filter rules from a message's sender, domain or mailing list 2026-10-29 03:41:18 +01:00
Linus Rath 31d8e4a4b7 feat: send and save drafts from a group or shared identity in that account 2026-10-29 03:40:42 +01:00
Linus Rath cfd81737e3 fix: drop tokens refreshed for an account that signed out meanwhile 2026-10-29 03:39:15 +01:00
Linus Rath acbd085ab4 fix: end a zero-length event at its start instead of at midnight 2026-10-29 03:38:25 +01:00
Linus Rath 06d68d73ee fix: keep refresh tokens, sign-in codes and OAuth metadata from following a redirect to another host 2026-10-29 03:36:49 +01:00
Linus Rath 0fd14a837a feat: browse the company directory in Contacts #1253 2026-10-29 03:34:44 +01:00
Linus Rath a8e7a3f49d feat: warn before sending a message with unfilled placeholders 2026-10-29 03:33:49 +01:00
Linus Rath fe4f92112d fix: sign in with password and two-factor code when the phone reports the server address without its default port or in punycode 2026-10-29 03:31:06 +01:00
Linus Rath f83284d7e5 fix: fold exported .ics lines by octets and quote attendee names 2026-10-29 03:30:14 +01:00
Linus Rath 484cac58b7 fix: search Sent, Archive and Drafts from the unified All mail view 2026-10-29 03:30:12 +01:00
Linus Rath 7c94039a7e Merge branch 'fx/auth' into feat/stalwart-1.0 2026-10-29 03:28:51 +01:00
Linus Rath a412b5163a feat: leave Spam and Trash out of search unless All folders is picked 2026-10-29 03:28:34 +01:00
Linus Rath e64f8629da Merge branch 'fx/push2' into feat/stalwart-1.0 2026-10-29 03:28:31 +01:00
Linus Rath 07fd03e284 fix: export contacts as vCard 4.0 that other apps read in full #1407 2026-10-29 03:28:10 +01:00
Linus Rath 32f70c103d fix: save contacts with calendar or scheduling links and re-import exported vCards by UID 2026-10-29 03:28:09 +01:00
Linus Rath 59c3278259 fix: leave blank participant names out of saved events 2026-10-29 03:27:44 +01:00
Linus Rath 152f308966 fix: tear down a removed account's push through its own client, not the open account's 2026-10-29 03:27:40 +01:00
Linus Rath 14d009de32 feat: open scheduled messages in a read-only view, and send cancelled ones back to Drafts #84 2026-10-29 03:26:32 +01:00
Linus Rath 04ff8728da Merge branch 'fx/files' into feat/stalwart-1.0 2026-10-29 03:26:03 +01:00
Linus Rath 0fb7c8cd09 fix: show an invitation's time in the user's time zone, with its name 2026-10-29 03:26:00 +01:00
Linus Rath b352e470c1 fix: keep a push for another login's group account out of the login whose own id it carries 2026-10-29 03:25:05 +01:00
Linus Rath 946304ab76 test: check the Files trash fixes live on Stalwart 0.16 and 1.0 2026-10-29 03:24:43 +01:00
Linus Rath 7a95d43f1a fix: trust an invitation only on checks aligned with its sender and stop flagging colleagues' invitations 2026-10-29 03:22:46 +01:00
Linus Rath 58bc4acac5 fix: keep loading older mail when the server sends no total bulwarkmail/webmail#1364 2026-10-29 03:20:49 +01:00
Linus Rath 22f2ac5b29 fix: cancel the original before scheduling its replacement on reschedule and send now, and put it back when that fails 2026-10-29 03:20:32 +01:00
Linus Rath 90e250d376 fix: go back to the previous push relay whenever a move to a new one fails 2026-10-29 03:20:06 +01:00
Linus Rath 3bfdf8cef9 fix: give a restored file with a clashing WebDAV name a free name the server accepts 2026-10-29 03:19:43 +01:00
Linus Rath 4de856923b fix: keep daily recurring events going past a DST gap 2026-10-29 03:18:00 +01:00
Linus Rath 5ea19a8581 fix: restore a trashed item into its folder when that folder is restored with it 2026-10-29 03:17:48 +01:00
Linus Rath b1550f143b feat: offer the code of sign-in and verification mails for copying in the list and the reader 2026-10-29 03:15:09 +01:00
Linus Rath 6f475c0c52 fix: remind of a task relative to its start when the reminder says so #83 2026-10-29 03:14:58 +01:00
Linus Rath 4a56e825e4 fix: work out where trashed items came from once per listing instead of once per row 2026-10-29 03:13:31 +01:00
Linus Rath 9d1337bf71 Merge branch 'fx/push' into feat/stalwart-1.0 2026-10-29 03:12:56 +01:00
Linus Rath d57bdfaace fix: send writes only once after a dropped connection, retry only batches of reads 2026-10-29 03:12:41 +01:00
Linus Rath 01abe1007c test: check the browser sign-in client, refresh after sign-out and the TOTP code for account changes live on Stalwart 0.16 and 1.0 2026-10-29 03:11:40 +01:00
Linus Rath 6b9552fcb4 fix: keep contacts loaded or saved for one account out of the next after a switch 2026-10-29 03:10:37 +01:00
Linus Rath d908f95fd7 fix: refuse .Trash as a name at the top of Files, where it would become the trash 2026-10-29 03:10:35 +01:00
Linus Rath c6c3d66a9b test: check feed refreshes and a task load overtaken by an account switch on the live servers 2026-10-29 03:10:30 +01:00
Linus Rath cc21317b5a feat: warn about unverified senders in the reader and stop offering Always trust for them 2026-10-29 03:09:04 +01:00
Linus Rath e1e6af3cca fix: offer to delete permanently when a Files delete can't go to the trash 2026-10-29 03:07:40 +01:00
Linus Rath df1a5ab4b6 feat: set up vacation forwarding, reply recipients, rule periods and all-messages rules on the phone #1152 2026-10-29 03:07:09 +01:00
Linus Rath 855d304b4f fix: stop a crafted winmail.dat from freezing the app 2026-10-29 03:05:04 +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 986216f4d6 fix: clear recent searches and calendar feeds when the last account signs out 2026-10-29 03:03:58 +01:00
Linus Rath 8be9743b80 fix: keep the Files trash out of the Recent files widget 2026-10-29 03:03:57 +01:00
Linus Rath c84c9ddde7 test: check that a range read stops at an account switch 2026-10-29 03:03:21 +01:00
Linus Rath 942f45179c fix: send a mailto unsubscribe to one address only and show what it sends 2026-10-29 03:02:33 +01:00
Linus Rath caeec2bdab fix: let Undo of a Files delete do nothing once another account is signed in 2026-10-29 03:00:35 +01:00
Linus Rath 99a4212804 fix: ask for the current code to change the password or turn off two-factor with TOTP on 2026-10-29 03:00:18 +01:00
Linus Rath a3b8b9fa9f test: check id reuse, the expiry read-back, the quota-before-address order and the Delete patch on live Stalwart 2026-10-29 03:00:16 +01:00
Linus Rath c0c3429c75 fix: forget a shared calendar refusal that arrives after a new sign-in 2026-10-29 02:59:53 +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 ef9561f16b fix: stop senders from faking a DKIM or DMARC pass in the authentication chips 2026-10-29 02:59:27 +01:00
Linus Rath d92a2ca835 fix: renew push for every signed-in account and show each account's push state in the notification settings 2026-10-29 02:57:24 +01:00
Linus Rath 323fea7451 fix: undo and restore against the drive as it is now, not the listing from before the delete 2026-10-29 02:57:10 +01:00
Linus Rath 41e8f00509 fix: keep a calendar write that outlasts an account switch out of the next account 2026-10-29 02:57:07 +01:00
Linus Rath 2167a9d700 fix: keep webmail filter periods, forwarding and exact address rules through a phone save #1152 2026-10-29 02:57:00 +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 f5e997ba92 fix: drop event handlers that follow a slash and run no inline handlers in the reader and composer 2026-10-29 02:54:23 +01:00
Linus Rath be04fcb179 fix: delete permanently only what is still in the trash, and read the drive again on returning to Files 2026-10-29 02:53:53 +01:00
Linus Rath 9ccb62286e fix: show a plain new-mail notification instead of another login's message when two logins share a JMAP account id 2026-10-29 02:51:24 +01:00
Linus Rath e47c05c58a fix: choose the reply From by an address the original names only in Bcc #87 2026-10-29 02:50:16 +01:00
Linus Rath a2caa4cca5 fix: register the app's own client on Stalwart 1.0 behind another public address and say when the server can't be reached 2026-10-29 02:44:00 +01:00
Linus Rath e993540ae6 fix: load the calendar again after an account switch overtook its first load 2026-10-29 02:43:21 +01:00
Linus Rath 65f00640ba fix: mark a message read when the notification's Delete moves it to Trash and the delete action says so #86 2026-10-29 02:42:00 +01:00
Linus Rath 77327ea856 fix: never patch or judge another client's push subscription that reuses this device's subscription id 2026-10-29 02:40:40 +01:00
Linus Rath 8a71495e1a fix: apply subscribed feed changes to imported events and stop a feed sync when the account changes 2026-10-29 02:40:13 +01:00
Linus Rath 28d928f6e9 fix: stop a refresh after sign-out from writing the removed account's tokens back 2026-10-29 02:39:31 +01:00
Linus Rath ce47c73f33 fix: keep extending push in the background after the app refreshed the subscription at launch 2026-10-29 02:37:52 +01:00
Linus Rath 388f46b5b9 fix: let the composer send and save again in the account it was opened in 2026-10-29 02:36:09 +01:00
Linus Rath d3d28d551e fix: show the relay push still goes through after the mail server refused a new one 2026-10-29 02:35:36 +01:00
Linus Rath 867ef20e51 Merge branch 's10/mail' into feat/stalwart-1.0 2026-10-29 02:34:22 +01:00
Linus Rath 2c05e7bd00 fix: keep the password sign-in from following a redirect to another server 2026-10-29 02:33:41 +01:00
Linus Rath db47162367 fix: register with the previous push relay again when the mail server refuses a new one 2026-10-29 02:33:11 +01:00
Linus Rath 93ea948045 fix: keep the working push registration when a re-register at the subscription limit moves to another relay 2026-10-29 02:31:06 +01:00
Linus Rath 7c91418336 feat: keep deleted files in a Files trash with Restore, Delete permanently and Empty trash 2026-10-29 02:30:36 +01:00
Linus Rath 80962e3079 Merge branch 's10/cal' into feat/stalwart-1.0 2026-10-29 02:27:44 +01:00
Linus Rath 3047d5c495 Merge branch 's10/push' into feat/stalwart-1.0 2026-10-29 02:22:54 +01:00
Linus Rath 8893a9e21e Merge branch 's10/auth' into feat/stalwart-1.0 2026-10-29 02:22:07 +01:00
Linus Rath 111af93a09 docs: say that Stalwart 1.0.0 serves JMAP Metadata in the folder and Sieve property comments 2026-10-29 02:22:05 +01:00
Linus Rath d76f296587 test: check keywords, submissions, contacts, search, Sieve and identities live on Stalwart 0.16 and 1.0 2026-10-29 02:22:05 +01:00
Linus Rath 547d9e5939 fix: drop the deleted messages from the list when the server refuses others of a bulk delete 2026-10-29 02:20:10 +01:00
Linus Rath 9daa3441c8 fix: keep a composer left open across an account switch from saving or deleting drafts in the other account 2026-10-29 02:15:14 +01:00
Linus Rath f733a54777 test: check the calendar live on Stalwart 1.0.0 and 0.16 with series, overrides, invitations and shares #89 2026-10-29 02:13:04 +01:00
Linus Rath b6c0cf1820 docs: say that Stalwart 1.0.0 answers the Metadata properties the calendar list leaves out 2026-10-29 02:12:16 +01:00
Linus Rath 7c495d03ab fix: let files in a shared folder be renamed and deleted on Stalwart 1.0, which sends rights for folders only 2026-10-29 02:11:28 +01:00
Linus Rath e03a38001d test: check push subscriptions live on Stalwart 0.16 and 1.0 2026-10-29 02:11:09 +01:00
Linus Rath 15171637cd fix: keep the own and a group's scheduled mail apart when their ids are the same 2026-10-29 02:10:26 +01:00
Linus Rath 87472ae937 Merge branch 's10/files' into feat/stalwart-1.0 2026-10-29 02:09:48 +01:00
Linus Rath 2f43adbc07 test: check sign-in, token renewal, sign-out and account security live on Stalwart 0.16 and 1.0 2026-10-29 02:09:21 +01:00
Linus Rath 9804b49979 fix: keep push working when the app isn't opened for a week 2026-10-29 02:08:10 +01:00
Linus Rath 1f190c9402 fix: keep one account's tasks and events out of another's calendar after an account switch 2026-10-29 02:07:52 +01:00
Linus Rath c6eb02d4cd fix: say why account security is unavailable when Stalwart 1.0 limits the app's sign-in 2026-10-29 02:07:47 +01:00
Linus Rath 1456e15d5c fix: keep accounts signed in when Stalwart 1.0 rotates the refresh token 2026-10-29 02:07:46 +01:00
Linus Rath 6ffd0473e5 fix: sign in through the server's own sign-in page on Stalwart 1.0 2026-10-29 02:07:46 +01:00
Linus Rath 96ef85159b fix: sign in with password and two-factor code on Stalwart 1.0 2026-10-29 02:07:36 +01:00
Linus Rath ec7e8e0530 docs: check webmail 1.10.0 to 1.18.0 against the app 2026-10-29 02:07:01 +01:00
Linus Rath 4d545f8f58 fix: show inline images whose part name is a path 2026-10-29 02:06:12 +01:00
Linus Rath 4ec2ccac11 fix: say the mail server refuses push relays that are not on a public domain 2026-10-29 02:03:24 +01:00
Linus Rath 18b8aa096f fix: send writes in shared folders to the account that holds them and offer the ones its rights allow 2026-10-29 02:01:47 +01:00
Linus Rath 20be110537 fix: keep the device sync push types when a subscription write fails for another reason 2026-10-29 02:00:50 +01:00
Linus Rath 7c775571c9 fix: load the calendar on Stalwart 0.16 when a range starts or ends in the hour the clocks change 2026-10-29 01:58:24 +01:00
Linus Rath 7d8e073587 fix: list files on Stalwart 1.0 when the account holds more nodes than one get allows #1385 2026-10-29 01:54:55 +01:00
Linus Rath f3e11d4c7c fix: stop asking an account that shares only mail for its calendars on every load on Stalwart 1.0 2026-10-29 01:54:55 +01:00
Linus Rath 913b37661f fix: send the quoted inline images along with replies and forwards 2026-10-29 01:53:59 +01:00
Linus Rath d2bb479ac6 fix: keep inline images of drafts and sent mail on Stalwart 1.0 2026-10-29 01:53:57 +01:00
Linus Rath 174c3fc8fa fix: create folders and upload files on Stalwart 1.0 and tell its symlinks from folders 2026-10-29 01:53:51 +01:00
Linus Rath 24232bf2e9 test: seed the live Stalwart test's own mail so it runs on any account and on 1.0 2026-10-29 01:38:30 +01:00
Linus Rath df4cb7688d fix: offer only the vacation and favicon settings the app has in the settings search 2026-10-29 01:37:46 +01:00
Linus Rath f1db6b488a Merge branch 'main' into feat/stalwart-1.0 2026-10-29 01:30:03 +01:00
Linus Rath f358d60584 fix: place calendar events at their clock time on DST change days 2026-10-25 14:18:45 +01:00
Linus Rath 9857fe1711 chore: update version number to 0.1.86 2026-10-25 12:14:47 +01:00
Linus Rath b4331a9bf9 feat: sign in with an access token 2026-10-25 12:14:30 +01:00
Linus Rath c08e9d6fa8 chore: update version number to 0.1.85 2026-10-23 20:23:48 +02:00
Linus Rath 470e7fc3ba fix: keep the drawers' account header below the status bar and sheet rows above the home indicator on iPhone 2026-10-23 19:51:48 +02:00
Linus Rath b3bd19cc8c ci: roll every android release out to the internal and closed testing tracks on google play 2026-10-23 16:48:22 +02:00
Linus Rath 95cfa809ef chore: update version number to 0.1.84 2026-10-22 23:09:57 +02:00
Linus Rath 3d3f4aec5a feat: log why the iOS notification extension falls back to the placeholder 2026-10-22 22:53:43 +02:00
Linus Rath 8303ff769f feat: let the iOS notification extension reach the active account without push setup 2026-10-22 22:53:42 +02:00
Linus Rath 7078dfe806 ci: sign the iOS notification extension with its own App Store profile 2026-10-22 22:53:42 +02:00
Linus Rath f62849d1fe feat: turn on push notifications on iOS through the relay's APNs transport 2026-10-22 22:53:42 +02:00
Linus Rath 4bb846b726 feat: add an iOS notification extension that shows sender and subject 2026-10-22 22:53:42 +02:00
Linus Rath 2618680b21 feat: keep iOS credentials where a notification extension can read them 2026-10-22 22:53:42 +02:00
lucletoffeandClaude Opus 5.5 45b4feeb58 feat(unified): let a single account turn on All mail / All unread / All starred
The drawer hid the whole Unified section unless a second account (with
cross-account on) or a merged group inbox fed it (#843), so a single
account never reached the cross-folder views - although they span the
inbox and every custom folder and repeat no single folder. Sieve-sorted
mail could only be read folder by folder.

Port the webmail's per-view toggles (enableCrossUnreadView,
enableCrossStarredView, enableCrossAllView; off by default) to Settings >
Layout. A turned-on view shows in the Unified section even when the
section would otherwise stay hidden; "All inboxes" and the per-role rows
keep the #843 rule. Alongside the section all three still show, as
before.

The toggles are labelled with the drawer rows' names: their
`label_mobile` in each RN overlay is that language's
`sidebar.unified_all_*` from the webmail catalog, so settings search
lands on the row. Descriptions are the webmail's own.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 16:40:35 +02:00
Linus Rath e420e3d576 fix: keep an occurrence's own change when another one moves onto its day
Changing every occurrence of a series takes its changed and cancelled
occurrences along, and two can still meet on one occurrence (bda118b).
A series on Mondays and Tuesdays that starts a day and an hour later
takes its first Monday's change onto the Tuesday, at the new time,
where the Tuesday's own override lands as well. Only an override that
stayed exactly where it was won such a meeting. Here neither did, so
the order the server listed the overrides in decided, and it could
keep the Monday's change and lose the Tuesday's cancellation.

The override that stays where it is now wins, then the one that stays
on its own day, then the first; the others are left out and logged, as
in the web app. The rest of a series split "this and following" keys
its overrides the same way.
2026-09-26 05:10:27 +02:00
Linus Rath 726e087795 fix: keep offline edits when the app cannot read an account's server record
The app records which server filled each account's caches and checks it
with every new session (a330d77). A read of that record that failed -
a storage error, such as a busy database - answered "none recorded",
so the session counted as the first sight of the account: its queued
offline edits were dropped, with a notice that the mail server had
changed when it had not, and the record was written anew. Had the
server been replaced, that record hid the change from every later
check.

A read that fails now fails the check: nothing is dropped or written,
and the next session checks again. As with any failed check, signing
in goes on.
2026-09-26 05:09:49 +02:00
Linus Rath b307ad1b26 fix: keep a working account when a switch fails during sign-in or start-up
A switch that began while an account was being added, or while a cold
start still brought the last account up, made that sign-in or restore
give up (efd31de). When the switch then failed as well, what it put
back was no working account:

- during an add-account sign-in, the switch fell back on a snapshot of
  the client, which served no account before the new session was up
  and the new account after. The app stayed on the account in use while
  every call failed with "Not connected", the session retry saw a
  session and returned, and tapping the account did nothing; or the
  client served the new account under the one in use, whose calendars,
  contacts and identities then came from it, and whose sends went
  through it.
- a cold start shows the last account's cached mail, drawer and all,
  while its session loads. A switch from there that failed left the
  app on the sign-in screen, with every account still registered.

A switch now falls back on the connection an add-account sign-in under
way saved, and resumes the queue of offline edits the sign-in held, as
a failed sign-in does itself. It waits for a restore under way and
switches from the account restored. A sign-in waits for switches under
way and a restore for neither, so none of them waits on another.
2026-09-26 05:09:12 +02:00
Linus Rath 8918b789e9 fix: keep the organizer and list occurrences once in the rest of a split series
Two faults in the new series "This and following events" makes, found
on 0.16.23 with the app's own code against the test server (1.0 did
not show them):

- The new series named its organizer only when the editor wrote one,
  which it does only for an event that had none. Created without it,
  0.16.23 makes the user who creates the event its organizer, whatever
  the owner participant says: a sharee who split the owner's meeting
  became the organizer of the rest (organizerCalendarAddress
  mailto:usera, isOrigin false, the invitation sent from the sharee),
  and a series organized under an alias would have moved to the user's
  main address. 1.0 takes the owner participant. The new series now
  carries the series' organizer address whenever it has participants.
- The overrides the new series takes over kept the old series' SEQUENCE
  while the new series starts at its own, and 0.16.23 listed each such
  occurrence twice, as changed and as the series has it, for the
  organizer and the attendee alike. The overrides now leave the old
  number behind.
2026-09-26 04:03:48 +02:00
Linus Rath bda118bc55 fix: keep occurrence changes on their days when a series moves or ends
Three faults in how a series' changed and cancelled occurrences follow a
change of the whole series (found in review, live on 1.0 and 0.16.23 for
the web's version of the same code):

- A rule that names its days - a series on Mondays and Tuesdays, one on
  the 5th and the 6th of each month - keeps its occurrences on those days
  when its start moves. Moving every occurrence by a day moved each
  override along anyway, so the Monday's landed on the Tuesday's key and
  one silently overwrote the other: the cancelled Tuesday came back with
  the Monday's title, and the Monday lost its change. Under such a rule
  every override but the first occurrence's now stays on its day, at
  the series' new time; the same holds for the new series of "this and
  following". Two overrides that still meet on one key (an hourly series
  turned all-day) no longer overwrite each other: the one that stays
  where it is, else the first, is kept, and the others are left out and
  logged.
- "Does not repeat" for every occurrence kept the first occurrence's
  override on the single event, because a series' start counts as an
  occurrence of any rule: cancelled, 1.0 left the event out of every
  range query (still stored); retitled, 0.16.23 listed it twice. A series
  made single now keeps no overrides.
- The SEQUENCE a change of every occurrence raises (0.16.6 to 0.16.23
  refuse a lower one) came from the series as the app held it. A series
  opened from a reminder or a link was that copy, possibly older than the
  server's. The store now reads a series from the server every time a
  scope edit asks for it, so the raise, the overrides a split or a move
  rewrites and the rule all start from what the server holds.
2026-09-26 04:03:05 +02:00
Linus Rath e6baa751a9 fix: keep single edits off a series opened as itself and end series west of UTC
More edits of a recurring meeting went wrong at the edges of a series
(0.16 and 1.0 alike, attendees included):

- A series the app shows as itself - opened from a reminder or an
  event link, or one an edit just made recurring (0.16 before 0.16.20
  showed it until the next reload) - took "This event only" as a change
  of the whole series: `CalendarEvent/set` on the series' id with the
  editor's title, start and rule. "Delete this event only" deleted the
  series. The series now stands for its first occurrence: a change
  becomes an override of it, in two writes when the user organizes the
  meeting, sends invitations and an attendee has answered (Stalwart
  sends a new override only to attendees who have not), and a delete
  excludes it. "This and following" from the first occurrence changes,
  or deletes, the whole series instead of ending it before its first
  occurrence. A series opened from a reminder on a calendar shared with
  the user is now known to the store, so the change goes to the owner's
  account under the series' own id.
- An edit that makes an event recurring, or single again, now loads the
  range again, so the grid shows the occurrences instead of the series
  itself.
- 0.16 (0.16.23 checked) reads a rule's UNTIL as the series' wall clock
  taken for UTC, so in a zone behind UTC a split or "delete this and
  following" kept the occurrence it was to end before. The UNTIL is now
  earlier by the zone's offset (the larger of the split's day and the
  day before), which ends the series there on 0.16 and 1.0 alike.
- 0.16.6 to 0.16.23 send a change of one occurrence with its SEQUENCE
  raised but do not keep the raise, so a later change of every
  occurrence - a new title - went out with a lower number for that
  occurrence and the attendee's server refused it. A change of every
  occurrence that the user as organizer sends, of something the
  attendees see, on a series with changed occurrences, now raises the
  SEQUENCE of the series and of each changed occurrence by one. 1.0
  raises and keeps the number itself; the result there is the same. The
  app's update sends `sequence` as it is, so nothing has to keep it.
- An existing override written on the base event (0.16 before 0.16.20,
  and ranges the device expands) gets the series' organizer again when
  it has none: 0.16.19 refuses an answer to such an occurrence. 1.0
  refuses the key inside an override once, and the store writes the
  override again without it.
2026-09-26 04:02:22 +02:00
Linus Rath 9025efee77 fix: move a series' changed and cancelled occurrences along when it moves or splits
A series keeps its changed and cancelled occurrences as overrides keyed
by recurrence id, where the rule puts each occurrence. The app's series
edits left them behind and made more of a series than was asked (0.16
and 1.0 alike, attendees included):

- "All events" that moved the series kept the overrides under the old
  ids: a cancelled occurrence came back, a retitled one showed twice.
  It also wrote everything the editor showed to the series, so a new
  room on an occurrence with a title of its own renamed the series,
  and it measured a move on the calendar's clock, so across a week in
  which only one of two zones changes to or from summer time the series
  moved an hour more or less than picked.
- "This and following events" ended the rule but not the overrides
  after the split, which showed on next to the new series; the new
  series got the whole COUNT again and started at the opened
  occurrence's own time in the calendar's zone. Deleting "this and
  following" left the later overrides too.
- A failed create of the rest of the series put back only the rule, and
  without telling the attendees the truncation had told.

Now, as on the web (`lib/recurrence-scope-edit.ts`, a port of the
webmail module of the same name):

- "All" moves each override with its occurrence in the same update: by
  the series' move, onto the same day at the new time when the series
  turns all-day or timed. An occurrence moved on its own keeps its time,
  except the one the editor was opened on; a new rule drops the
  overrides it no longer has, and overrides that move ask their
  attendees again when the series does. Only the fields the user
  changed go to the series, and the move is measured on the series'
  own clock.
- The split removes the overrides from the split on together with the
  rule and gives them to the new series, re-keyed and with its
  participants (0.16 keeps an override as an event of its own). The new
  series goes on from the split's recurrence id in the series' zone,
  moved only by the editor's move; its COUNT is what is left, an UNTIL
  stays, and a rule the user changed is taken as it is. It takes the
  old series' details, not the opened occurrence's own ones.
- A failed create puts back the rule and the whole overrides map (a
  pointer fails on a series left without overrides), with the
  truncation's scheduling, and fails the save; a store that answers
  with nothing counts as failed.
- The truncation tells the attendees only when the user organizes the
  series and sends invitations; an attendee ends their own copy
  quietly.

The scope handling moved from CalendarScreen into the new module, where
it is tested. The editor hands its dates to the save. An invitation to
one occurrence alone, which has no series, is changed or deleted as
itself for every scope instead of making a second event.
2026-09-26 04:01:39 +02:00
Linus Rath 198e952f61 fix: tell attendees when someone with write access changes the owner's meeting
Someone who writes to a calendar shared with them (read & write) changes
the owner's meeting there on the owner's behalf: Stalwart sends the
change from the addresses of the account the event is in. The app
decided who organizes by the user's own addresses only, so it took the
sharee for an attendee (0.16 and 1.0 alike):

- the editor wrote no participants, hid "Send invitations" and sent
  nothing, so the attendees kept the old time and their answers
- "All events" asked nobody again, and "This and following events"
  made the rest a new series that kept the old answers, which Stalwart
  sends to nobody who had answered

The app now reads `isOrigin`, which both versions compute from the
addresses of the account the event is in: true for the owner's meeting
and every expanded occurrence of it, false for an invitation the owner
got. `userOrganizes` counts such a copy as the organizer's; an
invitation is still changed without messages. "This and following"
asks by the old series, since the new one carries no `isOrigin` yet.

The editor no longer leaves the user's own main address off the
attendees it opens with, only the organizer's (or the user's, for an
event that names none yet): a user who invited their main address to a
meeting organized under an alias lost it on the next save.
2026-09-26 04:00:59 +02:00
Linus Rath 3e6e3d28e1 fix: keep a scheduled message in the app when its new time loses a recipient
"Send now" and "Reschedule" create a replacement submission and then
cancel the original. The server checks the recipients again for the
replacement, and a throttle used up since the original was taken ("452
4.4.5 Rate limit exceeded") can refuse some of them. The app kept the
original only when every recipient was refused; on a partial refusal it
cancelled it, and a recipient the original had taken lost the message.
Port of the webmail's aa99f038.

- the original's delivery status is read with its envelope, in the same
  request; when the replacement refuses a recipient the original took,
  the replacement is withdrawn (cancelled, or only its record destroyed
  when it took nobody) and the original goes at its old time to
  everyone it took
- a recipient the original was refused for as well loses nothing, so
  the reschedule goes ahead and names it; without a delivery status of
  the original, every recipient counts as taken
- a replacement that can't be withdrawn lets the reschedule go ahead,
  naming the refused recipients: "Send now" holds it for a second only,
  so it may be out already, and keeping both would send the others two
  copies; a withdraw whose answer got lost is read back, and when it
  went through the original is kept, since going ahead would reach
  nobody
- the alert in Scheduled and the undo bar's toast lead with "The
  message is still scheduled for ..." and say "Nothing was changed: the
  server refused ..."; they used to open with "Not sent", which invited
  sending the message a second time

Checked live on 0.16.23 and 1.0 with a relay-refused recipient: the
lookup, going ahead, and withdrawing a partly refused replacement while
the original stays at its old time. One new string, with the webmail's
translations in all 27 languages.
2026-09-26 02:51:36 +02:00
Linus Rath 5dcf5991ab fix: open the composer before the session is up without crashing 2026-09-26 01:46:42 +02:00
Linus Rath 7b18063630 fix: stop a start-up crash, and stale views after sign-out or a new server
- The app crashed on every cold start whose cached folders held a
  shared or group folder, which on Stalwart 1.0 every group member has:
  the sidebar works out the accounts to span while it renders, before
  the session is up, and the JMAP client's account id throws until one
  is. The client now also names its account without throwing, for code
  that runs while rendering; the mail list's account helpers (the
  sidebar's accounts, a row's account, the viewer's pages) use it, and
  a read receipt sent by itself waits for the session. API calls still
  throw without a session.
- Signing out of the last account left the app on an empty mail view
  ("No mailboxes found") with nothing to retry: the account list was
  read before the account was removed, so the app switched to the
  account it had just signed out of, which did nothing. It is read
  again now, and the sign-in screen shows. A failed switch to the next
  account no longer puts the signed-out one back: when its server does
  not answer, that account shows offline, as on a cold start, and one
  whose sign-in is refused is skipped.
- After signing in with the same address on another server (another
  port) or on a server reinstalled at the address (0.16 to 1.0), Files
  kept showing the old server's drive without asking the new one, and
  actions on its rows would have used the old server's ids. The local
  account id leaves the port out and stays the same there, so the
  drive now also starts over when the server behind the session
  changes, and is read again with every new session. The account id
  itself is unchanged: it keys the stored credentials and every
  per-account cache, and changing it would sign such accounts out and
  orphan their data.
2026-09-26 01:44:47 +02:00
Linus Rath 1caf73a3e9 fix: never send an occurrence change to the next occurrence
A first change to one occurrence that keeps the attendees' answers goes
out in two writes: the override as the occurrence stands, quietly, then
the change as an update of it. The occurrence is looked up again in
between, because the new override can give it another synthetic id.
When that lookup failed, the change went to the old id. Up to Stalwart
0.16.20 synthetic ids are positions in the series' expansion
(calendar_event/mod.rs), so after the new override the old id can name
the next occurrence, which then got the change and its scheduling
messages. 0.16.21 and 1.0 key the ids by recurrence id, and the id
stays (checked live on 1.0 and 0.16.23).

Now, when the occurrence is not found again, found twice, or the lookup
fails, nothing more is sent and the editor says the change was not
saved. The quiet override changes nothing that shows; the calendar is
read again so a retry uses the ids the series has now, and goes out as
an update of that override, which reaches every attendee. The message
is translated into all 26 languages.
2026-09-26 01:44:05 +02:00
Linus Rath a330d77d6d fix: keep push, queued edits and iCal feeds safe on a replaced server
The app compares the server behind an account with the one it recorded
and drops what came from a replaced one. The released version recorded
none, so on the first start of this one there is nothing to compare
with, and a server replaced before the update (Stalwart 1.0 is a fresh
install) went unnoticed:

- the push subscription expiry the released version recorded made the
  subscription the new server lacks look revoked, and push was turned
  off for good; it is now forgotten on the first sight of an account, so
  the subscription is re-created
- the queue of offline edits the released version left was replayed
  against the new server, where its ids name other messages, so a
  queued delete deleted another message; it is now discarded on the
  first sight, with the notice a replaced server shows. Nothing proves
  a queued edit belongs to the server: the queue records none, a
  reinstalled Stalwart may accept old JMAP states, and a queued move or
  delete took its message out of the cached list, so there is no copy
  to compare the server's message with

iCal subscriptions recognise their mirror calendar by name, but a sync
still imported the feed into whatever calendar the recorded id named:

- the feed now goes only into a calendar that carries the
  subscription's name, and otherwise into a new mirror; when the name
  can't be read, the sync fails and is tried again
- a rename the server refused renamed the subscription anyway, which
  then no longer recognised its mirror: pruning stopped and removing the
  subscription kept the calendar. Now nothing changes and the error is
  shown
- a replaced server claimed subscriptions made before they recorded
  their account for every account with the same JMAP id, another
  server's account included. They now go only when no other signed-in
  account has that id, or may have it, and a sync that finds their
  mirror records the account on them
2026-09-26 01:43:22 +02:00
Linus Rath efd31de051 fix: never send an account's offline edits to another account
Adding an account put the new account's session in the JMAP client as
soon as it was up, while the queue of offline edits (outbox) stayed on
the account in use, and ready to replay, until the new account's server
was checked. A retry, a network flip or the return from the OAuth
browser in that window replayed the queue through the new account, and
edits to the user's own mail took its JMAP account id. Stalwart's ids
repeat across accounts, so a queued move or delete of one account's
message "x" hit the other account's "x". An edit made in that window ran
the same way, and so did the rest of a replay already under way.

Account switches could overlap too: tapping B, then C before B's session
was up, left the mail list on C while B's switch, finishing last, put B
in the client and its queue in the outbox, so edits in C's list went to
B under C's ids. An older switch that failed last put back the account
it had left, under the newer one.

- the outbox replays and runs edits only while the client serves the
  account the queue belongs to, checked before every op, and holds the
  queue while another account signs in; a failed sign-in gives the
  client and the queue back to the account in use
- the outbox resumes only for the account the mail list shows
- every account change (a switch, a sign-in, a sign-out) takes a
  generation, and one a newer change overtook touches neither the client
  nor the stores; the client no longer installs the session of a load or
  sign-in that a newer one overtook
- a failed switch puts back the account the first of overlapping
  switches left, and tapping that account while they are under way goes
  straight back to it
- a session retry gives way to a switch or sign-in under way
- tokens refreshed for an account being left stay with that account;
  they were saved into the credentials of the account loaded meanwhile,
  which then signed in as the other one
2026-09-26 01:42:35 +02:00
Linus Rath 206756e13c fix: stop treating narrow refusals as total ones in the app's sends and exports
Two places took a narrow refusal from the server for a complete one.
Port of the webmail's 7977a6ed.

- Sending at a full submission quota (Stalwart 1.0): when making room
  found nothing, no search ran for five minutes, for any send. Scheduled
  mail and undo-send holds can go once their message is out, and until
  the next search every send failed with "ask your administrator". A
  send the user starts, a read receipt included, now always looks again;
  the pause stays for automatic read receipts.
- vCard export: Stalwart answers Blob/get only in accounts the user is a
  member of, so for an address book shared with the user it came back
  `forbidden` ("You are not an owner of account ...") and the export fell
  back to one photo download after the other. It checks the access to
  each blob by itself, whichever account the request names, so
  `getBlobsAsBase64` now asks again in the user's own account, on 0.16
  and 1.0 alike.
2026-09-26 00:52:43 +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 00619751d7 fix: never send a rescheduled message twice and handle partial refusals
"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)
2026-09-25 23:51:39 +02:00
Linus Rath a562ea59b8 fix: keep attendees' answers on save and deliver occurrence changes
Every save in the event editor rebuilt the participants with new ids
and put every attendee back to needs-action, the user's own answer on
an invitation included. Saves now keep each attendee's id and answer;
new attendees are asked. As on the web, only an organizer who moves an
event (its start, end, all-day or recurrence rule; not a time zone
rewrite, and not a zero-length event) asks the others again.

The series scopes did not fit that:

- "All events" wrote the opened occurrence's start, length, zone and
  participants onto the series: a stretched occurrence stretched every
  one, and one day's decline spread to the whole series. The series now
  keeps its own list and takes only the attendees the user added or
  removed, and its times change only when the occurrence moved (by the
  same delta), its rule only when it changed.
- "This and following" makes a new series, which Stalwart sends only to
  attendees who have not answered: its attendees are asked again.
- Stalwart sends a new occurrence override, the first change to one
  occurrence, only to attendees who have not answered (0.16.6 to 1.0).
  A change that keeps answers now goes out in two writes: the override
  as the occurrence stands without scheduling, then the change as an
  update of it, to the occurrence looked up again. Checked live on 1.0
  and 0.16.23.
- A save of an invitation someone else organizes writes no participants
  while the user left them alone, and never asks for scheduling, so the
  organizer gets no reply for a changed alert. The switch to send
  invitations is hidden there.

Also:

- a reminder change on one occurrence of a series that uses the
  calendar's default alerts turns the defaults off for the series first
  (keeping what it shows under ids of its own); 1.0 kept showing the
  defaults
- all-day and floating occurrences expanded on the device no longer
  carry the master's utcStart/utcEnd, so their reminders are scheduled
  for each day; master instants that are not the master's own (0.16.23's
  year 32548, or the next occurrence's) are ignored
- the editor warns when the attendees and the organizer go over the
  calendars capability's maxParticipantsPerEvent, and a refusal for it
  (Stalwart 1.0 enforces it, answers included) is explained in the
  editor, the series dialogs, the detail sheet and the invitation
  banner, in all languages; a failed save used to fail silently
- refused event creates, updates and answers keep their SetError type
- Calendar/get asks for its properties by name
2026-09-25 23:50:29 +02:00
Linus Rath f490886a8b fix: keep iCal subscriptions safe when the mail server was replaced
When the server behind an account is replaced (a fresh Stalwart 1.0
install at the address 0.16 ran on), its ids start over. The app
already dropped its caches then, but kept what the user set up with
server ids inside:

- iCal subscriptions kept the ids of their mirror calendars. The next
  sync deleted the "stale" events of whatever calendar now had the id
  and filled it with the feed, and "Remove subscription" destroyed it.
  Now the account's subscriptions keep their feed, name, colour and
  interval but lose their mirror, and the next sync makes a new one as
  adding a subscription does. Renaming or removing an unlinked one
  touches no calendar. Events are only pruned from, and remove only
  destroys, a calendar that still carries the subscription's name.
  Subscriptions now record the signed-in account, so accounts on two
  servers that got the same JMAP id no longer share them.
- the preferred sender identity kept its id, which a new server can
  give to another address (live: identity "b" is usera@ on 0.16.23 and
  team@ on 1.0). It is forgotten for the account's old and new JMAP
  id, which the app now records with the server fingerprint.
- hidden calendars kept their ids. The list is shared by all accounts,
  so it is cleared.

Offline changes the drop discards are now announced once ("3 offline
changes were discarded because the mail server changed"), in all
languages.

Revoking this device from another one turns push off here again: a
subscription gone before its recorded expiry counts as revoked, as it
did before the server check came in. A replaced server forgets the
recorded expiry, so there the subscription is re-created instead.
2026-09-25 23:49:36 +02:00
Linus Rath a2ca98e0e8 fix: send offline edits after a restart and never to another account
The queue of offline edits (outbox) and the offline mail cache were
attached to an account only when the email store switched to it. A cold
start restores the view of the account it showed last, so there was no
switch: the queue from the last session was never loaded or sent, an
edit made meanwhile was dropped with a console warning, and the offline
cache neither stored nor served bodies.

On an account switch the queue was loaded and flushed right away,
before the new account's session was up and before the check for a
replaced server. The JMAP client still served the previous account
then, so queued edits could reach it.

- the email store attaches the outbox and the offline cache whenever it
  is pointed at an account, also the one it already shows
- the outbox holds its ops until the auth store resumes it, once the
  account's session is up and its server was checked; edits made before
  that are queued, and edits made while the queue loads are kept
- the JMAP client serves no account while it loads or signs in to
  another one; a failed switch puts the previous account and its queue
  back
- dropping an account's queue or cache for a replaced server cancels a
  read of it still under way
- the offline sync stops when the cache or the client moves to another
  account, instead of filing one account's mail under another
2026-09-25 23:48:51 +02:00
Linus Rath 2e26ff7b64 fix: report refused tags and re-check the sort order after a server change
Stalwart 1.0 refuses keywords over 128 bytes and more than 100
keywords on one message, per message in Email/set `notUpdated`; a
read-only shared folder refuses any keyword on every version. The app
stopped a large selection at the first refused batch, "mark folder as
read" ignored refusals, and the list's swipe, bulk and tag actions let
the rejection go unhandled, so nothing was said. The keyword sort
polarity verdict was stored per server address for a week, so a server
reinstalled as 1.0 (which sorts as the RFC says, where 0.16.8-0.16.22
invert) kept the old reading.

- Email/set writes send every batch before reporting the first refused
  message, and "mark folder as read" marks the rest, then reports the
  refusal. The list shows "Tagging failed" or "Failed to update email"
  for swipe, bulk and tag actions, and the viewer's automatic
  mark-as-read puts the message back to unread when it is refused.
- The sort polarity verdict is keyed by the server fingerprint as well
  (stored as `keyword-sort-polarity:v2`), so a replaced or upgraded
  server is probed afresh. An upgrade that leaves the capabilities
  alone, such as 0.16.22 to 0.16.23, still waits out the week.
- Tests pin that Stalwart 1.0's `$hasattachment` / `$hasnoattachment`
  never show as tags or as tags to recover; the app lists no other
  keywords, and tag ids stay within 128 bytes by construction.
2026-09-25 20:59:19 +02:00
Linus Rath 42751a6b29 fix: keep the app's requests away from Stalwart 1.0's unfinished JMAP Metadata
Stalwart 1.0 advertises `urn:ietf:params:jmap:metadata` by default, but
its handlers are still `todo!()`: a get that selects `metadata` or
`privateMetadata`, or a /changes call with `ignoreMetadataOnlyChanges`,
panics the request on the server. The app never sends either, but its
Mailbox/get and SieveScript/get calls asked for the server's default
properties, which a later release could grow to include metadata.

- Mailbox/get (own folders, shared accounts' folders, by id, and the
  polling fallback) and SieveScript/get ask for explicit RFC property
  lists, the same as today's defaults on 0.16 and 1.0 (checked live).
- A test checks the requests the folder, filter, delta-sync and polling
  paths build, on 0.16.23 and 1.0 sessions, and scans the app's sources
  for metadata properties, the /changes argument and the capability.
2026-09-25 20:58:33 +02:00
Linus Rath 97daf76f05 fix: start over when the mail server behind an account was replaced
Stalwart 1.0 has no migration from 0.16, so the way there is a fresh
install at the same address, where ids start over ("a", "b", ...) and
name other objects. The app persists a lot that only means something on
the server it came from: JMAP states and query states, offline bodies
keyed by message id, an outbox that replays stored ids (a `destroy`
included), the calendar cache and push de-dupe lists. It also turned
push off for good when the server no longer had the subscription, and
would have taken another device's subscription for its own once the
reinstalled server handed out the recorded id again.

- Email/changes, Email/queryChanges and Mailbox/changes answers whose
  Stalwart state went below the one asked from count as not computable
  and lead to a full fetch (`stalwartChangeId`, a port of the webmail
  guard). It only reads accounts that list `urn:stalwart:jmap` and
  states in the exact form Stalwart writes, so it never trips while
  states grow or on other servers.
- Every time a session is established, a fingerprint of the server (API
  URL, primary account id, the session's and the primary account's
  capability names) is compared with the one recorded for the account.
  On a change, before anything reads them, the account's folders,
  lists, states and query states, offline bodies, queued and failed
  outbox ops, the calendar and contacts caches and the push de-dupe
  lists are dropped. Preferences, templates, tags and iCal
  subscriptions stay. The first sight of an account only records it.
- The whole outbox goes: every op names a message by its server id.
  Offline edits still waiting are lost, but never a message the user
  wrote, because the outbox holds no sends.
- A subscription the server no longer has counts as expired and is
  re-created, and the recorded one is only ours when its device client
  id matches, on setup and on teardown. Revoking this device from
  another one therefore no longer keeps push off here; turning it off
  on the device does.
2026-09-25 20:57:46 +02:00
Linus Rath e8a9c58d18 fix: keep push notifications working when a mailbox is shared with you
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.
2026-09-25 20:56:57 +02:00
Linus Rath 3f4a81b7ad fix: handle shared folders, symlinks and file names on Stalwart 1.0
Files in the app assumed Stalwart 0.16 in several places:

- Stalwart 1.0 lists the owner's folders above a node shared with the
  user, without dates and with every right false. The app took the
  owner's top folder for the shared root, so the folder actually shared
  sat several levels down, under folders where rename, delete and move
  could only fail. Those placeholders are now left out of the root and
  of folder listings.
- A node counted as a folder when it had no blobId, so 1.0's symlinks
  showed up as empty folders that could be opened. Listings ask for
  `nodeType` (returned since 0.16.6, never sent in FileNode/set), and
  a symlink is an inert row with a link icon: it can be renamed, moved
  and deleted, but not opened, downloaded, duplicated or shared.
- Only "/" was checked in names, and uploads were not checked at all,
  so `<>:"\|?*` already failed on 0.16. New folder and rename now apply
  the naming rules from the filenode capability plus 1.0's stricter
  ones (control characters, 255 bytes, reserved stems such as
  `con.txt`) and say what is wrong. Uploaded names are made acceptable
  instead: `con.txt` becomes `con_.txt`, also on 0.16.6 to 0.16.23.
- One refused file stopped a whole upload. The batch now goes on and
  reports the files that failed at the end.
- Every media type over 30 characters, which is every Office document,
  was stored as application/octet-stream. New files get the bare
  lowercase media type 1.0 requires, and no `size`, which 1.0 refuses
  when it differs from the blob.
- File errors keep their JMAP type, and the alerts say why an action
  failed: too many files or folders (`overQuota`), folders nested too
  deeply, a refused name, missing rights, or a file too large.
2026-09-25 20:46:41 +02:00
Linus Rath 1129ba339b fix: save filters whose rules contain */, line breaks or odd header names
The app stores its filter rules as JSON inside a `/* */` comment at the
top of the Sieve script. A `*/` anywhere in a rule name, a value or the
auto-reply text ended that comment early and turned the rest of the
rules into script, and a CR or LF in a rule name did the same to its
`# Rule:` comment. Stalwart (sieve-rs, on 0.16 and 1.0) also accepts
only the letters A-Z, digits, `-` and `_` in a header name. Any of these
made the server refuse the whole script, so saving any filter, and the
auto-reply with it, failed.

- `*/` in the embedded rules is written as `*\/`, which JSON reads back
  as `*/`, so every client parses the rules as before
- the `# Rule:` comment carries the rule name without line breaks, and
  the parser matches the comment back to its rule by that form
- the filter editor trims header names, turns autocorrect off for them
  and refuses to save one the server would reject, with a translated
  message; the generator leaves a rule with such a header name out of
  the script, since dropping only the condition could widen an "all of"
  rule, and keeps it in the stored rules for editing
2026-09-25 20:46:00 +02:00
Linus Rath e2c76d46c7 fix: stop the app re-reading the same folder changes forever
When Mailbox/changes answered with `hasMoreChanges` (Stalwart caps one
answer at 5000 changes, other servers earlier), the folder sync applied
that page but kept the old state and ran again, which asked for and got
the same first page back, without end.

The sync now follows `newState` page by page, storing each page's state
as it applies it. After five pages, or when a page does not move the
state on or can't be calculated, it reads the whole folder list instead,
which takes one request whatever changed.
2026-09-25 20:45:18 +02:00
Linus Rath 90b78da8c8 fix: keep time zones, default alerts and calendar renames working on 1.0
Four smaller calendar changes that Stalwart 1.0 needs.

- Time zone: 1.0 fails every CalendarEvent get and query whose
  timeZone its database lacks, with invalidArguments "Invalid timeZone".
  It matches IANA names case-sensitively and knows no offsets, so a
  "+01:00" or "europe/berlin" that Intl accepts empties the calendar.
  0.16 reads such a zone as UTC. The zone is now checked with a get of
  no events that rides along with the first calendar list; once refused,
  reads ask in UTC and log one warning, and a read refused before the
  check answered is repeated once in UTC. The comment that said Stalwart
  ignores bad values is corrected.
- Default alerts: 1.0 fills a calendar's default alerts into the
  alerts of an event that uses them. The editor wrote them back with
  useDefaultAlerts false on every save, freezing today's defaults into
  the event. It now writes nothing while the reminders are unchanged; a
  change turns the defaults off and saves the alerts under ids of their
  own, since 1.0 drops alerts saved under a default alert's id.
- Calendar colour: renaming sent the colour along, and 1.0 accepts only
  #rgb, #rrggbb or a CSS name, so a calendar another client coloured
  with an 8-digit hex could not be renamed. The edit sheet and the iCal
  subscription settings now send only the fields that changed.
- Reply stubs: 0.16 stored an attendee's answer to one occurrence
  without a title, place or description, and 1.0 builds that occurrence
  from the override alone. Hydration now takes a missing title from the
  series, and the place and description when the override removes the
  title.
2026-09-25 20:44:34 +02:00
Linus Rath 6eeee8df54 fix: explain sharing when Stalwart 1.0 keeps its directory closed
Stalwart 1.0 refuses Principal/query with `forbidden` unless the
administrator allows directory queries, which is off by default. The
app took the missing Principal/get answer for an empty directory: the
calendar share sheet said "Sharing is not available on this server.",
the file share sheet said "No other users found", and existing shares
showed bare ids.

- `listPrincipals` says whether the listing is complete, `restricted`
  or failed. On `forbidden` it falls back to Principal/get without ids,
  which 1.0 still answers with the user's own account, their groups and
  the accounts that shared something with them.
- Principal/get asks for `id`, `type`, `name`, `description` and `email`
  only; 1.0's default list adds `capabilities` and `accounts`.
- Both share sheets explain a restricted directory under the search
  box. Only 1.0's own refusal ("The administrator has disabled directory
  queries.") gets the wording that points at Stalwart's sharing
  settings; any other refusal gets a generic one, and a failed listing
  says so.
- A sharee the app can't name shows as "Unknown user" with its id, and
  can still be changed or removed.

On 0.16 with the default roles the listing is complete and the sheets
behave as before.
2026-09-25 20:42:07 +02:00
Linus Rath 6be51a2bab fix: find multi-word, address and long-word searches in the app on Stalwart 1.0
Stalwart 1.0 reads a text filter value that ends in `*` as one prefix
over the whole string, not split into words and capped at 16 bytes. The
app added `*` to every word it searched for, so on 1.0 `project budget`
became the prefix "project* budget", an address was no single word and
`Geschäftsführung` (18 bytes) was over the cap: all three found
nothing. Quoted phrases broke too, since `"annual report" 2024` went out
as one value ending in `*`.

The search text now goes out as typed, split the way the webmail does
it (5967a1c4): each quoted phrase and each `word*` becomes a condition
of its own, ANDed with one condition that holds the other words, so a
query without quotes or stars is sent unchanged. The folder, all-folders
and tag-view searches, the unified inbox and the from, to, subject and
body filters all use it, and `searchEmails` puts `inMailbox` next to
the text conditions in one AND instead of on an operator.

0.16's built-in index dropped the `*` anyway, so its results stay the
same; there a quoted phrase is an AND of its words. On 1.0 a typed
`word*` is now a real prefix.

The fake server of the store tests now matches text the way Stalwart's
built-in index does, which the old `*` after every word fails.
2026-09-25 20:41:11 +02:00
Linus Rath 2af3892fec fix: apply recurrence overrides as patches when the app expands a series
Stalwart 1.0 returns recurrence overrides as RFC 8984 PatchObjects,
with keys like participants/p1/participationStatus and nulls that
remove. Expanding a series on the device spread them onto the
occurrence, so a declined occurrence stayed accepted, a changed room
stayed the same and the pointer keys became junk properties. Moved or
resized occurrences also kept the time and length the rule gave them,
and were placed in the range by their original date.

- apply overrides as patches: pointer paths decoded per RFC 6901, null
  deletes, shorter paths first, a whole participants map completed
  from the series
- time an occurrence from its patched start, time zone and duration,
  and range-check a moved occurrence by where it is now
- fall back to the JSCalendar duration when the master's utcStart or
  utcEnd don't parse (0.16.23 returned the year 32548 for a counted
  series), and never keep the master's instants on another occurrence
- merge a change into an existing override without keys that are
  prefixes of one another, and match an occurrence listed twice
  against the members its override patches

The device expands series on 0.16.6 to 0.16.19, after a created series
and when a range can't be expanded on the server. 0.16 returns whole
override values, which apply the same way.
2026-09-25 20:38:43 +02:00
Linus Rath 2a3febcc79 fix: edit or answer one occurrence of a series in the app on Stalwart 1.0
Stalwart 1.0 refuses series properties such as the organizer on a
single occurrence unless they repeat the series' value, and refuses
them inside a recurrence override on the base event. The event editor
wrote the user's first address as organizerCalendarAddress on every
save of an event with attendees, so editing one occurrence failed with
"This property cannot be modified on a single occurrence." whenever the
series was organized by someone else, under another alias or in
another letter case. Removing every attendee from one occurrence failed
the same way.

- leave series keys and per-event metadata out of changes to one
  occurrence, as Stalwart's validate.rs lists them, and pointers at a
  participant's calendarAddress
- write overrides on the base event as before (0.16.19 needs the
  organizer there) and, when the server refuses series keys inside
  them, repeat the write once without them and remember that
- answer one occurrence through its synthetic id without the organizer
- let the editor keep the organizer of an event: an invitation the
  user received is no longer rewritten with the user as organizer, and
  organizerCalendarAddress is only written for an event that has none
- keep the SetError type on a refused event update

0.16.20 to 0.16.23 dropped these keys on synthetic ids anyway; the
base-event override payload for older servers is unchanged.
2026-09-25 20:36:58 +02:00
Linus Rath ef6f1540dd fix: keep the app's calendar filled past a year on Stalwart 1.0
Stalwart 1.0 refuses an expanded CalendarEvent/query that spans more
than the account's maxExpandedQueryDuration (P52W1D by default) with
expandDurationTooLarge, and one that expands to more than 3000
occurrences with cannotCalculateOccurrences. The scrolling views load
windows of up to about 460 days, and refresh() reloads the whole
window on every calendar push, after series edits and on pull to
refresh, so the calendar showed a raw error over stale events from
then on. The fallback to expanding on the device only knew 0.16's
invalidArguments.

Expanded range queries are now cut into chunks a day shorter than the
advertised limit, with overlapping borders, sent together as far as
maxCallsInRequest allows, and their ids de-duplicated. A chunk the
server still refuses is halved; if a week still expands to too many
occurrences, the whole range is expanded on the device as before.

The expanded query keeps a filter of the two bounds only: 1.0 refuses
operators there, so filtering several calendars would take a query per
calendar, and the store loads every calendar anyway. 0.16 advertises
the same limit without enforcing it and returns the same events.
2026-09-25 20:35:00 +02:00
Linus Rath f52ef7db7e fix: show and keep contact photos in the app on Stalwart 1.0
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.
2026-09-25 20:32:13 +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 28ade775e3 fix: load calendar events and tasks in the app on Stalwart 1.0
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.
2026-09-25 19:45:19 +02:00
1040 changed files with 196381 additions and 15028 deletions
+9 -7
View File
@@ -441,6 +441,7 @@ jobs:
cp android/app/build/outputs/mapping/playRelease/mapping.txt "$MAPPING"
echo "aab=$AAB" >> "$GITHUB_OUTPUT"
echo "mapping=$MAPPING" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@v4
with:
@@ -450,11 +451,11 @@ jobs:
${{ steps.artifact.outputs.mapping }}
if-no-files-found: error
# Runs once PLAY_SERVICE_ACCOUNT_JSON is set. Play only accepts API
# uploads after the first bundle was uploaded by hand in the Console, and
# releases of an app that was never published must be drafts; switch
# `status` to `completed` after the first production release.
- name: Upload to Google Play (internal testing, draft)
# Runs once PLAY_SERVICE_ACCOUNT_JSON is set (Play only accepts API
# uploads after the first bundle was uploaded by hand in the Console).
# Every release goes out to the internal and closed (beta) testers right
# away; closed testing may wait for Google's review first.
- name: Upload to Google Play (internal and closed testing)
if: env.PLAY_SERVICE_ACCOUNT_JSON != ''
uses: r0adkll/upload-google-play@v1
with:
@@ -462,5 +463,6 @@ jobs:
packageName: org.bulwarkmail.mobile
releaseFiles: ${{ steps.artifact.outputs.aab }}
mappingFile: ${{ steps.artifact.outputs.mapping }}
track: internal
status: draft
releaseName: ${{ steps.artifact.outputs.version }}
tracks: internal,beta
status: completed
+61 -39
View File
@@ -59,9 +59,6 @@ jobs:
- name: Install JS dependencies
run: npm ci
- name: Generate the iOS project
run: npx expo prebuild --platform ios --clean
- name: Check for signing credentials
id: creds
env:
@@ -69,14 +66,69 @@ jobs:
ASC_KEY: ${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY }}
TEAM: ${{ secrets.APPLE_TEAM_ID }}
PROFILE: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }}
NSE_PROFILE: ${{ secrets.IOS_NSE_PROVISIONING_PROFILE_BASE64 }}
run: |
if [ -n "$CERT" ] && [ -n "$ASC_KEY" ] && [ -n "$TEAM" ] && [ -n "$PROFILE" ]; then
if [ -z "$NSE_PROFILE" ]; then
# The notification service extension (plugins/ios-push) is its
# own App ID and needs its own App Store profile.
echo "::error::IOS_NSE_PROVISIONING_PROFILE_BASE64 is not set: the notification extension (org.bulwarkmail.mobile.NotificationService) can't be signed."
exit 1
fi
echo "signed=true" >> "$GITHUB_OUTPUT"
else
echo "signed=false" >> "$GITHUB_OUTPUT"
echo "::notice::Signing secrets are not configured - compiling without signing. No IPA will be produced."
fi
- name: Install the provisioning profiles
if: steps.creds.outputs.signed == 'true'
id: profiles
env:
APP_PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }}
NSE_PROFILE_BASE64: ${{ secrets.IOS_NSE_PROVISIONING_PROFILE_BASE64 }}
run: |
# Xcode moved the profile directory in 16; write both so this does not
# silently stop working on a runner image bump.
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
mkdir -p ~/Library/Developer/Xcode/UserData/Provisioning\ Profiles
# One App Store profile per target: the app and its notification
# service extension (plugins/ios-push).
for side in app nse; do
if [ "$side" = app ]; then DATA="$APP_PROFILE_BASE64"; else DATA="$NSE_PROFILE_BASE64"; fi
PROFILE="$RUNNER_TEMP/$side.mobileprovision"
echo "$DATA" | base64 -d > "$PROFILE"
# A .mobileprovision is a CMS-signed plist, so it has to be decoded
# before anything inside it can be read.
PLIST=$(security cms -D -i "$PROFILE")
UUID=$(echo "$PLIST" | plutil -extract UUID raw -)
NAME=$(echo "$PLIST" | plutil -extract Name raw -)
# application-identifier is "TEAMID.bundle.id" - deriving the bundle
# ID from the profile keeps ExportOptions honest if app.config.js
# changes.
APP_ID=$(echo "$PLIST" | plutil -extract Entitlements.application-identifier raw -)
BUNDLE_ID="${APP_ID#*.}"
EXPIRY=$(echo "$PLIST" | plutil -extract ExpirationDate raw -)
cp "$PROFILE" ~/Library/MobileDevice/Provisioning\ Profiles/"$UUID".mobileprovision
cp "$PROFILE" ~/Library/Developer/Xcode/UserData/Provisioning\ Profiles/"$UUID".mobileprovision
echo "${side}_uuid=$UUID" >> "$GITHUB_OUTPUT"
echo "${side}_name=$NAME" >> "$GITHUB_OUTPUT"
echo "${side}_bundle_id=$BUNDLE_ID" >> "$GITHUB_OUTPUT"
echo "$side: profile '$NAME' ($UUID) for $BUNDLE_ID, expires $EXPIRY"
done
- name: Generate the iOS project
env:
# plugins/ios-push names each target's profile: the command line
# can't, it would give every target the same one.
IOS_APP_PROFILE: ${{ steps.profiles.outputs.app_uuid }}
IOS_NSE_PROFILE: ${{ steps.profiles.outputs.nse_uuid }}
run: npx expo prebuild --platform ios --clean
- name: Resolve workspace and scheme
id: xcode
run: |
@@ -141,38 +193,6 @@ jobs:
echo "$ASC_KEY_BASE64" | base64 -d > ~/private_keys/AuthKey_${ASC_KEY_ID}.p8
chmod 600 ~/private_keys/AuthKey_${ASC_KEY_ID}.p8
- name: Install the provisioning profile
if: steps.creds.outputs.signed == 'true'
id: profile
env:
PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }}
run: |
PROFILE="$RUNNER_TEMP/profile.mobileprovision"
echo "$PROFILE_BASE64" | base64 -d > "$PROFILE"
# A .mobileprovision is a CMS-signed plist, so it has to be decoded
# before anything inside it can be read.
PLIST=$(security cms -D -i "$PROFILE")
UUID=$(echo "$PLIST" | plutil -extract UUID raw -)
NAME=$(echo "$PLIST" | plutil -extract Name raw -)
# application-identifier is "TEAMID.bundle.id" - deriving the bundle ID
# from the profile keeps ExportOptions honest if app.config.js changes.
APP_ID=$(echo "$PLIST" | plutil -extract Entitlements.application-identifier raw -)
BUNDLE_ID="${APP_ID#*.}"
EXPIRY=$(echo "$PLIST" | plutil -extract ExpirationDate raw -)
# Xcode moved the profile directory in 16; write both so this does not
# silently stop working on a runner image bump.
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
mkdir -p ~/Library/Developer/Xcode/UserData/Provisioning\ Profiles
cp "$PROFILE" ~/Library/MobileDevice/Provisioning\ Profiles/"$UUID".mobileprovision
cp "$PROFILE" ~/Library/Developer/Xcode/UserData/Provisioning\ Profiles/"$UUID".mobileprovision
echo "uuid=$UUID" >> "$GITHUB_OUTPUT"
echo "name=$NAME" >> "$GITHUB_OUTPUT"
echo "bundle_id=$BUNDLE_ID" >> "$GITHUB_OUTPUT"
echo "Profile '$NAME' ($UUID) for $BUNDLE_ID, expires $EXPIRY"
- name: Archive
if: steps.creds.outputs.signed == 'true'
env:
@@ -192,8 +212,7 @@ jobs:
-archivePath "$RUNNER_TEMP/Bulwark.xcarchive" \
CODE_SIGN_STYLE=Manual \
DEVELOPMENT_TEAM="$TEAM_ID" \
CODE_SIGN_IDENTITY="Apple Distribution" \
PROVISIONING_PROFILE_SPECIFIER="${{ steps.profile.outputs.uuid }}"
CODE_SIGN_IDENTITY="Apple Distribution"
# Signing failures report a symptom ("no profiles found") rather than the
# setting that caused it, and a re-run costs ~30 minutes. Dump the
@@ -215,8 +234,10 @@ jobs:
id: export
env:
TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
PROFILE_NAME: ${{ steps.profile.outputs.name }}
BUNDLE_ID: ${{ steps.profile.outputs.bundle_id }}
PROFILE_NAME: ${{ steps.profiles.outputs.app_name }}
BUNDLE_ID: ${{ steps.profiles.outputs.app_bundle_id }}
NSE_PROFILE_NAME: ${{ steps.profiles.outputs.nse_name }}
NSE_BUNDLE_ID: ${{ steps.profiles.outputs.nse_bundle_id }}
run: |
cat > "$RUNNER_TEMP/ExportOptions.plist" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
@@ -230,6 +251,7 @@ jobs:
<key>provisioningProfiles</key>
<dict>
<key>${BUNDLE_ID}</key><string>${PROFILE_NAME}</string>
<key>${NSE_BUNDLE_ID}</key><string>${NSE_PROFILE_NAME}</string>
</dict>
<key>uploadSymbols</key><true/>
<key>destination</key><string>export</string>
+3 -1
View File
@@ -32,7 +32,9 @@ tags:
visible: "Photos & Videos"
timeout: 10000
- assertVisible: "Camera"
- assertVisible: "Files"
# A server with Files offers "Upload from this device" and "Attach from
# Files" instead of a plain "Files".
- assertVisible: "Upload from this device|Files"
- takeScreenshot: compose-02-attach-sheet
- evalScript: ${output.pickPhoto = ORIENTATION != 'landscape'}
# The system photo picker runs in another process: XCTest sees none of its
+11 -3
View File
@@ -11,8 +11,12 @@ tags:
direction: DOWN
centerElement: true
timeout: 20000
# Swipe the row itself: the avatar checkbox's label has the subject too, and
# a swipe that starts on it only selects the row (#91).
- swipe:
from: ".*Grüße aus München.*"
from:
id: email-row
text: ".*Grüße aus München.*"
direction: LEFT
duration: 400
# The undo bar shows for 5 s; when it goes, the Compose button slides into
@@ -27,14 +31,18 @@ tags:
- takeScreenshot: swipe-02-undone
- swipe:
from: ".*Grüße aus München.*"
from:
id: email-row
text: ".*Grüße aus München.*"
direction: RIGHT
duration: 400
- waitForAnimationToEnd:
timeout: 3000
- takeScreenshot: swipe-03-read-toggled
- swipe:
from: ".*Grüße aus München.*"
from:
id: email-row
text: ".*Grüße aus München.*"
direction: RIGHT
duration: 400
- waitForAnimationToEnd:
+23 -5
View File
@@ -7,7 +7,7 @@ tags:
---
- runFlow: ../subflows/signed-in.yaml
# Move via More → Move to...
# Move via More → Move to
- runFlow:
file: ../subflows/open-message.yaml
env:
@@ -17,9 +17,11 @@ tags:
visible: "More actions"
timeout: 10000
- takeScreenshot: actions-01-more
- tapOn: "Move to..."
# Both the menu row and the sheet title read "Move to" (webmail catalog),
# so wait for the folder list instead of the title.
- tapOn: "Move to"
- extendedWaitUntil:
visible: "Move to..."
visible: "Projects"
timeout: 10000
- tapOn:
text: "Projects"
@@ -63,12 +65,21 @@ tags:
direction: DOWN
centerElement: true
timeout: 20000
- longPressOn: ".*Grüße aus München.*"
# The row, not its avatar checkbox (whose label has the subject too).
- longPressOn:
id: email-row
text: ".*Grüße aus München.*"
- extendedWaitUntil:
visible: "Clear selection"
timeout: 10000
- takeScreenshot: actions-04-selected
# Star (or Unstar, when an earlier run left it starred), then toggle back.
# On a tablet the list column is narrow and Star sits in the More menu.
- runFlow:
when:
notVisible: "Star|Unstar"
commands:
- tapOn: "More actions.*"
- tapOn: "Star|Unstar"
# A bulk action ends selection mode by itself.
- runFlow:
@@ -76,7 +87,14 @@ tags:
visible: "Clear selection"
commands:
- tapOn: "Clear selection"
- longPressOn: ".*Grüße aus München.*"
- longPressOn:
id: email-row
text: ".*Grüße aus München.*"
- runFlow:
when:
notVisible: "Unstar|Star"
commands:
- tapOn: "More actions.*"
- tapOn: "Unstar|Star"
- runFlow:
when:
+3 -1
View File
@@ -48,9 +48,11 @@ tags:
- extendedWaitUntil:
visible: "Keep scheduled"
timeout: 10000
# The dialog's button (below its message), not its title or the button
# behind it. Cancelling now moves the message back to Drafts.
- tapOn:
text: "Cancel send"
below: "Keep scheduled|.*will not be delivered.*"
below: ".*will not be (sent|delivered).*"
- extendedWaitUntil:
notVisible: ".*${output.subject}.*"
timeout: 30000
+19 -1
View File
@@ -34,7 +34,12 @@ tags:
timeout: 5000
- takeScreenshot: calendar-04-week
# Create
# Create. The button starts the event on the selected day: make that today
# (a stray tap, such as one through the notification prompt, can select
# another day), so the schedule below finds it after Go to today.
- tapOn: "Go to today"
- waitForAnimationToEnd:
timeout: 3000
- tapOn: "New event"
- extendedWaitUntil:
visible: "Save"
@@ -46,6 +51,19 @@ tags:
- pressKey: Enter
- waitForAnimationToEnd:
timeout: 3000
# A new event starts on the calendar's default reminders; Customize turns
# them into the event's own list, which has Add reminder.
- scrollUntilVisible:
element: "Customize|Add reminder"
direction: DOWN
timeout: 10000
- runFlow:
when:
visible: "Customize"
commands:
- tapOn: "Customize"
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element: "Add reminder"
direction: DOWN
+10 -8
View File
@@ -44,20 +44,22 @@ tags:
timeout: 20000
- takeScreenshot: files-04-folder-created
- longPressOn: ".*${output.folder}.*"
# Delete moves it to the trash at once, with Undo and no question (like the
# webmail). A tap while the actions sheet still slides in can miss: tap
# again while Delete is still there.
- extendedWaitUntil:
visible: "Delete"
timeout: 10000
- repeat:
while:
notVisible: "Delete this item.*"
visible: "Delete"
times: 3
commands:
- tapOn: "Delete"
- tapOn:
text: "Delete"
optional: true
- waitForAnimationToEnd:
timeout: 3000
- extendedWaitUntil:
visible: "Delete this item.*"
timeout: 10000
- tapOn:
text: "Delete"
below: "Delete this item.*"
- extendedWaitUntil:
notVisible: ".*${output.folder}.*"
timeout: 20000
+8 -13
View File
@@ -1,4 +1,6 @@
# Every settings page opens and shows its content.
# Every settings page opens and shows its content, first half (13-settings-more
# has the rest: one flow for all 24 pages ran into the 15-minute flow limit on
# the iPad simulator).
appId: org.bulwarkmail.mobile
name: Settings pages
tags:
@@ -11,6 +13,11 @@ tags:
visible: "Search settings"
timeout: 20000
- takeScreenshot: settings-00-list
# The search also lists the settings inside each page under it, so a common
# word ("Folders", "Calendar") pushes the page's row below the keyboard.
# Each Q matches its page near the top. A PROOF the search lists under the
# row would pass on the iPad without the page, so those differ. The Themes
# cards are one element each, named by the theme: check the page's text.
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: account, ROW: ".*@example.org, .*", PROOF: "Logged-in accounts|Display Name"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: language, ROW: "Language, Region & Time, .*", Q: "Language", PROOF: "Date Format"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: notifications, ROW: "Notifications, .*", Q: "Notifications", PROOF: "Email Notifications"}}
@@ -22,17 +29,5 @@ tags:
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: vacation, ROW: "Out of Office, .*", Q: "Office", PROOF: "Reply automatically"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: filters, ROW: "Filters, .*", Q: "Filters", PROOF: "Email Filters"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: templates, ROW: "Templates, .*", Q: "Templates", PROOF: "Email Templates"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: folders, ROW: "Folders, .*", Q: "Folders", PROOF: ".*Inbox.*"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: tags, ROW: "Tags, .*", Q: "Tags", PROOF: "Email Tags"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: downloads, ROW: "Downloads, .*", Q: "Downloads", PROOF: "Email .*eml.* filename"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: security, ROW: "Security, .*", Q: "Security", PROOF: "Account Security|Detecting server capabilities.*"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: content, ROW: "Content & Senders, .*", Q: "Content", PROOF: "External Content"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: calendar, ROW: "Calendar, .*", Q: "Calendar", PROOF: "Default view"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: contacts, ROW: "Contacts, .*", Q: "Contacts", PROOF: "Group by first letter"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: files, ROW: "Files, .*", Q: "Files", PROOF: "Default View"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: sidebar-apps, ROW: "Sidebar Apps, .*", Q: "Sidebar", PROOF: "Keep Apps Loaded"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: themes, ROW: "Themes, .*", Q: "Themes", PROOF: "The default light and dark palettes."}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: storage, ROW: "Storage & backup, .*", Q: "Storage", PROOF: "Offline mail.*"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: about, ROW: "About, .*", Q: "About", PROOF: "This device"}}
- tapOn:
id: tab-mail
+32
View File
@@ -0,0 +1,32 @@
# Every settings page opens and shows its content, second half (see 12-settings).
appId: org.bulwarkmail.mobile
name: Settings pages, continued
tags:
- features
---
- runFlow: ../subflows/signed-in.yaml
- tapOn:
id: tab-settings
- extendedWaitUntil:
visible: "Search settings"
timeout: 20000
- takeScreenshot: settings-00-list-more
# The search also lists the settings inside each page under it, so a common
# word ("Folders", "Calendar") pushes the page's row below the keyboard.
# Each Q matches its page near the top. A PROOF the search lists under the
# row would pass on the iPad without the page, so those differ. The Themes
# cards are one element each, named by the theme: check the page's text.
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: folders, ROW: "Folders, .*", Q: "New folder", PROOF: ".*Inbox.*"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: tags, ROW: "Tags, .*", Q: "Tags", PROOF: "Email Tags"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: downloads, ROW: "Downloads, .*", Q: "Downloads", PROOF: "Email .*eml.* filename"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: security, ROW: "Security, .*", Q: "Security", PROOF: "Account Security|Detecting server capabilities.*"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: content, ROW: "Content & Senders, .*", Q: "Content &", PROOF: "External Content"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: calendar, ROW: "Calendar, .*", Q: "Week start", PROOF: "Default view"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: contacts, ROW: "Contacts, .*", Q: "Group by", PROOF: "Sort by last name"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: files, ROW: "Files, .*", Q: "Files", PROOF: "Default View"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: sidebar-apps, ROW: "Sidebar Apps, .*", Q: "Sidebar", PROOF: "Keep Apps Loaded"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: themes, ROW: "Themes, .*", Q: "Themes", PROOF: "Pick a colou?r theme.*"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: storage, ROW: "Storage & backup, .*", Q: "Storage", PROOF: "Offline mail.*"}}
- runFlow: {file: ../subflows/settings-page.yaml, env: {NAME: about, ROW: "About, .*", Q: "About", PROOF: "This device"}}
- tapOn:
id: tab-mail
+4 -1
View File
@@ -10,8 +10,11 @@ tags:
env:
CAMERA: deny
- tapOn: "Scan a sign-in code.*"
# Denied for good: "Camera access is disabled..."; not asked yet (the
# simulator doesn't always apply the denial up front): "Bulwark Mail needs
# camera access..." (English catalog) or the built-in fallback wording.
- extendedWaitUntil:
visible: "Camera access is.*|Scanning a sign-in QR code uses the camera.*"
visible: "Camera access is.*|.*needs camera access.*|Scanning a sign-in QR code uses the camera.*"
timeout: 15000
- takeScreenshot: scanner-01-denied
- tapOn: "Close"
@@ -11,9 +11,13 @@ appId: org.bulwarkmail.mobile
- setOrientation: PORTRAIT
- waitForAnimationToEnd:
timeout: 3000
# Maestro repeats a tap that seems to change nothing; here the repeat
# landed on the app once the alert was gone (a day in the calendar's
# month grid, which opened that day). Tap once.
- tapOn:
text: "Allow"
optional: true
retryTapIfNoChange: false
- runFlow:
when:
true: ${ORIENTATION == 'landscape'}
+11
View File
@@ -18,3 +18,14 @@ appId: org.bulwarkmail.mobile
- tapOn:
text: "Continue"
below: "Sign in with a link."
# A tap while the alert is still animating in can go nowhere: tap again if
# it is still up.
- waitForAnimationToEnd:
timeout: 3000
- runFlow:
when:
visible: "Sign in with a link."
commands:
- tapOn:
text: "Continue"
below: "Sign in with a link."
+12 -3
View File
@@ -3,6 +3,9 @@
# covers it (the tap then opens another tab): on the phone, scroll it to the
# middle. The iPad's list is a column Maestro's scroll (in the middle of the
# screen) doesn't reach; its rows fit on screen.
# The avatar's "Select <sender>: <subject>" checkbox mentions the subject
# too, and a tap on it selects the row instead of opening it (#91): match
# the row itself by its id.
appId: org.bulwarkmail.mobile
---
- runFlow:
@@ -10,7 +13,9 @@ appId: org.bulwarkmail.mobile
true: ${DEVICE != 'tablet'}
commands:
- scrollUntilVisible:
element: ".*${SUBJECT}.*"
element:
id: email-row
text: ".*${SUBJECT}.*"
direction: DOWN
centerElement: true
visibilityPercentage: 100
@@ -20,13 +25,17 @@ appId: org.bulwarkmail.mobile
true: ${DEVICE == 'tablet'}
commands:
- scrollUntilVisible:
element: ".*${SUBJECT}.*"
element:
id: email-row
text: ".*${SUBJECT}.*"
direction: DOWN
timeout: 30000
# A tap while the list still moves lands on another row.
- waitForAnimationToEnd:
timeout: 5000
- tapOn: ".*${SUBJECT}.*"
- tapOn:
id: email-row
text: ".*${SUBJECT}.*"
# The message toolbar's "more" button: "Reply All" shows on the phone, but
# the iPad pane has a quick reply box instead for mail to one recipient.
- extendedWaitUntil:
+5 -3
View File
@@ -18,10 +18,12 @@ appId: org.bulwarkmail.mobile
- inputText: ${Q}
- waitForAnimationToEnd:
timeout: 3000
# Now and then a typed character goes missing: type it again.
- runFlow:
when:
# Now and then the simulator drops or garbles a typed character
# (seen: "Storagew,"): type it again, a few times if need be.
- repeat:
while:
notVisible: ${ROW}
times: 3
commands:
- tapOn: "Search settings"
- eraseText: 40
+8
View File
@@ -25,6 +25,14 @@ appId: org.bulwarkmail.mobile
element: "Sign out"
direction: DOWN
timeout: 30000
# The scroll can stop with the button half under the tab bar, where
# a tap reaches the tab bar instead: scroll on to the end of the list.
- swipe:
start: 50%,70%
end: 50%,30%
duration: 400
- waitForAnimationToEnd:
timeout: 3000
- tapOn: "Sign out"
- extendedWaitUntil:
visible: "The account is removed from this device.*"
@@ -26,3 +26,13 @@ appId: org.bulwarkmail.mobile
- tapOn:
text: "Sign in"
index: 1
# Stalwart 1.0.1 asks for consent after Sign in, once per client grant;
# 0.16 and repeat sign-ins go straight back to the app.
- waitForAnimationToEnd:
timeout: 5000
- runFlow:
when:
visible: "Allow"
commands:
- takeScreenshot: stalwart-consent-page
- tapOn: "Allow"
+7 -2
View File
@@ -4,10 +4,15 @@
# field and type again. The caret is still at the end of what was just
# typed, so eraseText (which deletes before the caret) empties it; selecting
# the text with a long press didn't work and appended instead. SECRET=yes
# skips the check (a password field shows dots).
# skips the check (a password field shows dots). TAP=no types into the
# field that has the focus already.
appId: org.bulwarkmail.mobile
---
- tapOn: ${HINT}
- runFlow:
when:
true: ${TAP != 'no'}
commands:
- tapOn: ${HINT}
- waitForAnimationToEnd:
timeout: 3000
- inputText: ${TEXT}
+13
View File
@@ -11,12 +11,25 @@ appId: org.bulwarkmail.mobile
env:
HINT: "user@example.com"
TEXT: ${LOGIN}
# On the iPhone, Safari's keyboard toolbar (Previous, Next, Done) can sit
# over the password field, and a tap there leaves the focus in Email (the
# password then went into Email). Its Next moves the focus to the password.
- evalScript: ${output.webNext = false}
- runFlow:
when:
visible: "Next"
commands:
- tapOn: "Next"
- waitForAnimationToEnd:
timeout: 2000
- evalScript: ${output.webNext = true}
- runFlow:
file: type-web.yaml
env:
HINT: "Enter your password"
TEXT: ${PASSWORD}
SECRET: "yes"
TAP: "${output.webNext ? 'no' : 'yes'}"
- tapOn: "Sign in"
- runFlow:
when:
+83 -31
View File
@@ -12,6 +12,7 @@ import { Mail, Calendar, BookUser, HardDrive, Settings } from './src/components/
import { startLiveUpdates, type LiveUpdatesHandle } from './src/api/push-stream';
import { jmapClient } from './src/api/jmap-client';
import { pruneEmailSubmissions } from './src/api/submissions';
import type { StateChange } from './src/api/types';
import { dispatchStateChange } from './src/lib/state-change-bus';
import {
@@ -34,6 +35,7 @@ import {
getInitialNotificationTap,
getStoredRelayBaseUrl,
notificationTapJmapAccountId,
renewOtherPushAccounts,
resyncPushNotifications,
teardownPushNotificationsForAccount,
type NotificationTapPayload,
@@ -59,6 +61,8 @@ import { useEmailStore } from './src/stores/email-store';
import { loadDetail, prefetchMessage } from './src/lib/email-detail-cache';
import { useHasCalendar, useHasContacts, useHasFiles } from './src/lib/capabilities';
import { useSettingsStore } from './src/stores/settings-store';
import { startPolicySync, usePolicyFeature } from './src/stores/policy-store';
import { startScreenPrivacySync } from './src/lib/screen-privacy';
import { useLocaleStore } from './src/stores/locale-store';
import { useLayout } from './src/lib/use-layout';
import { toast } from './src/stores/toast-store';
@@ -74,10 +78,12 @@ import { signOutWidgets, startWidgetSync } from './src/widgets/sync';
import { draftContextFromEmail } from './src/lib/draft-context';
import {
acceptSignInLink,
enterLinkAccount,
handleDeepLink,
parseDeepLink,
routeParkedSignInLink,
shareToDeepLink,
type AccountSwitchResult,
type DeepLink,
} from './src/navigation/linking';
import { usePendingSignInLinkStore } from './src/navigation/pending-sign-in-link';
@@ -99,20 +105,26 @@ const navigationRef = createNavigationContainerRef<RootStackParamList>();
// on every getInitialURL(), and a sign-in link must not run twice.
let initialSignInLinkRead = false;
// Move to the account a notification, reminder or link names. An account
// that was signed out meanwhile has nothing safe to open.
async function switchToAccount(accountId: string): Promise<AccountSwitchResult> {
const auth = useAuthStore.getState();
if (auth.activeAccountId === accountId) return true;
if (!useAccountStore.getState().getAccountById(accountId)) return false;
await auth.switchAccount(accountId);
return useAuthStore.getState().activeAccountId === accountId ? 'switched' : false;
}
async function navigateToNotificationTap(payload: NotificationTapPayload): Promise<void> {
if (!navigationRef.isReady()) return;
// The notification carries the account it was generated for. If the user
// has since switched to a different account (or had a different one active
// when the notification arrived), opening EmailThread under the active
// account would fetch the email from the wrong server and fail.
const auth = useAuthStore.getState();
if (payload.accountId && payload.accountId !== auth.activeAccountId) {
const account = useAccountStore.getState().getAccountById(payload.accountId);
if (!account) return; // account was logged out — nothing safe to open.
await auth.switchAccount(payload.accountId);
if (useAuthStore.getState().activeAccountId !== payload.accountId) return;
}
// account would fetch the email from the wrong server and fail. After a
// switch the stack starts over on that account's inbox, so Back from the
// message lands there instead of on the old account's screens.
if (!(await enterLinkAccount(navigationRef, payload.accountId, switchToAccount))) return;
prefetchMessage({ id: payload.emailId, threadId: payload.threadId });
navigationRef.navigate('EmailThread', {
@@ -130,12 +142,7 @@ async function navigateToNotificationTap(payload: NotificationTapPayload): Promi
// under the account it was scheduled for. The target is parked for
// CalendarScreen, which also picks it up when it mounts after a cold start.
async function openCalendarReminder(target: CalendarReminderTarget): Promise<void> {
const auth = useAuthStore.getState();
if (target.appAccountId && target.appAccountId !== auth.activeAccountId) {
if (!useAccountStore.getState().getAccountById(target.appAccountId)) return;
await auth.switchAccount(target.appAccountId);
if (useAuthStore.getState().activeAccountId !== target.appAccountId) return;
}
if (!(await enterLinkAccount(navigationRef, target.appAccountId, switchToAccount))) return;
setPendingCalendarOpen(target);
// On a cold start the navigator mounts right after the auth gate flips.
for (let attempt = 0; attempt < 50 && !navigationRef.isReady(); attempt++) {
@@ -170,13 +177,7 @@ async function openDeepLink(link: DeepLink): Promise<void> {
return false;
}
},
switchAccount: async (accountId) => {
const auth = useAuthStore.getState();
if (auth.activeAccountId === accountId) return true;
if (!useAccountStore.getState().getAccountById(accountId)) return false;
await auth.switchAccount(accountId);
return useAuthStore.getState().activeAccountId === accountId;
},
switchAccount: switchToAccount,
});
// A widget or a link elsewhere brought the app up for this; say it failed
// rather than leave the user wondering why the app just opened.
@@ -232,6 +233,12 @@ function MainTabsNavigator({ navigation }: NativeStackScreenProps<RootStackParam
const hasContacts = useHasContacts();
const hasFiles = useHasFiles();
const disabledTabStyle = { opacity: 0.4 } as const;
// The organisation policy can turn a whole area off (webmail feature
// gates); its tab goes away rather than showing as unavailable.
const calendarAllowed = usePolicyFeature('calendarEnabled');
const contactsAllowed = usePolicyFeature('contactsEnabled');
const filesAllowed = usePolicyFeature('filesEnabled');
const hiddenTab = { tabBarItemStyle: { display: 'none' as const }, tabBarButton: () => null };
const t = useLocaleStore((state) => state.t);
// The webmail's navigation names; a tab the server lacks says so.
const tabLabels = {
@@ -324,10 +331,11 @@ function MainTabsNavigator({ navigation }: NativeStackScreenProps<RootStackParam
tabBarIcon: ({ color, size }) => <Calendar size={size} color={color} />,
tabBarItemStyle: hasCalendar ? undefined : disabledTabStyle,
tabBarAccessibilityLabel: hasCalendar ? tabLabels.calendar : unavailable(tabLabels.calendar),
...(calendarAllowed ? null : hiddenTab),
}}
listeners={{
tabPress: (e) => {
if (!hasCalendar) e.preventDefault();
if (!hasCalendar || !calendarAllowed) e.preventDefault();
},
}}
/>
@@ -340,10 +348,11 @@ function MainTabsNavigator({ navigation }: NativeStackScreenProps<RootStackParam
tabBarIcon: ({ color, size }) => <BookUser size={size} color={color} />,
tabBarItemStyle: hasContacts ? undefined : disabledTabStyle,
tabBarAccessibilityLabel: hasContacts ? tabLabels.contacts : unavailable(tabLabels.contacts),
...(contactsAllowed ? null : hiddenTab),
}}
listeners={{
tabPress: (e) => {
if (!hasContacts) e.preventDefault();
if (!hasContacts || !contactsAllowed) e.preventDefault();
},
}}
/>
@@ -356,10 +365,11 @@ function MainTabsNavigator({ navigation }: NativeStackScreenProps<RootStackParam
tabBarIcon: ({ color, size }) => <HardDrive size={size} color={color} />,
tabBarItemStyle: hasFiles ? undefined : disabledTabStyle,
tabBarAccessibilityLabel: hasFiles ? tabLabels.files : unavailable(tabLabels.files),
...(filesAllowed ? null : hiddenTab),
}}
listeners={{
tabPress: (e) => {
if (!hasFiles) e.preventDefault();
if (!hasFiles || !filesAllowed) e.preventDefault();
},
}}
/>
@@ -422,7 +432,17 @@ export default function App() {
// Device sync (Android, #34): live changes, the app's own edits and the
// foreground ask Android to sync; accounts are checked against Android's.
startDeviceSyncTriggers();
return useNetworkStore.getState().init();
// The organisation policy of the active account's webmail: the cached
// one at once, a fresh one on start and on every account switch.
const stopPolicySync = startPolicySync();
// Settings > Security > Block screenshots.
const stopScreenPrivacy = startScreenPrivacySync();
const stopNetwork = useNetworkStore.getState().init();
return () => {
stopPolicySync();
stopScreenPrivacy();
stopNetwork?.();
};
}, []);
// Home-screen widgets follow the signed-in data; once the last account is
@@ -635,18 +655,39 @@ export default function App() {
return () => { cancelled = true; };
}, [addAccountForLink]);
// The server refused the stored sign-in of an account (auth store
// keepForSignIn): open the sign-in to it, filled in, over what is cached.
const reauthTarget = useAuthStore((s) => s.reauth);
React.useEffect(() => {
if (!isAuthenticated || !reauthTarget) return;
let cancelled = false;
void (async () => {
for (let attempt = 0; attempt < 50 && !navigationRef.isReady(); attempt++) {
await new Promise((resolve) => setTimeout(resolve, 100));
if (cancelled) return;
}
if (cancelled || !navigationRef.isReady()) return;
if (navigationRef.getCurrentRoute()?.name === 'AddAccount') return;
navigationRef.navigate('AddAccount');
})();
return () => { cancelled = true; };
}, [isAuthenticated, reauthTarget]);
// Re-register the device with the configured relay once authenticated,
// and whenever the FCM token rotates. Honours the user's notification
// preference - flipping it off tears down THIS account's subscription so
// notifications stop arriving for it. Other logged-in accounts keep their
// setups intact. An account the user turned push off for, or whose device
// registration was revoked, stays off (see resyncPushNotifications).
// setups intact. An account the user turned push off for on this device
// stays off (see resyncPushNotifications).
const emailNotificationsEnabled = useSettingsStore(
(s) => s.emailNotificationsEnabled,
);
const activeAccountId = useAuthStore((s) => s.activeAccountId);
React.useEffect(() => {
if (!isAuthenticated || !client) return;
// The setup asks the server through the client: it waits for the
// session, and runs again once one comes up (an account added or
// switched to, a cold start that began offline).
if (!isAuthenticated || !client || !haveLiveSession) return;
let cancelled = false;
const doSetup = async () => {
@@ -665,13 +706,16 @@ export default function App() {
relayBaseUrl,
accountLabel: client.username ?? undefined,
});
if (cancelled) return;
} catch (error) {
console.warn(
'[push] relay setup failed:',
error instanceof Error ? error.message : error,
);
}
if (cancelled) return;
// The other signed-in accounts' subscriptions expire too (webmail #1054); each
// is renewed through a client of its own.
await renewOtherPushAccounts().catch(() => undefined);
};
void doSetup();
@@ -689,7 +733,7 @@ export default function App() {
unsubscribe();
unsubscribeUp();
};
}, [client, isAuthenticated, emailNotificationsEnabled, activeAccountId]);
}, [client, isAuthenticated, emailNotificationsEnabled, activeAccountId, haveLiveSession]);
// Muting a folder or account changes the server-side push filter (#78),
// which the setup above would only send at the next launch. Send it now,
@@ -697,8 +741,10 @@ export default function App() {
// content, since hydration hands back an equal but new object; the value
// seen first after hydration is the baseline, not a change.
const notificationMutes = useSettingsStore((s) => s.notificationMutes);
// The Inbox-only switch changes the same filter (webmail #983).
const pushInboxOnly = useSettingsStore((s) => s.pushNotifyInboxOnly);
const settingsHydrated = useSettingsStore((s) => s.hydrated);
const mutesKey = JSON.stringify(notificationMutes);
const mutesKey = JSON.stringify([notificationMutes, pushInboxOnly]);
const lastMutesKey = React.useRef<string | null>(null);
React.useEffect(() => {
if (!settingsHydrated) return;
@@ -781,6 +827,7 @@ export default function App() {
void email.fetchMailboxes();
if (email.currentMailboxId) void email.refreshEmails();
void useOutboxStore.getState().flush();
void pruneEmailSubmissions();
};
// Foreground liveness for the per-account connection dot: the open
@@ -801,6 +848,11 @@ export default function App() {
});
void start();
// Stalwart 1.0 stops accepting sends once an account holds
// `maxSubmissions` submission records, so remove the ones that can no
// longer be cancelled (throttled per account; a resume or a send runs it
// again).
void pruneEmailSubmissions();
const subscription = AppState.addEventListener('change', (state) => {
const nowActive = state === 'active';
+4 -2
View File
@@ -98,9 +98,11 @@ the audit's "Fix pass, 2026-09-24" section.
| 07 | Filters (Sieve), vacation responder, Files | [docs/parity/07-filters-vacation-files.md](docs/parity/07-filters-vacation-files.md) | 32 | 29 | 3 | 3 | 8 | 21 |
| 08 | Settings, sync, push, i18n, themes, updates, misc UI | [docs/parity/08-settings-push-i18n-ui.md](docs/parity/08-settings-push-i18n-ui.md) | 46 | 38 | 8 | 0 | 14 | 32 |
| 09 | JMAP client core, live sync, offline, security, S/MIME | [docs/parity/09-jmap-core-sync-security.md](docs/parity/09-jmap-core-sync-security.md) | 56 | 54 | 2 | 7 | 23 | 26 |
| | **Total** | | **415** | **380** | **35** | **32** | **138** | **244** |
| 10 | Webmail 1.10.0 to 1.18.0 and Unreleased (all areas) | [docs/parity/10-webmail-1.10-to-1.18.md](docs/parity/10-webmail-1.10-to-1.18.md) | 112 | 52 | 60 | 10 | 40 | 62 |
| | **Total** | | **527** | **432** | **95** | **42** | **178** | **306** |
Counts are of the `- [ ]` and `- [x]` items per file as of 2026-09-24. Done and
Counts are of the `- [ ]` and `- [x]` items per file as of 2026-09-24 (row 10:
2026-10-29). Done and
Open split them by tick; the P columns count the priority tags on those items
(one item carries none).
+1 -1
View File
@@ -1 +1 @@
0.1.83
0.1.87
+10
View File
@@ -9,6 +9,9 @@
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" tools:node="remove"/>
<!-- expo-camera declares the microphone for video; QR scanning never records audio. -->
<uses-permission android:name="android.permission.RECORD_AUDIO" tools:node="remove"/>
<!-- expo-screen-capture registers its screenshot listener on Android 14+ as soon
as it loads; without this permission the app crashes at launch. -->
<uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<!-- Device sync (#34): contacts and calendars in Android's providers, driven by the sync framework. -->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
@@ -47,6 +50,13 @@
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="bulwarkmail"/>
</intent-filter>
<!-- Browser sign-in callback of the client the app registers at Stalwart 1.0 (org.bulwarkmail.mobile:/auth/callback). -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="org.bulwarkmail.mobile"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.intent.action.SENDTO"/>
Binary file not shown.
Binary file not shown.
+107
View File
@@ -0,0 +1,107 @@
The reading fonts in this folder (Settings > Accessibility > Font) are
licensed under the SIL Open Font License, Version 1.1, copied below.
AtkinsonHyperlegibleNext*.ttf:
Copyright 2020-2024 The Atkinson Hyperlegible Next Project Authors
(https://github.com/googlefonts/atkinson-hyperlegible-next)
Lexend*.ttf:
Copyright 2018 The Lexend Project Authors (https://github.com/googlefonts/lexend),
with Reserved Font Name "RevReading Lexend".
OpenDyslexic*.ttf:
Copyright (c) 2019-07-29, Abbie Gonzalez (https://abbiecod.es|support@abbiecod.es),
with Reserved Font Name OpenDyslexic.
Copyright (c) 12/2012 - 2019
The files are built by scripts/build-reading-fonts.py from the woff2 files
the webmail bundles (from Fontsource, https://fontsource.org): the variable
Atkinson Hyperlegible Next and Lexend fonts as static regular and bold
instances, OpenDyslexic unchanged apart from the woff2 decompression.
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file not shown.
@@ -77,6 +77,10 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext)
val markReadLabel = options.takeIf { it.hasKey("markReadLabel") }?.getString("markReadLabel") ?: "Mark as read"
val deleteLabel = options.takeIf { it.hasKey("deleteLabel") }?.getString("deleteLabel") ?: "Delete"
val replyLabel = options.takeIf { it.hasKey("replyLabel") }?.getString("replyLabel") ?: "Reply"
// Delete moves the mail to Trash where there is one: no unlock needed
// for that (#85). Without a Trash it destroys the mail, so it asks.
// Unlock stays the default for a caller that doesn't say.
val deleteNeedsUnlock = !options.hasKey("deleteNeedsUnlock") || options.getBoolean("deleteNeedsUnlock")
// Bitmap fetch + draw off the bridge thread so the caller doesn't
// block waiting for the favicon request.
@@ -86,7 +90,7 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext)
postNotification(
notificationId, title, body, preview, largeIcon, bgColorHex,
emailId, threadId, subject, accountId, jmapAccountId, groupKey,
groupTitle, markReadLabel, deleteLabel, replyLabel,
groupTitle, markReadLabel, deleteLabel, replyLabel, deleteNeedsUnlock,
)
if (groupKey != null) postGroupSummary(groupKey, groupTitle, bgColorHex, accountId)
promise.resolve(null)
@@ -122,6 +126,7 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext)
markReadLabel: String,
deleteLabel: String,
replyLabel: String,
deleteNeedsUnlock: Boolean,
) {
val ctx = reactApplicationContext
val intent = Intent(ctx, MainActivity::class.java).apply {
@@ -148,8 +153,10 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext)
}
}
val builder = NotificationCompat.Builder(ctx, BulwarkMessagingService.CHANNEL_ID)
val builder = NotificationCompat.Builder(ctx, BulwarkMessagingService.channelId(ctx))
.setSmallIcon(R.drawable.ic_notification)
// Android 7 has no channels: the notification names its sound.
.setSound(BulwarkMessagingService.soundUri(ctx))
.setLargeIcon(largeIcon)
.setContentTitle(title)
// body is the subject with the "(No subject)" fallback; an empty
@@ -209,8 +216,8 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext)
)
builder.addAction(
NotificationCompat.Action.Builder(0, deleteLabel, deletePending)
// Android 12+: unlock first, so a locked phone can't delete mail.
.setAuthenticationRequired(true)
// Android 12+: unlock first, so a locked phone can't destroy mail.
.setAuthenticationRequired(deleteNeedsUnlock)
.build()
)
@@ -279,7 +286,7 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext)
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
val summary = NotificationCompat.Builder(ctx, BulwarkMessagingService.CHANNEL_ID)
val summary = NotificationCompat.Builder(ctx, BulwarkMessagingService.channelId(ctx))
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(groupTitle)
.setContentText(if (count == 1) "1 new message" else "$count new messages")
@@ -6,6 +6,8 @@ import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.media.AudioAttributes
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.PowerManager
@@ -49,7 +51,10 @@ class BulwarkMessagingService : FirebaseMessagingService() {
}
companion object {
const val CHANNEL_ID = "bulwark_mail"
// Plays the Involved sound; see channelId().
const val CHANNEL_ID = "bulwark_mail_involved"
// The first mail channel, with the system's default sound.
const val LEGACY_CHANNEL_ID = "bulwark_mail"
private const val TAG = "BulwarkPush"
private const val START_WAKE_LOCK_MS = 10_000L
@@ -103,10 +108,39 @@ class BulwarkMessagingService : FirebaseMessagingService() {
startWakeLock = null
}
/**
* The channel new-mail notifications go to. A channel's sound can't
* change after it is created, so the Involved sound (the webmail's
* default, lib/notification-sound.ts) needs a channel of its own; the
* old one stays as the user left it. Someone who turned mail
* notifications off in the old channel keeps them off: their
* notifications keep going there.
*/
fun channelId(context: Context): String {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return CHANNEL_ID
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val legacy = manager.getNotificationChannel(LEGACY_CHANNEL_ID)
return if (legacy != null && legacy.importance == NotificationManager.IMPORTANCE_NONE) {
LEGACY_CHANNEL_ID
} else {
CHANNEL_ID
}
}
/** The Involved sound (res/raw, kept from the shrinker by res/raw/keep.xml). */
fun soundUri(context: Context): Uri =
Uri.parse("android.resource://${context.packageName}/raw/involved_notification")
fun ensureChannel(context: Context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (channelId(context) != CHANNEL_ID) return
if (manager.getNotificationChannel(CHANNEL_ID) != null) return
val sound = soundUri(context)
val attributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build()
val channel = NotificationChannel(
CHANNEL_ID,
"Mail notifications",
@@ -114,6 +148,7 @@ class BulwarkMessagingService : FirebaseMessagingService() {
).apply {
description = "Incoming email alerts"
enableVibration(true)
setSound(sound, attributes)
}
manager.createNotificationChannel(channel)
}
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- The calendar reminder channel (src/lib/calendar-notifications.ts) and the
mail channel (BulwarkMessagingService.kt) name their sound by resource
name, which the release resource shrinker can't see. -->
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@raw/involved_notification" />
+57 -5
View File
@@ -22,14 +22,23 @@ COMMIT = COMMIT.slice(0, 7);
// fall back to 1 (never uploaded).
const IOS_BUILD_NUMBER = process.env.IOS_BUILD_NUMBER || '1';
// Apple team: prefixes the keychain groups the app shares with its
// notification service extension (plugins/ios-push). Not a secret.
const APPLE_TEAM_ID = process.env.APPLE_TEAM_ID || '8ZANF9LQ39';
// Which APNs host the device tokens belong to: TestFlight and App Store builds
// use production, simulators and development builds the sandbox.
const APS_ENVIRONMENT = process.env.APS_ENVIRONMENT === 'development' ? 'development' : 'production';
module.exports = {
expo: {
name: 'Bulwark Mobile',
slug: 'bulwark-mobile',
// mailto: lets Android/iOS offer the app for mail links in other apps;
// bulwarkmail: is the webmail's sign-in link (bulwarkmail://pair?...).
// org.bulwarkmail.mobile: is where Stalwart 1.0 sends the browser sign-in
// back: it registers public clients only with a scheme that has a dot.
// Android's manifest is maintained by hand and lists the same schemes.
scheme: ['bulwarkmobile', 'bulwarkmail', 'mailto'],
scheme: ['bulwarkmobile', 'bulwarkmail', 'mailto', 'org.bulwarkmail.mobile'],
version: VERSION,
orientation: 'default',
icon: './assets/icon.png',
@@ -49,10 +58,10 @@ module.exports = {
bundleIdentifier: 'org.bulwarkmail.mobile',
buildNumber: IOS_BUILD_NUMBER,
config: {
// The app only speaks HTTPS/TLS and uses platform crypto, which is
// exempt. Declaring it here skips the manual export-compliance
// questionnaire that otherwise blocks every TestFlight build.
usesNonExemptEncryption: false,
// PGP and S/MIME (#58) add end-to-end encryption beyond HTTPS/TLS,
// so the app declares non-exempt encryption; App Store Connect asks
// for the export compliance documentation on upload.
usesNonExemptEncryption: true,
},
},
android: {
@@ -74,6 +83,10 @@ module.exports = {
},
plugins: [
'expo-secure-store',
// WebCrypto for PGP and S/MIME (#58). On iOS it raises the deployment
// target (app and pods) to 16.4, the minimum Nitro modules build for;
// Android has nothing to set.
'react-native-quick-crypto',
'@react-native-community/datetimepicker',
'expo-localization',
[
@@ -96,12 +109,51 @@ module.exports = {
microphonePermission: false,
},
],
['./plugins/ios-push', { apsEnvironment: APS_ENVIRONMENT, teamId: APPLE_TEAM_ID }],
// TLS client certificates (#3): the iOS side of src/lib/client-cert.ts.
'./plugins/ios-client-cert',
// The iOS inbox widget (WidgetKit); src/widgets/ios.ts feeds it.
['./plugins/ios-widget', { teamId: APPLE_TEAM_ID }],
// The reading fonts (Settings > Accessibility > Font) for the app's
// text on iOS, found by the family names in the files
// (src/theme/reading-fonts.ts). iOS only: Android reads the same files
// from its hand-maintained assets/fonts, where the plugin would copy
// them onto themselves.
[
'expo-font',
{
ios: {
fonts: [
'AtkinsonHyperlegibleNext.ttf',
'AtkinsonHyperlegibleNext_bold.ttf',
'Lexend.ttf',
'Lexend_bold.ttf',
'OpenDyslexic.ttf',
'OpenDyslexic_bold.ttf',
].map((file) => `./android/app/src/main/assets/fonts/${file}`),
},
},
],
// Bundles the Involved sound calendar reminders (src/lib/calendar-
// notifications.ts) and new mail (plugins/ios-push) play on iOS.
// Android has it in res/raw.
[
'expo-notifications',
{
mode: APS_ENVIRONMENT,
sounds: ['./assets/sounds/involved_notification.wav'],
},
],
],
extra: {
commit: COMMIT,
// Settings → About → Source code. A modified build sets SOURCE_CODE_URL
// to where its own source is (AGPL §13); the default is upstream.
sourceCodeUrl: process.env.SOURCE_CODE_URL || 'https://github.com/bulwarkmail/native',
// src/lib/ios-push-shared.ts
iosSharedKeychainGroup: `${APPLE_TEAM_ID}.org.bulwarkmail.shared`,
iosAppKeychainGroup: `${APPLE_TEAM_ID}.org.bulwarkmail.mobile`,
apnsEnvironment: APS_ENVIRONMENT === 'development' ? 'sandbox' : 'production',
},
},
};
Binary file not shown.
+5 -3
View File
@@ -228,9 +228,11 @@ The `play-bundle` job builds `bundlePlayRelease` for arm64-v8a, armeabi-v7a and
x86_64 (Play splits the bundle per ABI), checks the signer, the package name and
the permissions, and keeps the AAB and its R8 mapping as a workflow artifact.
With the `PLAY_SERVICE_ACCOUNT_JSON` secret set (vault: "Google Play – service
account bulwark-google-play-api") it also uploads the bundle to the internal
testing track as a draft. Play only accepts API uploads once a first bundle has
been uploaded by hand in the Play Console.
account bulwark-google-play-api") it also uploads the bundle and rolls it out
on the internal and closed (beta) testing tracks, so testers get every release
without a step in the Play Console; closed testing may wait for Google's
review. Play only accepts API uploads once a first bundle has been uploaded by
hand in the Play Console.
The `github` flavor stays on the old Firebase project until the app registers
its new FCM token with the relay when the token changes; moving it before that
+187
View File
@@ -0,0 +1,187 @@
# Native sync with webmail 1.18.0 and Stalwart 1.0.0 (2026-10-29)
Branch `feat/stalwart-1.0`: native main (f358d605) merged in, then the 1.0.0
release check, the webmail 1.10-1.18 parity fixes and the open native issues.
It is 200 commits ahead of main (181 without merges) and 0 behind. Nothing is
pushed.
At 595df26e (the last code change; only docs follow): `tsc` clean, vitest 323 files / 4911 tests pass
(113 skipped are the opt-in live suites), `i18n:check` passes. The app has no
lint script.
## What landed
Merge of main into the branch: f1db6b48 (19 conflicted files). Main's later
work won (iOS push and its notification extension, SafeAreaModal, file
preview, restyle and Tabler icons, token sign-in, notification mutes, device
sync); the branch's per-account routing, session epochs and refusal handling
were carried onto it. Notable calls: jmap-client keeps the branch's
epoch-safe credential writes plus main's shared-keychain writes and
`adoptStoredTokens`; auth-store keeps the branch's `signIn` helper and routes
main's token sign-in through it; push keeps the branch's SetError-only
narrowing plus main's device-sync types and mutes.
### Stalwart 1.0.0 (step 1)
- Sign-in: password + TOTP on 1.0 (96ef8515: one `/api/auth` body for both
versions, RFC 7591 public client registered per server, redirect
`org.bulwarkmail.mobile:/auth/callback` because 1.0 wants a dot in the
scheme); browser sign-in with the registered client and Stalwart's data
scopes (6ffd0473, a2caa4cc); refresh-token rotation (1456e15d, 28d928f6,
cfd81737); no secret follows a redirect to another host (2c05e7bd,
06d68d73, fe4f9211, 61654d7c); account security explains a limited client
(c6eb02d4).
- Files: draft-18 `@type`/`mediaType` (174c3fc8), listing past
maxObjectsInGet #1385 (7d8e0735), shared-folder writes per account
(18b8aa09, 7c495d03).
- Mail: inline images that 1.0 lists only in the body parts (d2bb479a),
quoted inline images in replies and forwards (913b3766), part names that
are paths (4d545f8f), scheduled mail of own and group accounts with equal
ids (15171637), bulk delete with partial refusals (547d9e59).
- Calendar #89: the branch's split CalendarEvent/get (28ade775) holds on the
release; plus mail-only shares (f3e11d4c), 0.16 loads at DST changes
(7c775571), account-switch races (1f190c94, e993540a, 41e8f005, 98dc13fe).
- Push: device-sync types retry only on a type refusal (20be1105), refused
non-public relay explained and rolled back (4ec2ccac, 90e250d3), Android
extends subscriptions in the background because both servers clamp expiry
to 7 days (9804b499, ce47c73f), subscription id reuse (77327ea8), renewal
for every account (d92a2ca8), logins sharing a JMAP account id
(9ccb6228, b352e470), removed-account teardown (152f3089).
### Webmail parity 1.10-1.18 (step 2)
The inventory is docs/parity/10-webmail-1.10-to-1.18.md (ec7e8e05): 112 rows,
10 P1, 40 P2. 52 are ticked with their commits (fe33dfc5); every P1 and every
P2 that doesn't need the org policy is done. Highlights:
- Security (P1): HTML sanitizer rebuilt on a tokenizer, reader CSP
`script-src 'none'`, composer script under a nonce (f5e997ba, 07679cbf,
47a1d249, 440a2e7a, 4e3da668); forged DMARC/DKIM chips (ef9561f1,
8adef1e1); mailto unsubscribe (942f4517, 918571c3); winmail.dat freeze
(855d304b).
- Wrong-account writes (P1): composer pinned to its login (9daa3441,
388f46b5, 4e57b191), undo/Send now per login (e81f0071), calendar and
contacts switch guards (see above, 6b9552fc).
- Sieve interop (P1): webmail metadata v2, address/domain rules, elsif
chains (2167a9d7, fc9c164d), plus forwarding, audience and periods
(df1a5ab4).
- P2 features: scheduled message view #84 (14d009de), group identity send
(31d8e4a4, 893f40a2), reschedule cancel-first #1308 (22f2ac5b,
fdf8eece), unconfirmed send #1019 (3eac700a), Files trash (7c914183 and
be04fcb1..946304ab), attach from and save to Files (8e07b7d8, 39e82f99),
templates in account metadata and team templates (fac318dc), shared tags
(b7b9c7e0), verification-code chip (b1550f14), unverified sender
(cc21317b), search without Spam/Trash (a412b516, 2b5257bf), calendar
search (b50270d8) and default reminders (9b87100a), feed changes reach
imported events (8a71495e, 47f29718), directory browser (0fd14a83),
rules from a message (d546d509), contact links and vCard (32f70c10,
07fd03e2, 3c156673), placeholder warning (a8e7a3f4).
### Issues (step 3)
#80 96c7c045, #81 01983a38, #82 7a1031a9, #83 6f475c0c, #84 14d009de,
#86 65f00640, #87 e47c05c5, #89 (branch 28ade775, checked on the release).
#85 needs a decision (below). No issue was commented on or closed.
Every group got a separate review agent; review findings were fixed in a
follow-up round on each branch (two HIGHs in the composer, two pre-existing
HIGHs in the HTML handling, one in calendar feed sync).
## Verified live
Opt-in suites in src/api/__tests__/live-stalwart*.test.ts, run at the tip
against Stalwart 1.0.0 (stw-rel, :8120) and 0.16.27 (:8016), each with its
own e2e account. All pass on both: base 17, mail 14, compose 10, files 16,
push 11, contacts 9, calendar 22, auth 14 (with a TOTP account). The base
suite now imports its own mail, so it runs on any account.
Android emulator (Pixel 7 API 34, release build of the tip):
- 1.0.0: browser sign-in (consent page, registered client, new redirect
scheme) as usera; reader with the new CSP (height, links open in the
browser); reply composer loads, draft saved; push enabled through the
hosted relay, a mail sent by userb arrived as a notification; calendar
loads with series (#89); Files shows the trash.
- 1.0.0 and 0.16: password + TOTP sign-in for e2e-totp-phone (the 1.0
`invalid_client` bug is gone); send from the 1.0 TOTP account.
- Unified inbox across the logins.
Not covered on the emulator: series edits, contact photos and
reschedule/undo (covered by the live suites only), and anything iOS.
## Open
- iOS, needs a CI run: reader CSP and composer nonce assume injected scripts
ignore page CSP and the editor loads as about:blank; the notification
extension's label check (b352e470, Swift); `target=_blank` link taps;
#81 keyboard. iOS push still lapses after 7 days without opening the app
(the extension can't extend it).
- Blocked on the webmail: org policy (locked settings, session length,
external sender label and recipient confirm, org signature and
disclaimer, template library). The policy route only takes the webmail
session cookie, and the app doesn't keep the paired webmail URL. Also the
hand-off still returns the password in the `bulwarkmobile://` fragment.
- Not done: the 60 P3 rows in the parity file; P3 bugs in Sieve (forward
limit, vacation length, editor losing text); device DST in other-zone
daily expansion keeps a wrong recurrenceId for that one day; a login
switch from a deep link doesn't reset the navigation stack, so a thread
left below can act through the new login.
- New strings are English-only except where the webmail already had
translations.
- The `C:\ns` junction (to the worktree, used for the emulator build) is
still there; removing it was blocked by the permission settings.
## Bugs that also live in the webmail
Not fixed there in this run:
- Inline images of drafts and sent mail on 1.0.0 come only from
`attachments` (components/email/email-viewer.tsx:2518, email-composer.tsx
:905, :1391, :2647).
- Save to Files on 0.16 stores the whole message (save-to-files-dialog.tsx
:79-84); Blob/copy without the blob capability (lib/jmap/client.ts:9985).
- Reschedule after a lost answer: cancel (client.ts:11619) and create
(:11643) can strand or double-send.
- Default alert ids `alert1…` reused across calendars and lists; 1.0
refuses them (lib/calendar-alerts.ts:206, calendar-management-settings.tsx
:200).
- Sieve parser drops `elsif`/`else` and trailing comments, which breaks the
category-tabs section (lib/sieve/parser.ts:251); generator falls back to
`header :contains` (lib/sieve/generator.ts:126).
- Vacation turned on with a failed filter re-include only logs
(stores/vacation-store.ts:210).
- Feed sync: no END:VCALENDAR check, no mass-deletion guard
(stores/calendar-store.ts:1978); invitation trust lets a failing DKIM
signature beat dmarc=pass (lib/calendar-invitation.ts:219); a HELO SPF
pass counts as aligned (lib/email-headers.ts:64).
- Files trash: purge without a fresh read (stores/file-store.ts:1947),
restore with its folder lands at the root (:1914), no `.Trash` name check.
- Push: no device client id check (lib/web-push.ts:786), cap path destroys
first (:852), refused relay URL shown raw (notification-settings.tsx:173).
- Probably: decoded CSS put back into STYLE (lib/email-sanitization.ts:733),
untested; Lite refresh follows redirects (lib/auth/lite-tokens.ts:533).
## Questions
1. #85: the notification Delete action requires unlock (#68), which is why
Android closes the shade. Drop the requirement (Delete then works from the
lock screen, it only moves to Trash) or keep it?
2. Org policy on the phone: add a webmail route that accepts a JMAP bearer
token, and keep the paired webmail URL in the app?
3. Full account security on 1.0 needs a first-party client for the app. Add
an admin-registered `bulwark-mobile` client to the OAuth setup?
4. MIG-02 on native: after a 0.16 to 1.0 migration every token account fails
to refresh and is removed with "Your session has expired". Keep it as
"sign in again" instead? A pasted OAuth access token also drops after
about an hour (no refresh token).
5. A password change from a TOTP account ends its grant on 0.16, so the app
asks to sign in again. Fine?
6. Translate the new English-only strings into the other 26 locales now?
7. Reset the navigation stack when a deep link or notification switches the
login?
8. Small ones: the feed mass-removal guard (keeps events when more than half
of 10+ would go, no override); contact re-import marks same-UID cards as
"update existing", unselected; team templates read-only; Delete
permanently and Restore on stale trash rows refresh silently.
9. The account switcher shows `10.10.10.62` for both servers (port dropped),
so two logins of the same user look identical. Show the port?
+719
View File
@@ -0,0 +1,719 @@
# Native catch-up with webmail 1.19.1 and Stalwart 1.0.1 (2026-11)
Branch `catch-up-2026-11` from native main 7a42a15f: 327 commits (300
without merges, 27 merges of `cu-*` topic branches). Main on GitHub was
fast-forwarded to it so CI could run (decision 17), so origin/main is the
tip, 63357420. The version is still 0.1.87; no release is cut.
The run brought webmail 1.19.1 and Stalwart 1.0.1 into the app, closed most
leftovers of the earlier parity files, built the large features that had
waited for their own project (org policy, PGP and S/MIME, offline Outbox,
iOS widget and client certificates, global search, folder sharing, dark mode
recolouring, Jalali, free/busy) and translated every app string into the 28
languages besides English. CI (full vitest suite) is green at 63357420:
https://github.com/bulwarkmail/native/actions/runs/41226998363
Left: the results of the last full iOS e2e run and of the emulator recheck
(RESULT PENDING below), some policy and accessibility pieces (see Open), and
the webmail and website commits, which are merged locally but not pushed.
The inventory from the start of the run and the decisions follow the report.
The rows the run closed are ticked there and in docs/parity.
## What landed
### Wave 1: inventory fixes
- Reader and list (merge f643df81): forged senders get a warning shield
instead of a photo 0b0fc021, favicons off by default 8e10e73e, blockquote
freeze 3243714d, direction-override characters in attachment names
a930d177, plain-text reading by setting or per message 7dc45778, body
follows the text size 7b551fc3, reduce motion and paused GIFs 6d701c4b,
plain-text mail in the theme's colours 43973608, select by avatar tap #91
6715df7f, undo after list actions da1da0ec, sort button 1cbe28b1, search
folder tree e5c9433a, and the file 10 mail list rows (#1241, #1156, #1219,
#1136, #1138, #1235, #1370, via badge, cold start, folder links).
- Composer (0e69c86c to 73a6adf2): quick reply runs the composer's checks
db8eb731, signature swap 60d0e98f, text part 0647cbf1, missing subject and
Discard 1cc4ccca, schedule presets #1390 8fe5798b, reply caret 8bb7b607,
recipient splitter ae2eb46b, offline message a43b55ec, reports left out of
forwards 482e9a20, quoted history folded 9fd80993, template manager
2c1f266f, alt text prompt 303dea01, confirm before send 0e69c86c.
- Calendar, contacts, files and filters (merge d9a759b7): new calendar keeps
its name and colour a2344ea0, cancelled meeting removal a19afe9b, editors
ask before discarding 10e9f81d, DST recurrence id 6e697baa, forward limit
and vacation length dcbfc907, Sieve editor text 6c3046c8, custom colours
#90 d3f6d6ae, save without invitations 9b5692a8, shared task moves #1204
b6995bb6, contact filters 18c6b03f, folder duplicate a0f586f9.
- Platform (merge 470f12d2): cancel a hung sign-in (U-133) 55d008a2,
accessibility page with presets and text sizes up to 200% c9b3bdfe, 4.5:1
muted text 11522995, field names ff5a8c91, announcements on iOS d812caf0,
consentRequired 1bef4719, IDN sign-in f5875f87, quota soft limit #1242
af86339b, push fallback notification #1041 f6e6f8f5, Inbox-only push
1821fda7, Thai and Estonian e8bc54eb, formal German 25f9b810.
### Wave 2
- Booking, parcel and order cards with calendar add and update, and Apple
Wallet passes (merge 30e06315, mounted in 90e4e230).
- Search snippets 1342e2a0 and size filter aac8a403, live checks 12b798c0.
- Recover deleted mail #1319 113e96bd and delegates 8a5bb728 (merge
3048525d).
- Follow-ups (merges cec68f0d, a4e5f81f, 7139390a): directory and Sent
recipient suggestions a7a37573, undo after deleting rules, tags and
trusted senders 8ac19971, IDN sending 4932764a, quick reply switch
bb45198f, trash leads to recovery 7467764e, address book sharing 96b163a1,
starred folders and recent files cad9296a, files next and previous
e3fc67e7, calendar, contact and files links fe455370 ecefd1cb d684b00e,
light event colours 01578ac2, week all-day area #1122 90ce7c1b.
### Emulator walkthrough fixes
A release APK at 7139390a was walked through on a phone and a tablet
emulator. Fixes:
- Calendar and sharing (merge 47b922ca): typed participant added once
9ec58be4, all-day events stay all-day on 0.16 75513969, hex colour input
826d5ad2, slot marker cleared 11fa0365, share sheet looks up a typed
address 72b41eab, no principal ids shown as addresses 236e9117, widget tap
opens the app without a chooser fe678174.
- Sheets and settings (merge 73823292): sheet buttons and fields above the
taskbar and keyboard 426f7403 98024346, localized folder names in the rule
folder picker 56c8b98b, scheduled out of office shown as scheduled
9781b304, screen reader names and selected states e9816809 e7fd665b
d6b9b342.
- Mail (merge f606866d): undo-send countdown d1c44f52, star undo ecf814b6,
tag undo 9763036c, sender on search hits from Sent ff878fc6, Android reply
keyboard 461e4da0, read state kept 9bc7b4a5, mail deleted elsewhere
f2513de0, reader header scales with the text size ce21f273, tablet pane
width e69bfc50.
- Direct fixes: sheets above the taskbar 9b0bba7c, TalkBack busy state
350b0e8e, moved count 58c80543, shared address book names 423e2780,
delegate server id b803da5d, unified views across ports 268b1105, id token
kept on refresh ce950430, unified inbox row label 172d0f63, Saturday week
start kept after a restart 10fb7ab7.
### Decided behaviour (merge 14ec671c)
- Two accounts on one host kept apart by port 688e8bef, also next to a kept
host-only account 7b249ab9.
- MIG-02: an account whose sign-in stops working stays and asks to sign in
again d0060549; a password change on a 0.16 TOTP account says so 149e5bce.
- A notification or link for another account starts the stack over on that
account's inbox f9a36b28.
- #85: the notification Delete skips the unlock when the mail goes to Trash
2fe83669, Android side 9669e07e.
- Sign-out ends the identity provider session 5a301d62.
- No crash when switching to an account left in Trash db717dcd.
### PR #66 and ios-perf
- PR #66 (lucletoffe): unified views for a single account, merged as
5ecf6f32 (45b4feeb).
- ios-perf: modules and screens load on first use, and the cached inbox
shows before the session arrives; merged as 36cc49b7 (0c8b8179, d13769a9,
3424bcf2). Fast start kept (decision 19).
### Org policy and hand-off
- The app redeems a one-time code from the webmail instead of reading the
sign-in from the link ab2e5a60.
- The app keeps the webmail it signed in through and applies its policy
9d977773: locked and hidden settings f27e2ceb, hidden pages 1d654bbe,
colour mode b7b3dd4c, themes a69bc071, external content 83981e8b,
settings export df1e2117, tasks 8ba2f27a, archiving off efb92593
13dcbdc0, delegates and recovery bc47c6a1, presets respect locks
c66e6209, no third-party traffic and custom From d254d7b5, and the
operator's help links in About.
- Org signature and disclaimer on new mail, templates hidden when off
fe3f860e.
- BIMI logos from the webmail 83ddd137, shown in the list, the unified inbox
and the reader 6b7e752c.
- @ mentions can be turned off, with a choice of where the contact goes
ab226b03.
### Webmail and website changes
Merged into webmail main, not pushed (decision 3):
- fb3ba0275 feat: hand the mobile app a one-time code instead of the
sign-in in its link
- f74ff49f5 feat: give the mobile app its organisation policy for its
access token
- 3bdbdb136 docs: move the mobile hand-off contract to the website
Website repo (repos/website), local only: 7292593 documents the hand-off
code and the policy token in docs/development/mobile-handoff.md.
### Calendar extras (merge 66b4305e)
- Free/busy of invited participants in the event editor e6a89ad0.
- Invitations, changes and cancellations from CalendarEventNotification
d663a5e2.
- Jalali months, days and dates for Persian f1297b6b.
- Involved sound for reminders b4f10829.
### Reading and accessibility (merge 6e5980e4)
- Dark mode recolours HTML mail instead of inverting it 23b2bf97.
- Reading fonts (Atkinson Hyperlegible Next, Lexend, OpenDyslexic) for the
app and plain-text mail 4724c28f, on iOS too cfca6b9c.
- Colour-blind-safe palette for tags c6501ec8 and calendars 21dd5a24.
- Plain language in English and German 97b71e22.
### Native iOS and Android (merge 62a6c22f)
- Involved sound for new mail 6a27c596.
- iOS push renews itself when the app isn't opened for a week c704c7e6.
- TLS client certificates on iOS #3 ba150060, described the iOS way
48a0590a.
- iOS inbox widget fbe54576.
### Compose extras (merge 609d36b0)
- Priority, delivery notifications (DSN) and REQUIRETLS 649d3adc, kept when
a scheduled send moves 409ada94.
- @ suggestions in the message body 8389db8f.
- Full-screen recipient picker in phone landscape (U-069) e1ad5c09.
- Outbox for mail sent offline, sent once back online 0ec809eb.
### Tags, search and sharing (merge 0865c1d6)
- Nested tags and a choice of which tags the sidebar shows 585c70a5.
- Share a mail folder from its menu 417f1843.
- Global search over mail, contacts, events and files of every account
f15b7bb3.
### Dependencies (decisions 10, 15 and 20)
- postal-mime 4.2 against crafted .eml headers 368c90ce.
- React Navigation 7.5, which drops its vulnerable URL and id helpers
38257f81.
- zustand, jszip and vitest patch releases b2cd83b1; SDK 54 patch line and
lockfile f9a3b761.
- Screenshot and recent-apps protection with expo-screen-capture 0ae33f3a.
- openpgp and pkijs 051f6bf2; react-native-quick-crypto provides WebCrypto
instead of webcrypto-liner 4c818d64.
### PGP and S/MIME #58 (merge e5027c45)
- Read PGP and S/MIME mail and manage keys under Settings, Encryption
8aa1d8e0.
- Sign and encrypt from the composer options b0997c0a.
- Live test between two accounts be98acdc; signed mail lists its
attachments once 7a06ab03.
- Export compliance declared 5ead7a81 (usesNonExemptEncryption true).
### Translations (merge 3c0d7ad5)
German by hand 8345be43, the other 27 locales in six commits (cef88b93 to
a5b40d5a). A test catches the informal German "passe an" 2e90f431. The key
offer and already-sent messages were translated last 63357420.
### End-to-end flows (merge d9d7a466)
11 Maestro flows now follow the app's intended changes: open and select by
row, swipe target, compose options, cancel send, default reminders,
notification prompt, files delete, settings search, sign out above the tab
bar, camera pre-prompt, Safari's Next key. The consent page step came
earlier in 3769b24b.
### Fixes after the merges
- iOS pod install: the widget and the notification extension get their own
copy of the shared keychain code 05bc0f56 (prebuild is idempotent again).
- Android 14 and later crashed at launch, because expo-screen-capture needs
DETECT_SCREEN_CAPTURE and the manifest removed it d2d44e0d.
- CI: test updates 77914b0a 22893d32 e4295dab, account id check threw for
saved accounts without a server b43472dd, share sheet loading state hidden
from screen readers 7e60d324, duplicate import 9192311d, PKCS#1 v1.5 on
newer Node 43bf8da7.
- Review fixes (merge b188c2db): PGP key planting 93c63070, no-Drafts send
looked sent e2d4915e, notice after three unconfirmed attempts 1c33f8da,
old favicon default on upgrade b1a0eaf8, Reply-To with a comma 1aba571a,
Move to Drafts after send ac875ffe, retry during a flush 75213f6a, retry
during a cached start 2c5d9518, iOS renewal ignored notUpdated 1194bf28.
## Testing
- CI (ci.yml, full vitest suite): green at 63357420. The failures on the way
came from tests that read source and from Node differences, plus two real
bugs (b43472dd, 7e60d324); all are fixed and listed above.
- Live suites at 7139390a: all 9 files pass on 1.0.1, and on 0.16.27 except
one compose case on a dirty account, which passes on a clean one. Live
cases added later and run on both servers: end session, all-day on 0.16,
delegation with an unknown principal and share lookup, free/busy, event
notifications, send options and moved scheduled send, send queue (also
without Drafts), global search, mailbox share, PGP and S/MIME both ways.
- iOS e2e (e2e-ios.yml):
- 41206960084 at 36cc49b7: build ok, signin-tablet pass, 6 legs failed,
all from outdated flows or flaky; flows fixed in d9d7a466.
- 41215412383 at 22893d32: pod install failed ("no parent for object
SharedKeychain.swift"); fixed in 05bc0f56.
- 41216860380 at 05bc0f56: build and signin-tablet pass, the first compile
of the new Swift (push renewal, client certificates, widget).
- 41220976434 at 43bf8da7, all legs, after a reset and seed of stw-v016:
RESULT PENDING.
- Android release builds (x86_64, debug-signed) at 43bf8da7 and d2d44e0d:
success. The emulator recheck found the Android 14 launch crash
(d2d44e0d). Emulator recheck results: RESULT PENDING.
- quick-crypto on a real Hermes runtime, through the in-app self-test on the
emulator: RESULT PENDING.
- Emulator walkthrough of 7139390a on a phone and a tablet: its bugs are
under "Emulator walkthrough fixes".
- Code review (code-review skill, high) of 7a42a15f..43bf8da7: 10 findings,
9 fixed (review fixes above). Not fixed: the org signature details are
always empty (needs settings sync).
## Webmail bugs
Carried from the last report, still open in the webmail:
- Inline images of drafts and sent mail on 1.0 only from `attachments`
(components/email/email-viewer.tsx:2508; email-composer.tsx:953, :969,
:1445, :2669, :2703).
- Save to Files on 0.16 stores the whole message
(save-to-files-dialog.tsx:78-88); Blob/copy without the blob capability
(lib/jmap/client.ts:10047).
- Reschedule after a lost answer can strand or double-send
(client.ts:11686, :11706, :11721).
- Alerts the settings rows don't show keep their old ids
(lib/calendar-alerts.ts:206).
- Sieve parser drops `elsif`/`else` and trailing comments
(lib/sieve/parser.ts:251-263); the generator falls back to
`header :contains` (lib/sieve/generator.ts:105, :126).
- Vacation with a failed filter re-include only logs
(stores/vacation-store.ts:206-210).
- Feed sync without END:VCALENDAR check or mass-deletion guard
(stores/calendar-store.ts:1978).
- A HELO SPF pass counts as aligned (lib/email-headers.ts:64).
- Files trash: purge without a fresh read (stores/file-store.ts:1947),
restore with its folder lands at the root (:1914), no `.Trash` name check
(lib/file-name-rules.ts:78).
- Push: no device client id check (lib/web-push.ts:788), the cap path
destroys first (:853), a refused relay URL is shown raw
(components/settings/notification-settings.tsx:180).
- Probably: decoded CSS put back into STYLE
(lib/email-sanitization.ts:613-740); Lite refresh follows redirects
(lib/auth/lite-tokens.ts:536, also :376, :399, :467, :504).
New in this run:
- `consentRequired` reads as a wrong password
(lib/auth/stalwart-login-request.ts).
- isValidEmail rejects Unicode domains.
- A bare /calendar/<date> link switches to the month view.
- Recent files are shared across accounts.
- Participant input: groups have an empty email, and onBlur adds the typed
address (harmless on the web; it caused the app's double add).
- 49 keys were dropped in the settings reorg.
- A stale comment says Stalwart takes limit 0 as "no limit".
- lib/account-utils.ts generateAccountId drops the port, so two servers on
one host collide (fixed in the app).
- hydrateExpandedOccurrences (lib/jmap/client.ts) and
lib/recurrence-instances.ts skip one-off synthetic ids without a
recurrenceId; on 0.16 one-off all-day events probably show as timed.
- components/settings/delegates/delegates-settings.tsx:182 shows the
principal id as the "Unknown person" detail line.
- components/settings/share-collection-dialog.tsx has no lookup for a typed
exact address.
- context_menu.items_selected has no singular.
- The filter-rule-modal.tsx folder picker shows server folder names, not
the localized role names.
- thread-list-item.tsx listMailboxRole picks sender or recipient by the
selected folder's role, so a search from Sent shows the recipient on Inbox
hits (also the email-list.tsx column header).
- The components/search/search-chips.tsx size chip reads "Larger than (KB):
1 KB".
- The mail-app.tsx undo-send toast doesn't count down (maybe intended).
- Jalali: the week view keeps Gregorian day numbers
(calendar-week-view.tsx:498), and month navigation uses Gregorian
addMonths (calendar-app.tsx:576-592).
- On 0.16 the event notification toast always says "Untitled event" (0.16
doesn't return `event`).
- free-busy.ts says Stalwart 1.0 sets mayGetAvailability false with
directory queries off; on 1.0.1 it's true.
- Reschedule and Send now (lib/jmap/client.ts ~11624-11640) drop the DSN and
REQUIRETLS envelope parameters.
- composing-settings.tsx never passes `locked` for plainTextMode,
autoSelectReplyIdentity and replyIdentityMatch; defaultReplyMode has no
locked UI.
- The externalContentEnabled gate only hides the setting; the user's stored
choice keeps applying (maybe intended).
- In the standard layout, Enter in the global search palette only saves the
recent search and closes.
- Plugins (repos/plugins): smime extractKeyUsage reads ku.digitalSignature,
but pkijs gives a BIT STRING, so every certificate with keyUsage ends up
canSign and canEncrypt false; smime importPkcs12 takes the first
certificate bag as the leaf; bulwark-gpg signerEmailFor compares primary
key ids (subkey signatures show no signer), renderInnerMime drops the first
paragraph of non-MIME inline PGP, and detect() searches the raw body, so it
misses armor in QP or base64 parts.
- Catalog: mn uses "Харилцах утас" for Contacts and "Тэмдэгтүүд" for
Identities and icons; ru has "Отменить изменения" where discard is meant.
Stalwart, not the webmail: on 0.16, ContactCard/query `{text}` doesn't find
a card whose name has only `full` set (1.0 does). With directory queries
off, 1.0 can't name a sharee or share by exact address over JMAP (DAV can).
The app explains this, and it goes to Stalwart (decision 18).
## Open
- Org signature details (title, department, phone, mobile) are always
empty: the app has no form for them and settings sync is blocked.
- Group-identity policy scopes (webmail policyForSender) aren't ported; the
app uses the active account's policy.
- The composer still offers the custom From sheet when customFromEnabled is
off (the webmail hides it).
- No-third-party mode counts as off until /api/config is read (the webmail
treats it as on until then).
- The Android home-screen widget still shows Archive when the organisation
turns archiving off (src/widgets/layouts/mail.tsx, src/widgets/actions.ts).
- Mailbox sharing: the admin's sharingRights don't hide the write presets
yet.
- Policy pieces not built: idle sign-out and session length, the external
sender chip, the external recipient confirm.
- PGP: Argon2-protected keys get a clear error (no WebAssembly on Hermes).
S/MIME RC2, DES, Camellia and AES-CCM content and legacy RC2 .p12 files
get a banner or an error. No WKD (the plugin has none). Drafts stay
unencrypted on the server, as in the webmail.
- iOS: the Swift for client certificates, push renewal and the widget
compiled in CI but never ran on a device; reading fonts on iOS are
untested. The widget needs the App ID org.bulwarkmail.mobile.Widget and the
secret IOS_WIDGET_PROVISIONING_PROFILE_BASE64 in release-ios.yml; until
then signed releases leave it out. The iOS minimum is now 16.4
(quick-crypto and Nitro).
- The APK is about 10 MB bigger per ABI (libQuickCrypto).
- Nested tags: renaming or moving a tag with its messages, and reordering,
aren't ported.
- Global search: other logins' file listing is capped at about 500 items;
the Pro search tab and saved searches aren't ported.
- Jalali: Android's native date pickers and the widgets stay Gregorian.
- Accessibility rows done only in part: no text spacing, more contrast or
underlined links; no names next to colours; no explanations for technical
terms; no default compose font; the text size still reaches only some
screens.
- Composer extras not built: numeric date placeholders, font size, pasted
lists. BIMI shows no verified-mark check.
- An expired session still drops an open composer without saving it.
- Still open from earlier: run a rule on a folder, one ring per message
(#1141), settings sync on 0.16, background reminder refresh without device
sync, the real APNs path, QR scan with the camera, SSO pairing, discovery
success.
## Test rig changes (VM 106)
- servers.conf: stw-rel on stalwartlabs/stalwart:v1.0.1 (backup
servers.conf.bak-2026-10-31), reset and seeded with rel_setup.sh: 93 ok,
0 failed.
- seed.py (backup seed.py.bak-2026-10-31): FileNode creates retry in the
draft-18 shape (`@type`, `mediaType`); the TOTP login registers a public
client with the data scopes and sends `query` and consent.
- register-mobile-client.py registers the first-party bulwark-mobile OAuth
client on a 1.0 server (run on stw-rel).
- rotate-user.py rotates one test user's password on all servers and in the
secrets. userc was rotated on 2026-10-31 after an emulator agent exposed
it once (it was typed into the visible username field), and the vault item
is updated. Secrets backup: secrets-bak-2026-10-31.tgz (without the
root-owned pushtest.json).
- stw-v016 (0.16.27) was reset and seeded twice, before each full e2e run.
The 0.16 admin password is regenerated each time and kept in the vault
item "Stalwart test – admin (0.16)".
## New questions
1. Dutch: the overlay mixes "je" (catalog) and "u" (older app strings).
Sweep it to one form, and which one?
2. English source strings the translator flagged. Fix them in one pass?
Title Case strings (files.settings_*, settings.vacation.date_range.title,
settings.vacation.message.title, widgets.vacation.title "Vacation
Responder" vs "Out of office"), a UK/US spelling mix, `one` branches
without # (contacts.import.updated_count,
files.trash_unavailable_message), "{count} days" without a plural
(calendar.views.n_days, settings.offline.keep_days), fragments
(settings.offline.keep_from, use_at_most), a {service} case problem
(settings.privacy.push), a bare {count}
(settings.keywords.discover.found), identical one/other branches
(widgets.calendar.more_this_week, unit_minutes,
widgets.draft.edited_minutes), misnamed keys
(email_composer.read_receipt_off, widgets.mail.in_outbox), stale "Android
only" push texts (settings.notifications.push.err_android_only,
status_android_only), the licences_desc wording, desktop words ("Click",
"the drawer"), and " - " next to "—".
3. ios-perf: re-measure the start time on the bench rig now that it's
merged?
4. Push the webmail commits (fb3ba0275, f74ff49f5, 3bdbdb136) and the
website commit (7292593)? The app's hand-off code and policy need the
webmail ones on the server.
5. iOS widget: create the App ID org.bulwarkmail.mobile.Widget and add the
IOS_WIDGET_PROVISIONING_PROFILE_BASE64 secret?
6. Export compliance: with usesNonExemptEncryption true, App Store Connect
asks for documentation on the next upload. Who files it?
## Leftovers on disk
- Junctions the permission check wouldn't let agents remove: C:\cn
(dangling), C:\cd, C:\cp; scratch folder C:\cn-iosfix.
- Folders Windows wouldn't delete (already unregistered from git):
C:\Users\info\worktrees\react-native-cu-deps and react-native-cu-pgp
("Filename too long", each with its own partial node_modules), and the
empty locked folders react-native-cu-calcon, -cu-calendar2, -cu-followup,
-cu-platform and -cu-compx.
- Kept on purpose: C:\ns and C:\Users\info\worktrees\native-sync (decision
14), C:\Users\info\worktrees\react-native-cu-build (the build worktree
with local-only patches, removed at the end of the run) and the C:\cu
junction to it.
## Inventory (Phase 0, 2026-10-31)
Written before the fixes; rows closed since are ticked with their commits.
Sources, all checked against native main 7a42a15f and webmail main
4887ed457:
- Webmail 1.19.0, 1.19.1 and the 237 commits since 2026-10-29:
docs/parity/11-webmail-1.19.md (42 rows, 11 P2, 31 P3). 1.19.1 is the
latest webmail release; the changelog has no Unreleased section.
- Stalwart 1.0.1 (2026-10-28, the latest release): below.
- Leftovers: the unticked rows of docs/parity/01-10, the "Open" and webmail
bug lists of docs/native-sync-2026-10-29.md, the deferred items of
docs/ux-audit-2026-10.md and docs/ios-test-2026-10.md, open issues and PRs:
below.
- Dependencies: below.
### Stalwart 1.0.1
v1.0.0...v1.0.1 is one squashed commit. Two release-note lines read the wrong
way round: the "Fixed" JMAP lines describe the old bug. 1.0.0 answered a
Mailbox, AddressBook, ContactCard or Identity create with only `{id}`; 1.0.1
adds the server-set and defaulted properties the client left out (plus
myRights, mayDelete, isDefault). Calendar adds description, color and
timeZone as null and an empty shareWith when omitted; CalendarEvent adds
isDraft, useDefaultAlerts, mayInviteSelf, mayInviteOthers and hideAttendees.
Every native create caller reads only the id, re-reads, or merges the reply
over what it sent (createMailbox src/api/email.ts:284, createAddressBook
src/api/contacts.ts:352, createContact :263-275, createIdentity
src/api/identity.ts:39-41, createEvent src/api/calendar.ts:1080, device sync
src/device-sync/engine/item-sync.ts:1287-1312), except createCalendar.
Moving stw-rel to 1.0.1 surfaced two seed failures that looked like 1.0.1
changes. Neither is: both behave the same on 1.0.0, and the app sends what
both versions want.
- FileNode creates need `@type` (`File`/`Directory`/`SymLink`) and
`mediaType` on the released 1.0.x; `nodeType` and `type` are refused. The
app sends that shape whenever the account capability lists
`supportedNodeTypes` (src/api/files.ts:67-72, :420-428, every create goes
through createFileNodeIn :441-460). The seed script's fallback was written
for the pre-release stw-v1 build, which still takes the old shape.
- `/api/auth` answered 400 "JSON deserialization failed" to the seed's 0.16
body: 1.0.x requires `query`. The app's body (src/lib/totp-login.ts:296-321)
carries `query` and `consent: true`; probed on 1.0.1 it deserializes and
gets as far as the client check.
Findings:
- [x] **A new calendar lands in the store without its name and colour** — `P2` — `bug` (also on 1.0.0) — fixed in a2344ea0
- What Stalwart does: the Calendar/set create answer never echoes properties the client sent (crates/jmap/src/calendar/set.rs:1307-1367).
- What RN does: createCalendar returns `created` alone (src/api/calendar.ts:1269) and the store inserts it (src/stores/calendar-store.ts:1415-1423), so the calendar shows nameless and colourless until the next fetch.
- Fix hint: `{ ...props, ...created }`, as createEvent does. Live test in live-stalwart-calendar.
- [x] **iOS Maestro OAuth flows have no step for the consent page** — `P2` — `test` — fixed in 3769b24b
- What Stalwart does: on 1.0.1 the consent page comes after Sign in, once per client grant ("Allow"/"Deny", login.html:462-463), for every client that isn't a Stalwart-installed Application, `bulwark-mobile` included.
- What RN does: .maestro/subflows/stalwart-login-page.yaml types and taps Sign in, then inbox.yaml waits for the tab bar. The app doesn't depend on the order (oauth-native.ts never sends `prompt=consent`). The iOS e2e server (`vars.E2E_SERVER`) is the 0.16 server, so CI isn't affected today.
- Fix hint: optional `runFlow when visible "Allow"` tap at the end of the subflow.
- [x] **A `consentRequired` answer reads as a wrong password** — `P3` — `bug` — fixed in 1bef4719
- What Stalwart does: new answer `{type:'consentRequired', token}` and request `{type:'authConsent', token, query, consent}` in 1.0.1.
- What RN does: readStalwartLoginResponse maps it to invalid credentials (src/lib/totp-login.ts:344-380). Not reachable while `consent: true` is sent. The webmail has the same gap (lib/auth/stalwart-login-request.ts).
- Fix hint: answer it with `authConsent` and the same query, or a clear error.
- [x] **The encrypt-on-upload copy no longer matches the server** — `P3` — `docs` — fixed in 853cb5d0 (comment; the live compose check is not written)
- What Stalwart does: 1.0.1 stores JMAP `$draft` messages and the Sent copy unencrypted (crates/email/src/message/ingest.rs:608-611).
- What RN does: no workaround for encrypted drafts; the comment at src/api/account-security.ts:249-254 says sent mail is encrypted too.
- Fix hint: fix the comment; a live check in live-stalwart-compose that a draft and the Sent copy read back as text with encryption on.
No action: registry listings now return only the requested account (an admin
login no longer sees other accounts' keys, src/api/account-security.ts:299-307);
the iTIP PRIORITY fix (RSVP is a participationStatus patch); the
FORGED_RCVD_TRAIL fix lowers spam scores the app shows; `denyLocation` left
GET /api/auth/client (the app reads only `error`); telemetry, ACME, RocksDB,
WebUI and installer changes.
### Leftovers
#### Older parity files (01-10)
95 unticked rows: 77 still open, 3 done but unticked, 7 blocked, 8 not to
do. About 15 of the 24 open rows in 01-09 duplicate a file 10 row. The four
commits after file 10 close none.
- Done, to tick: templates sync on Stalwart 1.0 (04, fac318dc; 0.16 still
needs settings sync), calendar strings (05, 84fef142), iOS push (08,
f62849d1).
- Not to do, reasons kept from the rows: identity visibility refresh (04),
S/MIME send stub (04, see #58), component set on new calendars #760 (05),
push without CalendarEvent types (05), legacy flat-name migration (07),
stability banner (07), status bar theming (08), sharing before 0.16.6 (10,
below the app's minimum).
- Blocked on the webmail: hand-off password in the fragment, org policy,
session limits, external sender chip, external recipient confirm, org
signature and disclaimer (10), settings sync (08).
- Priority changes: up to P2 the DuckDuckGo favicons (leak every sender
domain and the device IP, on by default, src/lib/avatar-utils.ts:149,
src/stores/settings-store.ts:312), the blockquote freeze, direction-override
characters in attachment names, and no notification when the push lookup
fails (#1041). Down to P3 the 09 offline-send row (same item as 04 and 10).
- New: the move sheet still picks icons by folder name
(src/components/MoveSheet.tsx:20-28), the #1235 bug 96c7c045 fixed in the
sidebar. The recipient splitter row also covers pasting into the composer.
- Status at the end of the run: the four P2 re-ratings are fixed (8e10e73e,
3243714d, a930d177, f6e6f8f5), the move sheet icons too (a4d38911), and
the S/MIME send stub is replaced by real sending (b0997c0a). Of the
webmail-blocked rows, the hand-off code and the org policy, signature and
disclaimer are built; session limits, the external sender chip and the
external recipient confirm are not.
The policy route doesn't take a JMAP bearer token (app/api/admin/policy/route.ts:15,
lib/auth/instance-details.ts:23). Without a session it still answers the
global restrictions, feature gates, defaults and theme policy, and strips only
organisation data, scopes, plugins and push relays (route.ts:19-37). If the
app kept the webmail URL it gets at pairing or hand-off (src/lib/oauth.ts:292
parses it, nothing stores it), the global locks need no webmail change.
Built since: the app keeps the webmail URL, and the webmail takes the app's
token for the organisation policy (f74ff49f5, not pushed).
#### Report "Open" (2026-10-29)
- [ ] **iOS: reader CSP, composer nonce, `target=_blank` taps, #81 keyboard** — `P2` — `partial` — needs an iOS run (e2e-ios.yml in testing step 4) — partly: the iOS build and the tablet sign-in leg pass at 05bc0f56 (run 41216860380); the full run 41220976434 is RESULT PENDING
- [x] **iOS push lapses after 7 days without opening the app** — `P2` — `bug` — the extension has no expiry handling (plugins/ios-push/NotificationService.swift), no BGTask; needs a device to verify — fixed in c704c7e6 (compiled in CI, not run on a device)
- [x] **Login switch from a deep link or notification keeps the old navigation stack** — `P2` — `bug` — App.tsx:118-122, :141-145, :181-186 switch and navigate on top; question 7 — fixed in f9a36b28
- [x] **Sieve editor loses typed text (#1162)** — `P3` — `bug` — src/components/filters/SieveEditorSheet.tsx:35-41 re-seeds on every content change — fixed in 6c3046c8
- [x] **No forward limit (#1155, #1208), no vacation length warning** — `P3` — `missing` — FilterRuleModal.tsx:443-459, VacationSettings.tsx:140-142 — fixed in dcbfc907
- [x] **Other-zone daily series keeps a wrong recurrenceId on the device's DST day** — `P3` — `bug` — src/lib/recurrence-expansion.ts:526-536 builds candidates from device-local fields — fixed in 6e697baa
- [x] **New strings English-only** — `P3` — `partial` — 331 overlay keys missing in 25 locales, 86 in de; question 6 — fixed in 3c0d7ad5 (German by hand in 8345be43, the other locales in six commits after it)
- [ ] **Org policy, hand-off password** — blocked on the webmail (above) — partly: hand-off code in ab2e5a60, organisation policy in 9d977773; group-identity scopes, session limits, the external sender chip and recipient confirm, and the org signature's user details are not done
- [ ] **`C:\ns` junction and the native-sync worktree still exist** — chore — C:\ns points at C:\Users\info\worktrees\native-sync (e6e5e521, on main) — kept on purpose (decision 14)
#### Webmail bugs from the 2026-10-29 report
All 19 are fixed in the app. In the webmail, 17 are still open at current
lines, one is fixed (invitation trust: failing DKIM beat dmarc=pass,
e1b43c894, dropped) and one is fixed except alerts the settings rows don't
show keep their old ids (lib/calendar-alerts.ts:206). The carried list with
current lines is in "Webmail bugs" above.
#### UX audit deferred (docs/ux-audit-2026-10.md)
- [x] **U-133 no way out of a hung "Signing in"** — `P2` — `missing` — src/screens/LoginScreen.tsx:218 swallows Back while busy, SigningInStep has no cancel; abortable sign-in, M — fixed in 55d008a2
- [x] **U-069 recipient suggestions hidden in phone landscape** — `P2` — `missing` — needs a full-screen picker, M (feature) — fixed in e1ad5c09
- [x] **U-045 German Prev/Next read "Zurück"/"Weiter"** — `P3` — `bug` — override in locales/rn/de.json — fixed in 25f9b810
- [x] **Plain-text mail dark background fixed at #0a0a0a** — `P3` — `partial` — src/lib/email-html.ts:963 ignores the theme — fixed in 43973608
- [ ] **U-017 system alert vs Dialog, U-136 empty all-day strip, U-124 language order** — product calls, kept deferred unless you say otherwise — kept (decision 12)
- U-016 scrim token: won't fix (no visible change). U-018 font scale resets navigation: React Native limitation. U-131: done (776447dd).
#### iOS test deferred (docs/ios-test-2026-10.md)
- [ ] **Sign-in and features matrices empty; the last three release runs of e2e-ios failed** — `P2` — most failures miss seeded mail, so a reset and seed of stw-v016 comes first (testing step 4) — partly: stw-v016 reset and seeded, 11 outdated flows fixed in d9d7a466, pod install fixed in 05bc0f56; the full run is RESULT PENDING
- [x] **ios-perf branch never merged** (d13769a9, measured -18% to first inbox) — not part of this run unless you want it — fixed in 36cc49b7
- Blocked on a device or setup: real APNs path through the extension, QR scan with the camera, SSO pairing (needs an external IdP), discovery success (needs DNS records), iOS widgets (WidgetKit extension, L). The iOS widget is built since (fbe54576), compiled in CI only.
- Done: the six app bugs from the test (on main), the webmail hand-off fix 310f910a6.
#### GitHub (bulwarkmail/native)
- Fixed on main, unreleased (close after 0.1.87 ships): #80, #82, #83, #84, #86, #87, #89. #81 is fixed in code (01983a38), unchecked on iOS.
- [x] **#91 multi-select without long press** — `P3` — `missing` — EmailListScreen.tsx:190-218; tap on the avatar toggles selection — fixed in 6715df7f
- [x] **#90 custom calendar colours** — `P3` — `missing` — CalendarEditSheet.tsx:222 offers the palette only — fixed in d3f6d6ae
- [x] **#85 keep the shade open on Delete** — question 1 — fixed in 2fe83669 (the Android side in 9669e07e)
- [x] **#58 PGP / S/MIME** — `P3` — L, not in this run — fixed in e5027c45 (reading 8aa1d8e0, sending b0997c0a; no Argon2 keys, WKD or legacy S/MIME ciphers)
- [x] **#3 TLS client certificates** — Android done in 0.1.83; iOS has none (L) — fixed in ba150060 (compiled in CI, not run on a device)
- [x] **PR #66 unified views for a single account** (lucletoffe) — conflicts, no review, the author's question unanswered — fixed in 5ecf6f32 (the author's 45b4feeb)
### Dependencies
Expo SDK 54 (expo 54.0.33, React Native 0.81.5, React 19.1.0). Newest stable
is SDK 58 (React Native 0.88, React 19.3), four behind; SDK 54 still gets
patch releases.
Done in this run: every "On SDK 54 now" upgrade below (368c90ce, 38257f81,
b2cd83b1, f9a3b761), plus expo-screen-capture, openpgp, pkijs and
react-native-quick-crypto. npm audit on 2026-10-31 at the tip: 30 findings
(2 high, 27 moderate, 1 low, no critical), down from 53. The two highs are
postcss and image-size, both pulled in by Expo's build packages; the
postal-mime and React Navigation chains are gone.
npm audit: 53 findings (2 critical, 17 high, 32 moderate, 2 low). Three chains
ship in the app:
- postal-mime 3.0.0, high, reachable: quadratic CPU on RFC 2047 headers; the
app parses .eml attachments and embedded messages with it
(src/components/email/use-body-override.ts). Fixed in 4.0.8+; the webmail
moved to postal-mime 4 on 2026-10-29 (4aea0976a). The local patch
(Blob from typed arrays) has to be redone for 4.2.0.
- query-string/decode-uri-component and nanoid via React Navigation, high,
not reachable (no `linking` prop; default nanoid size). React Navigation
native 7.5.1 drops them.
The rest (both criticals included: tar, shell-quote) sit in Expo CLI, Metro,
the dev server or vitest and run only on dev machines and CI; a lockfile
refresh within current ranges clears most.
Upgrades:
- On SDK 54 now: postal-mime 4.2.0 (redo the patch; fixes the advisory, risk:
the RN Blob path, covered by src/lib/__tests__/postal-mime-rn.test.ts),
@react-navigation native 7.5.1 with native-stack and bottom-tabs 7.21.0
(drops two advisories, risk: navigation regressions, needs the emulator
walk), zustand 5.0.16, jszip 3.10.2, vitest 4.1.12, the SDK 54 patch line
(expo 54.0.38, expo-file-system 19.0.24, expo-image-picker 17.0.11,
expo-localization 17.0.9), lockfile refresh.
- With an SDK bump (its own project): SDK 58 means Gradle 9 and AGP 9 hand
edits in android/, removing newArchEnabled/edgeToEdgeEnabled, Node 22 in
six workflow lines, iOS 16.4 minimum, Expo's fetch replacing the global
fetch (SDK 56), async File.write (StorageSettings.tsx:105,
use-file-preview.ts:75), foreground notifications shown by default
(calendar-notifications.ts), react-native-webview 14 (mail body and
composer), react-native-android-widget 0.26 (the #52 patch can probably
go). SDK 57 first keeps AGP 9 as its own step.
- Skip: AsyncStorage 3, TypeScript 7, vitest 5.
## Decisions
Taken on 2026-10-31, before the fixes:
1. #85: Delete from the notification skips the unlock only when the account
has a Trash; without one the unlock stays.
2. Org policy: store the webmail URL from pairing or hand-off and apply the
global policy now.
3. Webmail changes: yes. The hand-off one-time code and a policy route that
takes the app's token get built in the webmail repo too.
4. MIG-02: keep the account and ask to sign in again; same for an expired
pasted token.
5. TOTP password change on 0.16: show a clear "sign in again" message.
6. Translations: German by hand, the other 25 locales by an agent.
7. Notification or deep link switching the account resets the navigation.
8. The four small behaviours stay as they are.
9. Port in the account switcher: always when it isn't the default.
10. Dependencies: the SDK 54 updates now (postal-mime 4.2, React Navigation
7.5.1, patch updates, SDK 54 patch line, lockfile). SDK 58 is separate.
11. The large items formerly under "Not in this run": all of them, by agents,
on this branch after the fixes.
12. U-017, U-124, U-136, U-018 stay as they are.
13. PR #66 and the ios-perf branch: take both.
14. C:\ns and the native-sync worktree stay on disk.
15. Screenshot protection: yes, with the dependency install.
16. End the identity provider session at sign-out: yes.
17. CI: push to main.
Taken during the run:
18. Stalwart 1.0 sharing with directory lookups off: explain it in the
app and report it to Stalwart; no DAV path.
19. ios-perf fast start kept. Risk: an action in the first round trip
after launch on a just-replaced server can go out with old ids.
20. Dependency install approved, then `npm ci` in the main checkout (twice:
after the dependency updates and after quick-crypto).
21. PGP and S/MIME crypto through react-native-quick-crypto;
webcrypto-liner removed (it couldn't do the job and pulled an elliptic
fork over git+ssh with a critical audit finding).
22. iOS minimum 16.4, usesNonExemptEncryption true and the bigger APK
accepted.
+1 -1
View File
@@ -258,7 +258,7 @@ RN covers the core single-folder loop well (folder tree incl. shared/group accou
### Tags / keywords
- [ ] **Tag definition features: visibility, nesting/parent, reorder, rename with migration, unknown tags** — `P3` — `partial` — partial in 3194b5e: unknown `$label:` ids listed/removable in TagSheet; deferred: visibility, nesting, reorder, rename migration (device-local tag model, see native issue #1)
- [ ] **Tag definition features: visibility, nesting/parent, reorder, rename with migration, unknown tags** — `P3` — `partial` — partial in 3194b5e: unknown `$label:` ids listed/removable in TagSheet; deferred: visibility, nesting, reorder, rename migration (device-local tag model, see native issue #1) — partly: nesting and sidebar visibility in 585c70a5; reorder and rename with migration still missing
- What WEB does: `visibility: show|unread|hide` (`stores/settings-store.ts:180-190`, `components/settings/keyword-settings.tsx:98-132`), nested ids `parent/child` (`lib/keyword-nesting.ts`, `keyword-settings.tsx:213-243`), drag reorder (atomic, changelog 1.9.0), rename keyword migrates messages (`renameKeyword`/`client.migrateKeyword` `lib/jmap/client.ts:1963`), tag picker lists unknown ids present on a message so they can be removed (`components/email/tag-picker.tsx:484-491`).
- What RN does: `{ id, label, color }` only (`src/stores/keywords-store.ts:9-13`); `TagSheet` lists defined tags only (`src/components/TagSheet.tsx:76-95`), so a `$label:work/clients` set by WEB can neither be seen nor removed on RN. Tag definitions are device-local (see native issue #1).
- Fix hint: show unknown `$label:*` ids found on the selected emails at the bottom of `TagSheet`; add `visibility` when the drawer tag section lands; nesting/reorder are lower priority.
+3 -3
View File
@@ -71,7 +71,7 @@ Legend for refs: WEB paths are relative to `the webmail repo`, RN paths to `the
- What RN does: `sendEmail` sets no `messageId`, relying on the server; RN's `src/lib/uuid.ts:6-20` already has a guarded UUID generator (Hermes fallback) so the crash class of the web fix does not apply.
- Fix hint: optional — set `messageId: [`${generateUUID()}@${domain}`]` in `sendEmail` so the id is known before the response (useful once drafts/threading exist).
- [ ] **Sending from shared/group accounts unsupported** — `P3` — `missing` (partly N/A) — deferred: RN is single-active-account; envelope/identity routing for shared accounts not implemented
- [x] **Sending from shared/group accounts unsupported** — `P3` — `missing` (partly N/A) — deferred: RN is single-active-account; envelope/identity routing for shared accounts not implemented — fixed in 31d8e4a4, 893f40a2
- What WEB does: routes the submission to the identity's owning account (#461, `email-composer.tsx:2258-2274`, `lib/jmap/client.ts:3131-3134` targetAccountId) and preselects the shared folder's identity (`lib/reply-identity.ts:114-128`).
- What RN does: `ComposeScreen.tsx:267-272` explicitly always uses the user's own Sent ("composing on behalf of a shared account isn't supported"), `getIdentities` (`src/api/identity.ts:5-12`) only queries the primary account. Replying from a shared-folder message sends as the reaching login.
- Fix hint: low priority; if implemented, query `Identity/get` for the shared `accountId`, submit with that `accountId`, and file into that account's Sent.
@@ -260,7 +260,7 @@ Legend for refs: WEB paths are relative to `the webmail repo`, RN paths to `the
### Security / crypto
- [ ] **S/MIME sign/encrypt on send is a stub** — `P3` — `missing` — deferred: no raw-MIME send path in RN; certificates are stored only
- [x] **S/MIME sign/encrypt on send is a stub** — `P3` — `missing` — deferred: no raw-MIME send path in RN; certificates are stored only — fixed in b0997c0a
- What WEB does: crypto plugins hook `onComposeSend` (`email-composer.tsx:2180-2216`) and the store has `sendRawEmail` (`stores/email-store.ts:1724`).
- What RN does: `src/components/settings/SmimeSettings.tsx` lists imported certificates ("Your Certificates", "Recipient Certificates") but `ComposeScreen.tsx` has no sign/encrypt control (`grep -i smime|sign|encrypt` → none) and `sendEmail` has no raw-MIME path.
- Fix hint: out of scope for parity right now; document as a known gap in the settings pane ("certificates are stored but not yet used for sending").
@@ -272,7 +272,7 @@ Legend for refs: WEB paths are relative to `the webmail repo`, RN paths to `the
- What RN does: `settings-store.ts:105-106` has `trustedSenders`, but `ComposeScreen.tsx` never touches it (`grep -i trusted` → none).
- Fix hint: after a successful reply push `finalTo`/`finalCc` emails into `trustedSenders` (dedupe).
- [ ] **Offline send is not queued** — `P3` — `missing` (RN-only opportunity) — deferred: needs a new outbox op kind carrying the whole Email/set + submission payload; drafts now autosave to the server instead
- [x] **Offline send is not queued** — `P3` — `missing` (RN-only opportunity) — deferred: needs a new outbox op kind carrying the whole Email/set + submission payload; drafts now autosave to the server instead — the offline message (says offline, the text stays) is in a43b55ec. One verdict for the 04, 09 and 10 rows — fixed in 0ec809eb
- What RN does: `outbox-store.ts:40-43` only knows `keywords|mailboxes|destroy` ops; `performSend` fails with a network error when offline (`ComposeScreen.tsx:975-979`) and the composed text is lost unless the user keeps the screen open. WEB has no offline outbox either, so this is not a parity item, but once drafts exist the natural mobile behaviour is "save draft locally, submit when online".
- Fix hint: add a `send` op kind carrying the Email/set create + submission payload, flushed by `outbox-store.flush`.
+6 -6
View File
@@ -83,7 +83,7 @@ RN has a solid read path (month/week/agenda, shared-calendar namespacing, per-vi
- What RN does: fixed preset list, multiple allowed, non-offset alerts are dropped from the UI but kept until save (`src/components/calendar/EventModal.tsx:299-312`, `src/lib/calendar-alerts.ts:51-70`). Note the "kept until save" comment is wrong: `remindersToAlerts` rebuilds the map from presets only, so an absolute-trigger alert is lost on the next save.
- Fix hint: add a custom value+unit row; when rebuilding alerts, carry over alerts whose trigger has no `offset`.
- [ ] **Duplicate event, export .ics, copy meeting link / title, add note** — `P3` — `missing` — duplicate (+1 day, opens editor), share as .ics (`eventToICS` port + expo-sharing) and copy meeting link done in 5fe8e40; deferred: copy title, append-a-note
- [x] **Duplicate event, export .ics, copy meeting link / title, add note** — `P3` — `missing` — duplicate (+1 day, opens editor), share as .ics (`eventToICS` port + expo-sharing) and copy meeting link done in 5fe8e40; deferred: copy title, append-a-note — fixed in baa3ff59 (copy title, add note)
- What WEB does: popover/context-menu actions duplicate (+1 day, opens editor), export via `downloadEventICS`, copy meeting link, copy title, append a timestamped note to the description (ref `components/calendar/calendar-app.tsx:1017-1125`, `lib/calendar-ics-export.ts:96, 176`).
- What RN does: `EventDetailSheet` has an `onDuplicate` prop that `CalendarScreen` never passes (`src/components/calendar/EventDetailSheet.tsx:59, 336-342`); no export/copy/note.
- Fix hint: wire `onDuplicate` (clone like WEB `handleDuplicateFromDetail`), add "Share .ics" via `expo-sharing` + a port of `eventToICS`, add "Copy link" with `Clipboard`.
@@ -169,12 +169,12 @@ RN has a solid read path (month/week/agenda, shared-calendar namespacing, per-vi
### Views / navigation / locale
- [ ] **Entire calendar UI is hard-coded English although RN has i18n** — `P2` — `missing` — partial in 0f80e98: screen, month/week/agenda views, event card/sheet/modal, drawer, scope dialog, settings, banner and calendar/share sheets use `t()` + date-fns locales; TasksSheet (09c24fa), `formatReminder` (2abea97) and ICalImportSheet (b7f7110) localized too; deferred: ICalSubscriptionSheet and RecurrenceEditor still hard-code English
- [x] **Entire calendar UI is hard-coded English although RN has i18n** — `P2` — `missing` — partial in 0f80e98: screen, month/week/agenda views, event card/sheet/modal, drawer, scope dialog, settings, banner and calendar/share sheets use `t()` + date-fns locales; TasksSheet (09c24fa), `formatReminder` (2abea97) and ICalImportSheet (b7f7110) localized too; deferred: ICalSubscriptionSheet and RecurrenceEditor still hard-code English — fixed in 84fef142
- What WEB does: everything through `next-intl` (`t('calendar.*')`), month/day names and popover dates localized (changelog 1.6.x "Localize event start date in detail popover and event modal").
- What RN does: `src/i18n/index.ts` + `useLocaleStore` are used by 18 other files, but none of `src/components/calendar/*`, `src/screens/CalendarScreen.tsx` or `CalendarSettings.tsx` import it; all labels ("Today", "No events", "Does not repeat", "Going?", RSVP labels, drawer titles, etc.) and `date-fns` `format()` calls without a `locale` are English (`CalendarScreen.tsx:408, 448, 504, 640-641`, `EventModal.tsx:60-67, 326, ...`, `MonthView.tsx:25-26`, `AgendaView.tsx:33-37`).
- Fix hint: add a `calendar.*` namespace to `locales/*.json` (WEB's keys can be copied) and pass a date-fns locale from the locale store into `format()`.
- [ ] **First day of week limited to Monday/Sunday; no Saturday, no Jalali (#490)** — `P3` — `missing` — Saturday (0|1|6) done in 0f80e98; deferred: Jalali month grid needs a port of `lib/jalali-utils.ts` + `useCalendarLocale`
- [x] **First day of week limited to Monday/Sunday; no Saturday, no Jalali (#490)** — `P3` — `missing` — Saturday (0|1|6) done in 0f80e98; deferred: Jalali month grid needs a port of `lib/jalali-utils.ts` + `useCalendarLocale` — fixed in f1297b6b (Saturday kept after a restart since 10fb7ab7; Android date pickers and widgets stay Gregorian)
- What WEB does: `firstDayOfWeek` 0/1/6 with Saturday default for Persian and a full Jalali month grid / headers (ref `hooks/use-calendar-locale.ts:35-120`, `lib/jalali-utils.ts:161-169`, changelog 1.8.0 #490).
- What RN does: `FirstDayOfWeek = 0 | 1` (`src/stores/settings-store.ts:76`), `MonthView`/`WeekView` typed `0 | 1` (`MonthView.tsx:34`, `WeekView.tsx:40`), no Jalali.
- Fix hint: widen the type to `0|1|6`, add Saturday to `CalendarSettings`; Jalali is a larger port of `jalali-utils.ts` + a `useCalendarLocale` equivalent (only if the fa locale is added to RN).
@@ -214,7 +214,7 @@ RN has a solid read path (month/week/agenda, shared-calendar namespacing, per-vi
- What RN does: `formatRange` uses `eventTimeRange` -> `getEventEndDate` (exclusive), so a single-day all-day event on Mar 1 prints "Mar 1 – Mar 2" and a two-day one "Mar 1 – Mar 3" (`src/components/calendar/EventDetailSheet.tsx:63-74`, `src/lib/calendar-utils.ts:84-90`).
- Fix hint: for `allDay` use `getEventDisplayEndDate(event)` before comparing/formatting.
- [ ] **Deep links to calendar/event** — `P3` — `missing` — partly: incoming links are parsed since 5802cae (`src/navigation/linking.ts`), and an event link opens the event since 9282ee6; a date link only opens the Calendar tab
- [x] **Deep links to calendar/event** — `P3` — `missing` — incoming links are parsed since 5802cae (`src/navigation/linking.ts`), an event link opens the event since 9282ee6, a date link opens its view and day since fe455370 (same verdict as the 10 row)
- What WEB does: `parseCalendarPath`/`buildCalendarPath` handle `/calendar/<view>/<date>?event=` (ref `lib/deep-links.ts:272-300`, changelog 1.8.3 "Deep links for mail, calendar, contacts, files").
- What RN does: `Calendar: undefined` route params (`src/navigation/types.ts:33`); `handleDeepLink` hands an event to the Calendar tab through `pending-calendar-open`, the same path a tapped reminder takes, but ignores the date.
- Fix hint: accept `{ date?, eventId? }` params on the Calendar route and open the detail sheet.
@@ -246,7 +246,7 @@ RN has a solid read path (month/week/agenda, shared-calendar namespacing, per-vi
- What RN does: `createEvent` merges the `/set` echo over the payload (`src/api/calendar.ts:272-274`) and appends without expansion (`src/stores/calendar-store.ts:286-304`).
- Fix hint: after create, `CalendarEvent/get` the id (RN `getEvents([id])`) and, if `recurrenceRules` is set, run `expandRecurringEvents` for `loadedRange` or call `refresh()`.
- [ ] **Push: FCM subscription excludes `CalendarEvent`/`Calendar` types** — `P3` — `partial` — deferred: local reminders (8e1d03c) are resynced at launch, on `Calendar`/`CalendarEvent` state changes and on return to the foreground (4b903ed), but nothing refreshes them in the background; adding the calendar types to the push subscription lives in `src/lib/push-notifications.ts` (push area) and would need a background refresh + reschedule hook
- [ ] **Push: FCM subscription excludes `CalendarEvent`/`Calendar` types** — `P3` — `partial` — deferred: local reminders (8e1d03c) are resynced at launch, on `Calendar`/`CalendarEvent` state changes and on return to the foreground (4b903ed), but nothing refreshes them in the background; left in the second calendar pass: background reminder refresh without device sync: left, it needs native work: the push subscription would have to carry `CalendarEvent`/`Calendar` for every account (src/lib/push-notifications.ts), PushRouting.kt would have to start a headless JS task for a calendar-only push (today it starts nothing without device sync), and that task would need a reminder scheduler that runs without the restored session and stores the in-app one (src/lib/calendar-notifications.ts) depends on; Android only, and not testable without a device. While the app runs, reminders are rescheduled on every calendar change already (10 row)
- What WEB does: push subscription covers calendar types so the sidebar refreshes on external changes (FEATURES.md "JMAP push keeps everything in sync").
- What RN does: foreground EventSource/polling dispatches to `useCalendarStore.handleStateChange` (`App.tsx:390-397`, `src/stores/calendar-store.ts:261-284`) — at parity while the app is open; the background push subscription carries only `['EmailDelivery']` since 00faceb (`src/lib/push-notifications.ts`), so calendar changes never wake the app.
- Fix hint: leave as is unless background calendar refresh becomes needed.
@@ -267,7 +267,7 @@ RN has a solid read path (month/week/agenda, shared-calendar namespacing, per-vi
### Settings
- [ ] **No custom time-zone setting (#755); no birthday-calendar colour; no "Day" view on mobile (see above)** — `P3` — `missing` — time-zone setting (`calendarTimeZone`, used for saved events and JMAP queries) done in 58a6f45 + 0f80e98; the editors and views work in calendar-zone wall time since a8e6f6e and 8af163a, and floating task dues since 1a651a9; deferred: birthday-calendar colour setting
- [x] **No custom time-zone setting (#755); no birthday-calendar colour; no "Day" view on mobile (see above)** — `P3` — `missing` — time-zone setting (`calendarTimeZone`, used for saved events and JMAP queries) done in 58a6f45 + 0f80e98; the editors and views work in calendar-zone wall time since a8e6f6e and 8af163a, and floating task dues since 1a651a9; deferred: birthday-calendar colour setting — fixed in 574db426 (birthday colour; the time zone and the Day view were done before)
- What WEB does: `timeZone: 'auto' | IANA` overriding browser detection, used for display (`displayNow`, `toDisplayDate`) and for the `timeZone` on saved events (ref `lib/timezone.ts:29-75`, `stores/settings-store.ts:317, 553`, changelog 1.9.2 #755); `birthdayCalendarColor` (`settings-store.ts:377, 609`).
- What RN does: device zone only (`src/api/calendar.ts:15-21`); birthday colour fixed (`src/lib/birthday-calendar.ts:7`).
- Fix hint: add `timeZone` to the settings store (synced with WEB's key so it round-trips through settings sync), pass it as the JMAP `timeZone` arg and into saved events; convert display via `Intl` like WEB's `getWallClock`.
+4 -4
View File
@@ -73,7 +73,7 @@ RN covers the visible surface reasonably well (list with alphabetical index, det
- What RN does: shows the trash icon whenever `books.length > 1 && mayDelete !== false` (`src/components/settings/ContactsSettings.tsx:209`); the server then rejects and the user sees a raw error.
- Fix hint: add `!book.isDefault` to the guard.
- [ ] **Address book share / visibility (subscription) management missing** — `P3` — `missing`
- [x] **Address book share / visibility (subscription) management missing** — `P3` — `missing` — sharing fixed in 96b163a1 (same verdict as the 10 row); visibility (subscription): the webmail has no subscription switch for address books to port
- deferred: `shareWith`/`myRights` are now requested explicitly (580e010) and typed on `AddressBook`; no sharing sheet on mobile yet (reuse the calendar-sharing sheet when it exists).
- What WEB does: `shareAddressBook` via `shareWith/<principal>` patch + ShareCollectionDialog (`stores/contact-store.ts:1064-1085`, `components/contacts/contacts-app.tsx:1142-1158`); sidebar shows a shared indicator (`contacts-sidebar.tsx:879-881`).
- What RN does: nothing; `getAddressBooks` requests no `properties` so `shareWith` is whatever the server defaults to (`src/api/contacts.ts:17-24`; WEB requests `ADDRESS_BOOK_PROPERTIES` incl. `shareWith`, `lib/jmap/client.ts:252-261`, #257).
@@ -146,7 +146,7 @@ RN covers the visible surface reasonably well (list with alphabetical index, det
- What RN does: filter = contacts with no `addressBookIds` (`src/stores/contacts-store.ts:439-440`; `src/screens/ContactsScreen.tsx:146-149`), drawer row passes `count={0}` (`src/components/contacts/ContactsSidebarDrawer.tsx:128-136`) so the count never shows; on Stalwart every card has a book so the view is always empty.
- Fix hint: redefine as "no keywords", move the row under the Tags section, compute the count.
- [ ] **Contact list filters (org / title / location / domain / birthday month / has email-phone-photo) missing** — `P3` — `missing`
- [x] **Contact list filters (org / title / location / domain / birthday month / has email-phone-photo) missing** — `P3` — `missing` — fixed in 18c6b03f
- deferred: optional; free-text search only.
- What WEB does: filter drawer with tri-state chips (`components/contacts/contact-list.tsx:18-76, 154-228, 350-456`; changelog 1.5.x "Revamp contact detail view with filters").
- What RN does: free-text search only (`src/lib/contact-utils.ts:216-241`).
@@ -189,7 +189,7 @@ RN covers the visible surface reasonably well (list with alphabetical index, det
### Composer autocomplete
- [ ] **Autocomplete lacks groups, recent recipients (Sent), on-demand server search and directory principals** — `P2` — `missing`
- [x] **Autocomplete lacks groups, recent recipients (Sent), on-demand server search and directory principals** — `P2` — `missing` — fixed in a7a37573 (the composer's on-demand Sent search and directory people; event participants got groups and recent recipients in e988dc29)
- store-side done in 26a34d5 (`getAutocomplete` with group entries + recent recipients, `loadRecentRecipients`/`searchRecipients` fed by `src/api/recent-recipients.ts`, `getGroupRecipients`, #672 normalisation); the composer uses `getAutocomplete` and the recent Sent recipients since 5b72c4d. Still open: the on-demand server search (`searchRecipients` has no caller), directory principals, and the calendar's `ParticipantInput`, which still offers contacts only.
- What WEB does: `getAutocomplete` merges contacts, group entries (as one chip with `memberCount`), RFC 9670 directory principals and recent Sent recipients, deduped and sanitised (#672) (`stores/contact-store.ts:622-707, 23-49`; `loadRecentRecipients` `:1118-1142` fed from `mail-app.tsx:423`; `searchRecipients` `:1144-1154` used by `components/email/email-composer.tsx:1175-1212`; group chip insertion `:1218-1233`; changelogs 1.6.x "Recipient autocomplete from Sent, with on-demand server search", 1.7.x group chips).
- What RN does: suggestions come only from `individuals` matched with `matchesContactSearch` (`src/screens/ComposeScreen.tsx:367-392`); groups are explicitly filtered out and nothing else is consulted. `ParticipantInput` (calendar) likewise flattens contacts only (`src/components/calendar/ParticipantInput.tsx:34-51`).
@@ -209,7 +209,7 @@ RN covers the visible surface reasonably well (list with alphabetical index, det
- What RN does: none (see previous item).
- Fix hint: same sheet as above; show the matching card's phone/org and an "Open" that navigates to `ContactDetail`.
- [ ] **Contact deep links (`/contacts/<id>[/edit]`, `/contacts/new`)** — `P3` — `missing`
- [x] **Contact deep links (`/contacts/<id>[/edit]`, `/contacts/new`)** — `P3` — `missing` — fixed in ecefd1cb (same verdict as the 10 row)
- partly: `/contacts/<id>` opens the contact since 5802cae; `/contacts/new` and `/contacts/<id>/edit` only open the Contacts tab.
- What WEB does: `parseContactsPath`/`buildContactsPath` (`lib/deep-links.ts:323-329`), applied once on mount (`components/contacts/contacts-app.tsx:182-224`).
- What RN does: `src/navigation/linking.ts` (5802cae) maps `/contacts/<id>` to `ContactDetail { contactId }` and every other contacts path to the tab; it never opens `ContactForm`.
+1 -1
View File
@@ -143,7 +143,7 @@ Filters: RN carries a byte-for-byte port of WEB's Sieve parser/generator/tests a
- What WEB does: on first listing, reparents nodes named with `/`, `∕`, `⁄`, `/` separators and replaces blob-backed "dir marker" files with real folders, with a progress overlay (`files-app.tsx:245-263`, `710-734`).
- What RN does: reads only the real hierarchy (`src/api/files.ts:6-14` comment acknowledges WEB migrates). A user who never opens WEB sees legacy files as flat names at the root. Acceptable if WEB is always used at least once; otherwise port `migrateLegacyFlatNodes`.
- [ ] **Deep link `/files/<folder>` and `?preview=` not handled** — `P3` — `missing` (changelog 1.8.1 "Deep links for mail, calendar, contacts, files") — partly: `/files` links open the Files tab since 5802cae; folder paths and `?preview=` are ignored
- [x] **Deep link `/files/<folder>` and `?preview=` not handled** — `P3` — `missing` (changelog 1.8.1 "Deep links for mail, calendar, contacts, files") — `/files` links open the Files tab since 5802cae; folder paths, `?preview=` and `?shared=` since d684b00e (same verdict as the 10 row)
- What WEB does: `parseFilesPath`/`buildFilesPath` walk the drive to the folder and open the preview (`components/files/files-app.tsx:190-278`; `lib/deep-links.ts:381`).
- What RN does: `src/navigation/linking.ts` (5802cae) turns any `/files/...` link into `{ kind: 'files' }` and opens the tab; `Files: undefined` in `src/navigation/types.ts:35`.
- Fix hint: parse the rest of the path in `linking.ts`, pass it to `Files` as a `path` param and walk `allNodes` by names.
+1 -1
View File
@@ -171,7 +171,7 @@ RN toggles that are stored but never read (fix: either wire them or remove the c
- What RN does: `setupPushNotificationsInner` calls `native.getToken()` directly (not the guarded `getFcmToken`) so a Firebase rejection (`fcm_token_failed`, typical on de-Googled devices or right after `deleteToken()` during Disable) bubbles up raw; `registerWithRelay` throws `Relay register failed: <status>` without the body (`RN: src/lib/push-notifications.ts:312-313,215-217`); `pollVerificationCode` times out after 75 s with the same message for every cause (`:268`). The UI shows `error.message` verbatim (`NotificationSettings.tsx:95-99`). Disable then immediately Enable also races: `teardownPushNotificationsForAccount` deletes the FCM token when it was the last account (`:477-481`) and the next `getToken()` may be rejected until Firebase re-registers.
- Fix hint: read the relay response body into the error, distinguish "no Google Play services" (map to the UnifiedPush finding), skip `deleteToken()` unless the user disables push for all accounts, and log each phase (`token`, `relay`, `jmap create`, `verify`) so the settings pane can show which step failed. Needs a repro on the reporter's device to confirm the exact failure.
- [ ] **iOS push missing entirely** — `P2` — `missing` — deferred: needs an APNs transport in the relay + iOS token module; the settings pane now states Android-only
- [x] **iOS push missing entirely** — `P2` — `missing` — deferred: needs an APNs transport in the relay + iOS token module; the settings pane now states Android-only — fixed in f62849d1
- What WEB does: Web Push works on iOS 16.4+ once installed to the home screen (`lib/web-push.ts:157-164,394-398`).
- What RN does: `getNative()` returns `null` off Android and setup throws "Push notifications require Android" (`RN: src/lib/push-notifications.ts:125-128,303-304`); the relay has only FCM and Web Push transports (`repos/relay/README.md` endpoints).
- Fix hint: needs an APNs transport in the relay (`POST /api/push/register/apns` storing a device token, HTTP/2 to `api.push.apple.com` with a `.p8` key) plus an iOS native module (or `expo-notifications`) for the token and a Notification Service Extension to fetch sender/subject over JMAP. Content-blind payload stays the same.
+2 -2
View File
@@ -181,7 +181,7 @@ RN's JMAP client (`src/api/jmap-client.ts`, 615 lines) is a thin transport: sess
- What RN does: `android:allowBackup="true"` with `@xml/secure_store_backup_rules` from the expo-secure-store plugin, which only excludes SecureStore's own prefs (`android/app/src/main/AndroidManifest.xml:16`); the AsyncStorage SQLite DB (full cached bodies `webmail:offline-cache:entry:v2:*`, outbox, settings, account registry) is backed up to the user's Google account.
- Fix hint: add an app-owned `fullBackupContent`/`dataExtractionRules` excluding `databases/RKStorage` (AsyncStorage) or set `allowBackup=false` via an Expo config plugin.
- [ ] **Send has no offline path and no draft fallback** — `P2` — `missing` — partly: drafts autosave to the server since 5b72c4d, and since 92b42e4 the close dialog no longer drops the text when the draft can't be saved; an offline send is still not queued, and a draft can't be saved offline
- [x] **Send has no offline path and no draft fallback** — `P2` — `missing` — partly: drafts autosave to the server since 5b72c4d, and since 92b42e4 the close dialog no longer drops the text when the draft can't be saved; a draft can't be saved offline — the offline message (says offline, the text stays) is in a43b55ec. One verdict for the 04, 09 and 10 rows — fixed in 0ec809eb
- What WEB does: no offline queue either, but send failures leave the composer open with the draft autosaved (composer agent's area); send itself has a deadline (#702).
- What RN does: `ComposeScreen` shows `Alert('Send failed')` and keeps the modal (`src/screens/ComposeScreen.tsx:975-979`); there is no draft API in `src/api/email.ts` (`grep -n '[Dd]raft' src/api/email.ts` only finds the send create id), so the body is lost if the user leaves, and an offline send is not queued although the outbox exists.
- Fix hint: either add an `outbox` `send` op kind (blob uploads must already be done) or at minimum persist the composer state locally on failure.
@@ -231,7 +231,7 @@ RN's JMAP client (`src/api/jmap-client.ts`, 615 lines) is a thin transport: sess
- What RN does: `onShouldStartLoadWithRequest` returns `true` for any `data:` URL (`src/components/EmailBodyView.tsx:715-718`), so `<a href="data:text/html,...">` replaces the message with attacker HTML rendered inside the app chrome (phishing form); the WebView is otherwise well hardened (`originWhitelist=['about:blank']`, file access off, `mixedContentMode="never"`, `setSupportMultipleWindows={false}`, `incognito`, `domStorageEnabled={false}`, `EmailBodyView.tsx:675-712`).
- Fix hint: allow `data:` only for `request.isTopFrame === false` or block it entirely (the body is loaded via `source={{html}}`, not a data: URL).
- [ ] **No screenshot / recent-apps protection option** — `P3` — `missing`
- [x] **No screenshot / recent-apps protection option** — `P3` — `missing` — fixed in 0ae33f3a (the Android 14 launch crash it caused in d2d44e0d)
- What WEB does: N/A.
- What RN does: no `FLAG_SECURE`/`expo-screen-capture` anywhere; mail content appears in the Android recents thumbnail.
- Fix hint: optional "Hide content in app switcher" setting using `expo-screen-capture` `preventScreenCaptureAsync`.
+441
View File
@@ -0,0 +1,441 @@
# Webmail 1.10.0 to 1.18.0 (and Unreleased)
## Summary
The last parity pass was against webmail 1.9.2. This file walks every changelog entry from 1.10.0 to 1.18.0, the Unreleased section and the commits after the 1.18.0 release (b0d1a97bb..main, 2026-10-29), checked against native `feat/stalwart-1.0` with main merged in (f1db6b48). Web paths are relative to the webmail root, native paths to this repo.
What stands out:
- Several webmail security fixes never reached the app: the HTML sanitizer lets `<img src=x/onerror=…>` through, a sender can fake a DMARC pass in the auth chips, mailto unsubscribe sends to every address, a crafted winmail.dat freezes the JS thread, and the webmail hand-off still puts the password in the `bulwarkmobile://` fragment.
- Sieve interop broke in both directions: the app reads webmail 1.13+ scripts (metadata v2) as raw Sieve and its "Reset to visual builder" wipes them, and a save on the phone broadens "is the address" rules into substring matches.
- Account switches still leak: the composer follows a switch with an open draft (and its autosave can destroy another account's message), calendar, task and contact loads from the previous account land after a switch, Undo/Send now of a delayed send goes through the active login, and push renewal only reaches the active account.
- Everything the organisation policy drives (locked settings, session length, external recipient confirm and label, org signature and disclaimer) has no source on the phone: the policy is served by the webmail at `/api/admin/policy` behind its session cookie (`lib/auth/instance-details.ts:23`), and the app forgets which webmail it was paired with (`src/lib/oauth.ts:292`). These need a webmail change first (see "Needs a decision").
- Features that live on the JMAP server and only need porting: shared tags and team templates (JMAP Metadata `org.bulwarkmail/*`), recover deleted items (`x:ArchivedItem`), delegates (`shareWith`), Files trash, save attachment to Files, scheduled message view.
Classification: `bug` = fixed in the webmail, still live in the app; `missing`; `partial`. Not applicable on mobile and left out: admin panel, plugins and marketplace, command palette, keyboard shortcuts, print, Bulwark Lite, CSP/third-party switch, Mountain View, desktop-only layout (drag and drop, hover, multi-pane), server routes the app doesn't use, Docker, docs and translation-only entries.
Stalwart 1.0.0 items found in the same pass (1.0 SSO scopes, Files on 1.0, #1385) are tracked with the Stalwart 1.0 work and not repeated here.
## Findings
### Security
- [x] **HTML sanitizer lets `on*` handlers through after a slash** — `P1` — `bug` — fixed in f5e997ba, 07679cbf, 47a1d249, 440a2e7a
- What WEB does: DOMPurify for mail and drafts (`lib/email-sanitization.ts`, GHSA-g35q-3gpc-x2rr).
- What RN does: the handler regex needs whitespace before `on*` (`src/lib/email-html.ts:142-145`), so `<img src=x/onerror=…>` survives. The viewer WebView (`src/components/EmailBodyView.tsx:793`) and the editor (`src/lib/editor-html.ts:37`, seeded at `src/screens/ComposeScreen.tsx:557`) allow inline script; a received mail can navigate without a tap (`EmailBodyView.tsx:841-843`).
- Fix hint: drop every `on*` attribute whatever precedes it (or parse), and stop allowing inline handlers.
- [x] **A sender can fake a DMARC/DKIM pass in the auth chips** — `P1` — `bug` — fixed in ef9561f1, 8adef1e1
- What WEB does: splits Authentication-Results on `;` outside quotes and comments, reads the method at the start of each part, takes DKIM/DMARC from the topmost header only (`lib/email-headers.ts:231`, 8f7124c87).
- What RN does: matches `dkim=`/`dmarc=` anywhere in all headers joined (`src/lib/email-headers.ts:143`, `:152`, `:265`); this also feeds invitation trust.
- Fix hint: port the web parser.
- [x] **mailto unsubscribe sends to every address and the confirm hides it** — `P1` — `bug` — fixed in 942f4517, 918571c3
- What WEB does: one address, ignores `to=`/`cc=`, caps subject and body, shows them in the confirm (`lib/validation.ts:180`, `components/email/unsubscribe-banner.tsx:42`, f0aa0eeda).
- What RN does: sends to every to/cc and `to=` address with the sender's subject and body (`src/lib/unsubscribe.ts:95-113`); the confirm only says an email will be sent (`src/components/email/UnsubscribeBanner.tsx:139-147`).
- Fix hint: port parseUnsubscribeMailto and the confirm text.
- [x] **A crafted winmail.dat freezes the JS thread** — `P1` — `bug` — fixed in 855d304b
- What WEB does: clamps the MAPI value count and stops on a read that doesn't advance (`lib/tnef.ts:173`, 012fe6eda).
- What RN does: trusts the count (up to 2^32) and loops on non-advancing reads (`src/lib/tnef.ts:201`, `:211`).
- Fix hint: clamp to remaining/4, break on a null or non-advancing read.
- [x] **Webmail hand-off returns the password in the `bulwarkmobile://` fragment** — `P2` — `bug` (cross-repo) — fixed in ab2e5a60 (webmail side fb3ba0275)
- What WEB does: `app/(main)/[locale]/login/page.tsx:426`, `lib/auth/mobile-handoff.ts:31` put the password in the redirect.
- What RN does: reads it from the fragment (`src/lib/oauth.ts:247`); any app can claim a custom scheme.
- Fix hint: hand back a one-time code and redeem it like QR pairing (`src/lib/oauth.ts:435`, `/api/auth/pair/redeem`). Needs the webmail side first.
- [x] **Sender favicons are fetched from icons.duckduckgo.com on the device** — `P3` — `partial` (privacy) — fixed in 8e10e73e (off by default; the push background task keeps its own default, see the reader report)
- What WEB does: proxies favicons on the server (`app/api/favicon/route.ts:484`).
- What RN does: fetches directly, on by default (`src/lib/avatar-utils.ts:149`, `src/stores/settings-store.ts:312`), leaking sender domains and the device IP.
- Fix hint: default off, or use the paired webmail's proxy.
### Accounts and sign-in
- [ ] **Org user policy and "Set by your organisation"** — `P2` — `missing` (blocked) — partly: applied in 9d977773, locked and hidden settings in f27e2ceb, colour mode in b7b3dd4c, archiving off in efb92593 and 13dcbdc0, delegates and recovery in bc47c6a1; not yet: share rights for mail folder presets, group-identity scopes, Archive on the Android widget
- What WEB does: locks or hides settings per domain or group (`stores/policy-store.ts:110`, `hooks/use-restriction.ts:22`, `components/settings/org-locked-note.tsx:14`), incl. colour mode #1280, archiving off #1257, tags above shared mailboxes #1334, share rights, hiding delegates or recovery.
- What RN does: nothing (`src/stores/settings-store.ts:453`); the paired webmail is forgotten (`src/lib/oauth.ts:292`).
- Fix hint: keep the webmail URL at hand-off/pairing and fetch the policy with a bearer token. Needs a webmail route that accepts a JMAP bearer token. See "Needs a decision".
- [ ] **Admin-set idle sign-out and maximum session length** — `P2` — `missing` (blocked)
- What WEB does: `components/session/session-timeout-guard.tsx:94`, `lib/session-policy/limits.ts:36`, refuses to restore over-age sessions (6e5d8faec), saves drafts first (79c697a93).
- What RN does: stays signed in for good (`src/stores/auth-store.ts:929`, `App.tsx:818`).
- Fix hint: needs the policy; then an AppState idle timer and a max-age check in restoreSession, drafts flushed first.
- [x] **With TOTP on, changing the password and turning TOTP off fail** — `P2` — `bug` — fixed in 99a42128
- What WEB does: asks for the current code and sends it (`components/settings/account-security-settings.tsx:52`).
- What RN does: sends neither (`src/api/account-security.ts:346-353`, `:387-395`; `src/components/settings/AccountSecuritySettings.tsx:292`); Stalwart answers "Current OTP code is required".
- Fix hint: ask for the code and patch `otpAuth/otpCode` (not the whole object).
- [x] **A full sign-out keeps the search history** — `P2` — `bug` — fixed in 986216f4, 639aa4aa
- What WEB does: `stores/auth-store.ts:1301`, `lib/sign-out-cleanup.ts:22`.
- What RN does: clearAllFeatureStores (`src/stores/auth-store.ts:126-134`) never clears `src/stores/search-history-store.ts:44`.
- Fix hint: clear it, and check the other persisted stores in the same pass.
- [x] **Internationalized domains** — `P3` — `missing` — fixed in f5875f87 (sign-in, discovery, account rows), 4932764a (composer recipients sent in ASCII form; own-domain senders shown as written in the composer and reader), b35d15ca (mail list rows), ecf1f3ec (auto-reply forward address in ASCII form); unit tests only, the test servers have no IDN domain
- What WEB does: `lib/idn.ts:25` (#1100). What RN does: ASCII-only checks (`src/lib/recipients.ts:31`, `src/lib/server-discovery.ts:78`).
- Fix hint: toASCII on the wire, Unicode on screen.
- [x] **Sign-out doesn't end the identity provider session** — `P3` — `missing` — fixed in 5a301d62
- What WEB does: `app/api/auth/token/route.ts:90` (#905). What RN does: `src/lib/oauth.ts:219`, `src/lib/oauth-native.ts:158`.
- Fix hint: open end_session_endpoint, or use an ephemeral browser session.
- [x] **Explain a refused token exchange on TOTP sign-in** — `P3` — `partial` — fixed in 37e72c12
- What WEB does: `stores/auth-store.ts:153`, `:1432`. What RN does: `src/lib/totp-login.ts:148`, `:156` throw token_exchange_failed; `src/lib/login-errors.ts:129` has no copy.
- [ ] **Shared account settings scope for calendar and contacts** — `P3` — `missing` (old 01 item)
- What WEB does: `components/settings/account-settings.tsx:32`. What RN does: `src/lib/capabilities.ts:86` covers filters and vacation only.
### Mail list and folders
- [x] **The list stops loading older mail when the server sends no total (#1364)** — `P2` — `bug` — fixed in 58bc4aca
- What WEB does: keeps paging while full pages come back (`stores/email-store.ts:6508-6515`).
- What RN does: a missing total becomes 0 (`src/api/email.ts:529`, `:757`) and paging stops (`src/stores/email-store.ts:1113`).
- [x] **View scheduled messages; Cancel send puts them back in Drafts (native #84)** — `P2` — `partial` — fixed in 14d009de, 51b3208b, c817f978
- What WEB does: opens scheduled mail with a banner (Send now, Reschedule, Cancel) (`components/mail/mail-app.tsx:2523`, `components/email/email-viewer.tsx:773`, f7adbf1ef, 780b64461, 831a3e6b8).
- What RN does: a row tap only opens the action sheet (`src/screens/ScheduledScreen.tsx:278`); Cancel leaves the copy in Sent (`:129-132`, `src/api/email.ts:2079`).
- Fix hint: open the row in the thread screen with a scheduled banner; make Cancel do the cancel-to-Drafts that Edit already does.
- [x] **Shared tags in group mailboxes (#1310, #470)** — `P2` — `missing` — fixed in b7b9c7e0, ba672050 (read-only; editing stays in the webmail)
- What WEB does: reads and writes `org.bulwarkmail/tags` via JMAP Metadata on the group account (`lib/shared-tags.ts:185`, `components/settings/shared-tag-settings.tsx`, `lib/jmap/bulwark-metadata.ts`).
- What RN does: local tag names only (`src/stores/keywords-store.ts:5`, `src/components/settings/KeywordSettings.tsx`).
- Fix hint: read the group's tags through Metadata (Stalwart 1.0 implements it; the 0.16 guard from 42751a6b stays). Locking needs the policy.
- [x] **Search leaves out Spam and Trash by default** — `P2` — `missing` — fixed in a412b516, 2b5257bf
- What WEB does: `inMailboxOtherThan` trash/junk unless "All folders" is picked (`lib/unified-mailbox.ts:98`, 763732fdc, 3904af2cb).
- What RN does: no mailbox filter (`src/stores/email-store.ts:268-271`, `:492`).
- [x] **Unified "All mail" search leaves out Sent, Archive and Drafts** — `P2` — `bug` — fixed in 484cac58
- What WEB does: with a query, excludes only trash and junk (`lib/unified-mailbox.ts:510`).
- What RN does: applies the view exclusions also with a query (`src/api/unified-inbox.ts:353`, `:35`).
- [ ] **Undo after a move to another account and in the unified inbox** — `P3` — `partial`
- What WEB does: `stores/email-store.ts:5177` (ed9fa2de4), `lib/email-action-toast.ts` (6c5aead20).
- What RN does: Undo exists within an account and in spanning lists, not after crossAccountMove (`src/stores/email-store.ts:1540-1550`, `:1786-1796`) or for `src/screens/UnifiedInboxScreen.tsx:246-270`.
- [ ] **Archive without an Archive folder; unified archive modes (#1176, #1178)** — `P3` — `missing`
- What WEB does: creates Archive and reuses year/month folders (`stores/email-store.ts:1332`, `:2561`).
- What RN does: returns silently (`src/stores/email-store.ts:1594`, `:1729`) or throws (`src/api/unified-inbox.ts:546`).
- [x] **A server's own "all" mailbox is included in All mail/Unread (#1241)** — `P3` — `bug` — fixed in 43bb5e47
- What WEB does: role `all` excluded (`lib/jmap/types.ts:1158`). What RN does: `src/api/unified-inbox.ts:34`, `:354`.
- [x] **Tag views and counts include Trash and Spam (#1156)** — `P3` — `bug` — fixed in eb6bf6d3
- What WEB does: `lib/unified-mailbox.ts:104`, `:577`. What RN does: `src/api/tag-counts.ts:37-48`, `src/stores/email-store.ts:455`.
- [x] **User folders named like system folders get system icons (#1235, native #80)** — `P3` — `bug` — fixed in 96c7c045
- What WEB does: icons by role only (`components/layout/sidebar.tsx:168`). What RN does: name matching fallback (`src/components/SidebarDrawer.tsx:92`).
- [x] **Sort by "Date sent" falls back to received date in merged lists (#1219)** — `P3` — `bug` — fixed in 04a962a6
- What WEB does: lists fetch sentAt (`lib/jmap/client.ts:443`). What RN does: EMAIL_LIST_PROPERTIES lacks it (`src/api/email.ts:22`; comparator `src/lib/message-list-order.ts:225`).
- [x] **Previews start with a style sheet (#1136)** — `P3` — `bug` — fixed in 219aef04 (list, unified list, conversation cards; notifications: platform)
- What WEB does: cleanPreview (`lib/utils.ts:320`, `:363`). What RN does: `src/screens/EmailListScreen.tsx:270`, `src/lib/push-background-task.ts:513`.
- [x] **Paperclip on messages with only inline images** — `P3` — `bug` — fixed in 4470a4ba (folder list)
- What WEB does: `components/email/attachment-chips.tsx:165`. What RN does: `src/screens/EmailListScreen.tsx:237`, `src/screens/UnifiedInboxScreen.tsx:358` use hasAttachment.
- [x] **List row grows when its attachment chips arrive** — `P3` — `bug` — fixed in 4470a4ba
- What WEB does: reserves the row (7b25d3a95). What RN does: `src/components/email/ListAttachmentChips.tsx:89`.
- [x] **Cold start reopens the last folder or search instead of the inbox** — `P3` — `bug` — fixed in 9067908f
- What WEB does: 64b39c504. What RN does: `src/stores/email-store.ts:2195`, `:829`.
- [x] **List keeps its scroll position on folder, tag or search switch (#1138)** — `P3` — `bug` (from the code, not seen on a device) — fixed in aa25d08d
- What RN does: `src/screens/EmailListScreen.tsx:1590`.
- [ ] **Unified inbox live updates for other logins (#1350)** — `P3` — `partial`
- What RN does: `src/screens/UnifiedInboxScreen.tsx:154-167` listens to the active login only.
- [ ] **Copy to another account (#1150); move several folders (#1173); move menu order in a shared mailbox (#1149)** — `P3` — `missing`
- What WEB does: `hooks/use-message-file-actions.ts:94`, `components/settings/folder-settings.tsx:202`. What RN does: `src/components/MoveSheet.tsx:31`, `src/components/settings/FolderSettings.tsx:137-173`, `src/lib/mailbox-tree.ts:162-205`.
- [ ] **Hide shared mail accounts (#1228); remove from this folder (#615); folder name on all-folder results (#1172); own folders section (#1205); per-folder counter (#982); hide account dot (#912)** — `P3` — `missing`
- What WEB does: `lib/shared-mail-visibility.ts:19`, `hooks/use-remove-from-folder.ts:15`, `stores/settings-store.ts:695`. What RN does: `src/components/SidebarDrawer.tsx:124-150`, `:672`; `src/screens/EmailListScreen.tsx:103`.
- [x] **Search folder picker is flat and capped at 12** — `P3` — `partial` — fixed in e5c9433a
- What WEB does: `lib/search-scope-folders.ts`. What RN does: `src/screens/EmailListScreen.tsx:1729-1740` (`slice(0, 12)`).
- [ ] **Tags without a definition are grey; no account tint (#1052); tag visibility, nesting, order** — `P3` — `partial` — partly: nesting and visibility in 585c70a5; grey undefined tags, account tint and order still open
- What WEB does: `hooks/use-keyword-format.ts`, `stores/settings-store.ts:391`. What RN does: `src/screens/EmailListScreen.tsx:164`, `src/stores/keywords-store.ts:9`.
- [ ] **Today/yesterday follow the time zone setting (#1259); .eml import and cross-account moves keep the date (#891)** — `P3` — `partial`
- What RN does: `src/lib/date-format.ts:111-115` (device zone); `src/api/email.ts:1059-1072` sends no receivedAt.
- [ ] **Reorder folders; custom folder icons** — `P3` — `missing` (old 02 items)
- What WEB does: `stores/email-store.ts:7502`, `components/settings/folder-settings.tsx:261`, `:701`. What RN does: `src/components/settings/FolderSettings.tsx:350`, `:32` (`src/api/email.ts:297` already takes sortOrder).
- [ ] **RTL swipe sides** — `P3` — `partial` (old 08 item; web doesn't swap either)
- What RN does: `src/components/SwipeableRow.tsx:353`.
- [x] **`/mail/folder/<ref>` deep link opens only the tab** — `P3` — `bug` — fixed in ed490b71 (folder of the active account; `?account=` switches first as before)
- What RN does: `src/navigation/linking.ts:134` parses it, `:285-287` drops it.
### Reading mail
- [x] **Unverified-sender warning, badge, no "Always trust" for forged mail** — `P2` — `partial` — fixed in cc21317b
- What WEB does: getSenderVerification (`lib/email-headers.ts:112`), banner and badge (`components/email/email-viewer.tsx:4037`).
- What RN does: a spoof line in the collapsed details only, first DKIM signature only, trust still offered (`src/components/email/MessageHeader.tsx:108`, `:273`; `src/lib/email-headers.ts:99`; `src/components/EmailBodyView.tsx:771`).
- [ ] **External sender chip** — `P2` — `missing` (blocked on the policy)
- What WEB does: `lib/external-mail.ts:119`, `components/email/external-sender-chip.tsx`, `components/email/email-viewer.tsx:903`.
- What RN does: `src/components/email/MessageHeader.tsx:159`, nothing.
- Fix hint: internal and partner domains come from the policy; identity domains would only be a fallback.
- [x] **Invitation trust uses unaligned SPF/DKIM and flags colleagues (#1374)** — `P2` — `bug` — fixed in 7a95d43f, 1bce37bb, 6133bcd9
- What WEB does: alignment check and own-domain exemption, REPLY/COUNTER checked against the answering attendee (`lib/calendar-invitation.ts:210-216`, `:400-422`).
- What RN does: `src/lib/calendar-invitation.ts:325-328`, `:375-376`.
- [x] **Copy chip for sign-in and verification codes** — `P2` — `missing` — fixed in b1550f14
- What WEB does: `lib/verification-code.ts:275`, `components/email/email-viewer.tsx:1378`, setting at `stores/settings-store.ts:540`.
- What RN does: nothing (`src/components/email/MessageHeader.tsx`, `src/screens/EmailListScreen.tsx`).
- [x] **A mail full of blockquotes freezes the reader** — `P3` — `bug` — fixed in 3243714d
- What WEB does: single walk (`lib/quote-collapse.ts:92`). What RN does: quadratic walk (`src/lib/quote-collapse.ts:95-112`).
- [x] **Attachment names keep direction-override characters** — `P3` — `bug` — fixed in a930d177
- What WEB does: `lib/bidi.ts:10`. What RN does: `src/lib/attachment-display.ts:34`, `src/lib/email-export.ts:56`.
- [x] **Part names that are paths (#1371, #1373)** — `P3` — `bug` — fixed in 4d545f8f
- What WEB does: `lib/jmap/download-name.ts:11`. What RN does: `src/api/jmap-client.ts:1350` puts `%2F` into the download URL.
- [x] **Dark mode recolours instead of inverting** — `P3` — `missing` — fixed in 23b2bf97
- What WEB does: `lib/email-dark-mode.ts:48`, `:71`. What RN does: `filter: invert(1) hue-rotate(180deg)` (`src/lib/email-html.ts:500`); mail's own dark styles follow the system scheme (`:640`).
- [x] **"via" badge repeats the sender's address** — `P3` — `bug` — fixed in fac54b41
- What WEB does: `components/email/email-identity-badge.tsx:69`. What RN does: `src/components/email/MessageHeader.tsx:116-117`, `:223`.
- [ ] **Fixed-width tables don't reflow (#1020)** — `P3` — `partial`
- What WEB does: `lib/email-fit-width.ts:111`. What RN does: shrinks the whole mail (`src/components/EmailBodyView.tsx:277-290`).
- [ ] **Invitation banner: View in calendar, collapse, counter-proposal review** — `P3` — `missing` (old 03 item)
- What WEB does: `components/email/calendar-invitation-banner.tsx:727`, `:463`, `:245`. What RN does: `src/components/email/CalendarInvitationBanner.tsx:153`.
- [x] **Quick reply can be turned off (#1370)** — `P3` — `missing` — fixed in 56e4a1e0 (setting `quickReplyEnabled`; the switch is the platform agent's)
- What WEB does: `stores/settings-store.ts:560`. What RN does: always rendered (`src/screens/EmailThreadScreen.tsx:1233`).
### Composer and sending
- [x] **An open draft follows an account switch; its autosave can destroy another account's message** — `P1` — `bug` — fixed in 9daa3441, 388f46b5, 4e57b191
- What WEB does: pins the account when the composer opens (`components/email/email-composer.tsx:506`, d58344329).
- What RN does: reads Drafts/Sent, identities and `jmapClient.accountId` live (`src/screens/ComposeScreen.tsx:443-455`, `:778`, `:1355`; `src/api/email.ts:1899`, `:1928-1930`). A notification tap or deep link switches with the composer open (`App.tsx:117-120`); the next autosave creates the draft in the other account and destroys `previousDraftId` there.
- Fix hint: pin account, identities and mailboxes at open; pass the account to saveDraftVersion, sendEmail and uploads.
- [x] **Undo or Send now of a delayed send goes through the active login** — `P1` — `bug` — fixed in e81f0071, 893f40a2
- What WEB does: GHSA-8x23 (`components/mail/mail-app.tsx:2549`).
- What RN does: the pending send has no login (`src/stores/send-undo-store.ts:89`, `:109-118`; `src/screens/ComposeScreen.tsx:2202`); after a switch the cancel hits the same submission id on another server.
- Fix hint: store the login with the pending send; refuse or clear on a switch.
- [x] **Reschedule and Send now create the replacement before cancelling (#1308)** — `P2` — `bug` — fixed in 22f2ac5b, fdf8eece
- What WEB does: cancels first, restores on failure (`lib/jmap/client.ts:11532-11664`).
- What RN does: creates first (`src/api/email.ts:2189`).
- [x] **A send the server doesn't confirm is reported as sent (#1019)** — `P2` — `bug` — fixed in 3eac700a
- What WEB does: `lib/jmap/client.ts:994`, `:5115`.
- What RN does: returns success without a submission id (`src/api/email.ts:1857`), drops the draft and closes (`src/screens/ComposeScreen.tsx:2158`).
- [x] **Send and save drafts as a group or shared identity (#1090)** — `P2` — `missing` (old 04 item too) — fixed in 31d8e4a4, 6414b0d6, 893f40a2
- What WEB does: resolveSendAccountId (`lib/jmap/client.ts:6257`), identities of every account (`components/email/email-composer.tsx:214`, `:271`).
- What RN does: primary account's identities only (`src/api/identity.ts:17`, `src/screens/ComposeScreen.tsx:778`); sendEmail has no account (`:2151`).
- [x] **Team templates, personal templates in the account, org template library** — `P2` — `missing` — fixed in fac318dc (personal + team read-only; org library needs the policy)
- What WEB does: templates in JMAP Metadata with tombstones (`lib/templates/personal-templates.ts:141`, `lib/templates/team-templates.ts:57`, `lib/templates/org-library.ts:13`); on 1.0 the webmail moves them into the account.
- What RN does: local only (`src/stores/templates-store.ts:30`, `src/components/TemplateSheet.tsx`).
- Fix hint: read and write `org.bulwarkmail` templates via Metadata. The org library and locks need the policy.
- [x] **Attach from Files (#1179)** — `P2` — `missing` — fixed in 8e07b7d8
- What WEB does: `components/email/email-composer.tsx:4574`. What RN does: device picker only (`src/screens/ComposeScreen.tsx:1686-1711`).
- [ ] **External recipient confirm (composer, quick reply, invitations)** — `P2` — `missing` (blocked on the policy)
- What WEB does: `lib/outgoing/external-recipients.ts:53`, `:107`, `components/email/external-recipient-confirm.tsx`.
- What RN does: nothing (`src/screens/ComposeScreen.tsx:2151`, `src/components/email/QuickReplyBox.tsx`, `src/components/calendar/EventModal.tsx`).
- [ ] **Org signature with user details; legal disclaimer on every sent message** — `P2` — `missing` (blocked on the policy) — partly: org signature and disclaimer in fe3f860e; the user details (title, department, phone) stay empty without settings sync
- What WEB does: added in the client before sending (`lib/org/outgoing.ts:73`, `:99`, `lib/org-signature.ts:311`, 0154e24ff).
- What RN does: mail from the phone goes out without the disclaimer (`src/screens/ComposeScreen.tsx:2151`, `src/lib/signature-utils.ts`).
- [x] **Unfilled placeholder warning** — `P3` — `missing` — fixed in a8e7a3f4, 7594e459
- What WEB does: findUnfilledPlaceholders with per-locale words and a confirm (`lib/placeholder-check.ts:115`, `components/email/email-composer.tsx:3261`), setting placeholderWarningEnabled.
- What RN does: nothing (`src/screens/ComposeScreen.tsx:2151`).
- [x] **Signature swap when changing From** — `P3` — `bug` — fixed in 60d0e98f
- What WEB does: `components/email/email-composer.tsx:1148`, `components/email/compose-format.ts:71`.
- What RN does: an identity without a signature keeps the primary's (`src/lib/signature-utils.ts:83-91`, `src/screens/ComposeScreen.tsx:975`); typing under the signature breaks the swap (`src/lib/signature-utils.ts:245`).
- [x] **Plain-text part of sent mail** — `P3` — `bug` — fixed in 0647cbf1
- What WEB does: `lib/html-to-text.ts:73`, `:169`. What RN does: "-- " trimmed to "--", "> " dropped, ordered lists bulleted (`src/lib/compose-html.ts:109`, `:112`, `:125`).
- [x] **Composer prompts: missing-subject asks again, autosaved draft says "unsaved", no Discard for a reopened draft** — `P3` — `bug` — fixed in 1cc4ccca
- What WEB does: `components/email/email-composer.tsx:3251`, `:3770`, `:3758`. What RN does: `src/screens/ComposeScreen.tsx:1983`, `:2135`, `:1501`, `:1454-1471`.
- [x] **Offline send shows a raw network error; no offline send queue** — `P3` — `partial` (old 04/09 item) — the offline message (says offline, the text stays) is in a43b55ec. One verdict for the 04, 09 and 10 rows — fixed in 0ec809eb
- What WEB does: says offline and keeps the message (`components/email/email-composer.tsx:3661`). What RN does: `src/screens/ComposeScreen.tsx:2236`, `src/lib/send-failure-message.ts:57`, `src/stores/outbox-store.ts:53`.
- [x] **Scheduled time shows a minute early; presets (tomorrow 13:00, next Monday 08:00)** — `P3` — `bug`/`partial` — fixed in 8fe5798b
- What WEB does: `lib/scheduled-send-label.ts:11`, `lib/schedule-presets.ts:20`. What RN does: `src/screens/ScheduledScreen.tsx:53`, `:222`; `src/screens/ComposeScreen.tsx:2046`.
- [x] **Reply puts no cursor in the body; autofill offered in address fields** — `P3` — `missing` — fixed in 8bb7b607 (composer; ParticipantInput in the event editor is the calendar pass)
- What WEB does: `components/email/email-composer.tsx:2885`. What RN does: `src/screens/ComposeScreen.tsx:2466`, `:2316`, `:2946`, `src/components/calendar/ParticipantInput.tsx:119`.
- [ ] **Composer extras: priority (#970), numeric date placeholders (#1284), @mentions (#1134), directory lookup (#1253), font size, pasted lists, DSN/REQUIRETLS** — `P3` — `missing` — partly: priority and DSN/REQUIRETLS in 649d3adc, @mentions in 8389db8f, directory lookup in a7a37573; numeric date placeholders, font size and pasted lists still missing
- What WEB does: `lib/recipient-mentions.ts`, `components/email/plain-text-paste.ts`, `components/email/rich-text-editor.tsx:445`, `lib/jmap/client.ts:1392-1413`. What RN does: `src/lib/template-utils.ts:9-15`, `src/components/RichTextEditor.tsx:26`, `src/lib/editor-html.ts`.
- [x] **The recipient splitter treats an escaped quote as closing** — `P3` — `bug` — fixed in ae2eb46b
- What WEB does: `lib/email-composer-utils.ts:276`. What RN does: `src/lib/recipients.ts:57-90` (reached only from IdentitySettings).
### Calendar
- [x] **Calendar and task data from the previous account lands after a switch** — `P1` — `bug` — fixed in 1f190c94, e993540a, 41e8f005, 98dc13fe
- What WEB does: epoch check (`stores/calendar-store.ts:956`).
- What RN does: in-flight fetchEvents and `tasksInFlight` survive reset (`src/stores/calendar-store.ts:742`, `:750`, `:786`; reset at `src/stores/auth-store.ts:830`); a tick or delete then writes to the new account by the old one's id.
- [x] **Task reminders relative to the start use the due date (native #83, webmail #1328)** — `P2` — `bug` — fixed in 6f475c0c
- What WEB does: `lib/calendar-alerts.ts:343`. What RN does: `src/lib/calendar-alert-scheduler.ts:86`.
- [x] **Daily series stop at a DST gap** — `P2` — `bug` — fixed in 4de85692
- What WEB does: candidates from the start's wall clock (`lib/recurrence-expansion.ts:420`, 34fb9d247).
- What RN does: `src/lib/recurrence-expansion.ts:526-531`, `:396-398`, `:709`.
- [x] **Subscribed feed changes never reach imported events** — `P2` — `bug` — fixed in 8a71495e, 47f29718
- What WEB does: calendar-feed-diff (`stores/calendar-store.ts:2012`). What RN does: known UIDs skipped (`src/stores/calendar-subscriptions-store.ts:222`, `src/stores/calendar-store.ts:1158`).
- [x] **Invitation time in the wrong zone (#1252)** — `P2` — `bug` — fixed in 0fb7c8cd
- What WEB does: `lib/calendar-invitation.ts:701`. What RN does: `src/components/email/CalendarInvitationBanner.tsx:175`.
- [x] **Default reminders per calendar and email reminders (#1209)** — `P2` — `partial` — fixed in 9b87100a, b767df9f
- What WEB does: `components/settings/calendar-management-settings.tsx:184`, `components/calendar/event-modal.tsx:448`, `lib/calendar-alerts.ts:175`.
- What RN does: fires a calendar's defaults (`src/lib/calendar-alert-scheduler.ts:96`) but can't set them and new events don't start with them (`src/components/calendar/EventModal.tsx:266`).
- [x] **Search events** — `P2` — `missing` — fixed in b50270d8
- What WEB does: `components/calendar/calendar-search.tsx:82`. What RN does: `src/screens/CalendarScreen.tsx`.
- [x] **Zero-length events run to 23:59 (#837)** — `P3` — `bug` — fixed in acbd085a
- What RN does: `src/stores/calendar-store.ts:144`, `src/lib/calendar-utils.ts:116`. Fix: `duration || 'PT0S'`.
- [x] **.ics export breaks emoji and colon names** — `P3` — `bug` — fixed in f83284d7
- What RN does: `src/lib/calendar-ics-export.ts:9`, `:160`, `:167`. Fix: port foldLine and parameter quoting.
- [x] **Blank participant names are written (#748)** — `P3` — `bug` — fixed in 59c32782
- What WEB does: `lib/calendar-participants.ts:368`. What RN does: `src/lib/calendar-participants.ts:360`, `:385`, `:484`.
- [x] **Offer to save without invitations when the server refuses them** — `P3` — `partial` — fixed in 9b5692a8
- What WEB does: `lib/jmap/scheduling-error.ts:7`. What RN does: `src/components/calendar/EventModal.tsx:385-390`.
- [x] **Moving a task into or within a shared calendar (#1204)** — `P3` — `partial` — fixed in b6995bb6
- What WEB does: `lib/jmap/client.ts:9473`. What RN does: updateTask sends unmapped calendarIds (`src/stores/calendar-store.ts:1311-1315`).
- [x] **Working hours (#1164); week all-day area (#1122); light tinted colours; Jalali; birthday colour; copy title/append note; component set on new calendars (#760); date deep links; background reminder refresh without device sync** — `P3` — `missing`/`partial` — birthday colour fixed in 574db426, copy title/append note in baa3ff59 (duplicate and .ics export were there), week all-day area in 90ce7c1b (sized from the days in view, tasks under their day's events, three rows with +N), light tinted colours in 01578ac2 (Settings > Calendar "Light event colors"), date deep links in fe455370 (`/calendar/<view>/<date>`, a bare date keeps the view, `tasks` opens the task list); left: background reminder refresh without device sync: left, it needs native work: the push subscription would have to carry `CalendarEvent`/`Calendar` for every account (src/lib/push-notifications.ts), PushRouting.kt would have to start a headless JS task for a calendar-only push (today it starts nothing without device sync), and that task would need a reminder scheduler that runs without the restored session and stores the in-app one (src/lib/calendar-notifications.ts) depends on; Android only, and not testable without a device. While the app runs, reminders are rescheduled on every calendar change already; Jalali in f1297b6b; working hours: own feature, not in this run; component set (#760): needs a CalDAV client (05 row)
- What RN does: `src/components/calendar/WeekView.tsx`, `src/components/calendar/TimeGridScrollView.tsx:177-185`, `src/lib/birthday-calendar.ts:6`, `src/components/calendar/EventDetailSheet.tsx:422`, `src/api/calendar.ts:1150`, `src/navigation/linking.ts:151`, `:288-299`, `src/lib/push-notifications.ts:243`.
- [ ] **Free/busy, CalendarEventNotification, organizer from ParticipantIdentity (1.10.0)** — `P3` — `missing` — partly: free/busy in e6a89ad0, CalendarEventNotification in d663a5e2; the organizer from ParticipantIdentity is not ported
- What WEB does: `lib/jmap/client.ts:1198`.
### Contacts
- [x] **Contacts from the previous account land after a switch** — `P1` — `bug` — fixed in 6b9552fc
- What WEB does: epoch guard (`stores/contact-store.ts:597`). What RN does: `src/stores/contacts-store.ts:357`, `:373`; reset (`:873`) keeps contactsPromise.
- [x] **Contacts with a calendar, scheduling or free/busy link don't save** — `P2` — `bug` — fixed in 32f70c10
- What WEB does: maps them to `calendars`/`schedulingAddresses` (`lib/jmap/contact-wire.ts:63-87`).
- What RN does: writes `calendarUri`, `schedulingUri`, `freeBusyUri` as card properties (`src/lib/contact-form.ts:412-414`, `src/api/contacts.ts:97`); Stalwart refuses the card.
- [x] **vCard import sends fields Stalwart rejects** — `P2` — `bug` — fixed in 32f70c10, 3c156673
- What WEB does: `lib/jmap/contact-wire.ts`. What RN does: flat ADR and `source` (`src/lib/vcard.ts:652-672`, `:989-990`).
- [x] **Company directory in Contacts and the composer (#1253)** — `P2` — `missing` — fixed in 0fd14a83 (Contacts; the composer lookup is exported, not wired)
- What WEB does: `components/contacts/directory-browser.tsx:61`, `stores/contact-store.ts:924`. What RN does: `src/screens/ContactsScreen.tsx`, `src/stores/contacts-store.ts:728`; listPrincipals exists (`src/api/files.ts:593`).
- [x] **Deleting an address book that still holds contacts fails** — `P3` — `bug` — fixed in 32f70c10
- What WEB does: `onDestroyRemoveContents` (`lib/jmap/client.ts:6937`). What RN does: `src/api/contacts.ts:404`.
- [x] **vCard 4.0 export (#1407)** — `P3` — `bug` — fixed in 07fd03e2
- What RN does: dashes in dates, MOBILE instead of CELL (`src/lib/vcard.ts:21`, `:24`, `:1264`).
- [x] **Delete contact categories; address book sharing; list filters; deep links new/edit** — `P3` — `missing`/`partial` — category delete with Undo fixed in 65ffef71, list filters in 18c6b03f, address book sharing in 96b163a1 (Settings > Contacts, own books the user may share; one share sheet with the calendars), deep links new/edit in ecefd1cb (`/contacts/new?email=&name=`, `/contacts/<id>/edit`, the older `?contactId=`/`?addEmail=` form)
- What WEB does: `components/settings/address-book-management-settings.tsx:84`, `:435`, `components/contacts/contact-list.tsx:30`, `lib/deep-links.ts:394`. What RN does: `src/components/contacts/ContactsSidebarDrawer.tsx:144`, `src/components/settings/ContactsSettings.tsx:33`, `src/screens/ContactsScreen.tsx:163`, `src/navigation/linking.ts:155`.
### Files
- [x] **Files trash** — `P2` — `missing` — fixed in 7c914183 and be04fcb1..946304ab
- What WEB does: `lib/files-trash.ts:20`, `components/files/files-app.tsx:419`.
- What RN does: deletes for good (`src/api/files.ts:316`, `src/screens/FilesScreen.tsx:673`) and lists the webmail's `/.Trash` as an ordinary folder.
- [x] **Save an attachment to Files (incl. shared and group mail)** — `P2` — `missing` — fixed in 8e07b7d8, 39e82f99
- What WEB does: `components/email/save-to-files-dialog.tsx:43`, `lib/jmap/client.ts:9981`. What RN does: `src/components/email/AttachmentChips.tsx:183`; `src/api/files.ts:411` has createFileNodeFromBlob.
- [x] **Copy folders, numbered new folder names, next/previous in preview, starred and recent, deep links** — `P3` — `partial`/`missing` — copy folders fixed in a0f586f9; numbered new folder names: no webmail behaviour to port (its New folder dialog also takes the typed name, file-browser.tsx:1389); next/previous in preview in e3fc67e7; starred and recent in cad9296a (kept on the device per account like the webmail's localStorage, listed above the root's files, Star in a folder's actions); deep links in d684b00e (`/files/<path>`, `?preview=`, `?shared=`)
- What WEB does: `lib/jmap/client.ts:10155`, `stores/file-store.ts:640`, `lib/deep-links.ts:444`. What RN does: `src/api/files.ts:511-519`, `:328-345`, `src/components/files/FilePreviewModal.tsx`, `src/navigation/linking.ts:158`, `:307`.
- [ ] **Sharing fails on Stalwart before 0.16.6** — `P3` — `bug`
- What WEB does: LegacyFileNodeRights (`lib/jmap/client.ts:66-70`). What RN does: `src/components/files/ShareSheet.tsx:33-41`.
### Filters and vacation
- [x] **Filter scripts saved by webmail 1.13+ open as raw Sieve; reset wipes them (#1152)** — `P1` — `bug` — fixed in 2167a9d7, fc9c164d
- What WEB does: accepts metadata v1 and v2 (`lib/sieve/parser.ts:855`); writes v2 for rule periods, vacation forwarding or audience (`lib/jmap/sieve-types.ts:146`).
- What RN does: `if (metadata.version !== 1) return OPAQUE` (`src/lib/sieve/parser.ts:804`); "Reset to visual builder" (`src/components/settings/FilterSettings.tsx:290-318`) wipes the rules and forwarding.
- Fix hint: accept v2 and carry activeFrom/activeUntil, vacationForward, vacationAudience through parse and save.
- [x] **Webmail "is the address" / "has the domain" rules are broadened on a phone save** — `P1` — `bug` — fixed in 2167a9d7
- What WEB does: `address :is` / `address :domain :is` (`lib/sieve/generator.ts:106`, `lib/sieve/parser.ts:485`).
- What RN does: no such comparators (`src/lib/sieve/types.ts:27`); the generator's default branch writes `header :contains` (`src/lib/sieve/generator.ts:130-131`).
- [x] **"All messages" condition (#1154)** — `P2` — `missing` — fixed in 2167a9d7, df1a5ab4
- What WEB does: `lib/sieve/generator.ts:61`. What RN does: throws on save (`src/lib/sieve/generator.ts:113`), so every save fails once a web rule uses it.
- [x] **"Stop processing" after Delete silently or Reject (#1153)** — `P2` — `bug` — fixed in 2167a9d7
- What WEB does: `lib/sieve/generator.ts:487`. What RN does: skips `stop;` (`src/lib/sieve/generator.ts:396`).
- [x] **Rule names with double spaces get a duplicate (#1153)** — `P2` — `bug` — fixed in 2167a9d7
- What WEB does: collapses whitespace (`lib/sieve/generator.ts:455`, `lib/sieve/parser.ts:904`). What RN does: `src/lib/sieve/parser.ts:841`, `src/lib/sieve/generator.ts:39`.
- [x] **Out of Office forwarding, reply audience and rule periods (#1152)** — `P2` — `missing` — fixed in df1a5ab4
- What WEB does: `components/settings/vacation-settings.tsx:135`, `components/filters/filter-rule-modal.tsx:188`, `:299`, `lib/sieve/period.ts`. What RN does: `src/components/settings/VacationSettings.tsx:209`, `src/lib/sieve/types.ts:66-108`.
- [x] **Create rules from a message** — `P2` — `missing` — fixed in d546d509, 7b7236f0
- What WEB does: `components/email/rules-menu.tsx`, `lib/filters/quick-rules.ts:187`. What RN does: `src/components/email/ActionSheet.tsx`, `src/components/filters/FilterRuleModal.tsx`.
- [ ] **Forward limit (#1155, #1208); vacation length warning; run a rule on a folder (#1166); Sieve editor loses typed text (#1162); size value raw into Sieve; undo after deleting tags/rules/trusted senders** — `P3` — `missing`/`bug` — forward limit and vacation length fixed in dcbfc907, Sieve editor text in 6c3046c8, size value in 2167a9d7 (before this pass); undo after deleting rules (store side b95f2c29), tags and trusted senders, and the forward limit notes in the rule list, in 8ac19971; not done: run a rule on a folder, which needs a server-side run and is its own feature
- What WEB does: `lib/filters/forward-limit.ts:16`, `components/settings/vacation-settings.tsx:253`, `lib/filters/quick-rule-flow.ts:221`, `lib/sieve/generator.ts:63`. What RN does: `src/components/filters/FilterRuleModal.tsx:63`, `:413`, `src/components/settings/VacationSettings.tsx:106`, `src/components/filters/SieveEditorSheet.tsx:35-41`, `src/lib/sieve/generator.ts:69-74`, `src/components/settings/KeywordSettings.tsx:50-56`.
### Push, settings and core
- [x] **Push is renewed for the active account only; settings show only that account (#1054)** — `P2` — `bug` — fixed in d92a2ca8
- What WEB does: `components/push-notification-prompt.tsx:173`, `:219`, `components/settings/multi-account-push.tsx:33`.
- What RN does: resyncPushNotifications works on the active client (`App.tsx:671`, `:723`; `src/lib/push-notifications.ts:1592-1602`), so other accounts' subscriptions expire.
- [x] **Push with two logins that share a JMAP account id** — `P2` — `bug` — fixed in 9ccb6228, b352e470
- What WEB does: 07e6ecce1.
- What RN does: the iOS extension's credentials are keyed by JMAP id (`src/lib/ios-push-shared.ts:54`); a tap opens the first match (`src/lib/push-notifications.ts:1873`); the background task checks every login with that id (`src/lib/push-background-task.ts:201`).
- [x] **Writes other than sends are replayed after a dropped connection** — `P2` — `partial` — fixed in d57bdfaa
- What WEB does: retries read-only batches only (`lib/jmap/client.ts:245`).
- What RN does: blocks the retry only for EmailSubmission/set, Email/import, Blob/upload (`src/api/jmap-client.ts:102-109`, `:853-857`).
- [x] **Recover deleted items (#1319)** — `P3` — `missing` — fixed in 113e96bd (Settings page only; the admin day limit and hiding wait for the org policy and plug in at `src/lib/recovery/policy.ts`; the Trash folder's "Recover deleted items" link is not ported, the store's `checkRecent` is ready for it) — Trash link in 7467764e
- What WEB does: `x:ArchivedItem/query` (`lib/recovery/archived-items.ts:139`, `lib/jmap/client.ts:12119`, `components/settings/recover-deleted-settings.tsx`).
- What RN does: nothing (`src/screens/SettingsScreen.tsx:112`). The day limit and hiding need the policy.
- [x] **Delegates** — `P3` — `missing` — fixed in 8a5bb728 (Settings > Sharing; the admin hiding, rights limit and app gates wait for the org policy and plug in at `src/lib/delegation/policy.ts`)
- What WEB does: Mailbox/AddressBook/Calendar shareWith in one batch (`lib/delegation/delegation.ts:96`).
- What RN does: no mailbox sharing (`src/components/settings/FolderSettings.tsx`).
- [x] **Shared accounts treated as capable when their capabilities say otherwise (#1239)** — `P3` — `bug` — fixed in 67732348 (filters and auto-reply), 7a15c0c6 (contacts and files)
- What WEB does: `lib/jmap/client.ts:6431`. What RN does: `src/api/contacts.ts:76`, `src/api/files.ts:98`, `src/api/sieve.ts:43`, `src/api/vacation.ts:77`.
- [x] **Storage quota ignores softLimit (#1242)** — `P3` — `bug` — fixed in af86339b
- What WEB does: `lib/quota.ts:32`. What RN does: `src/api/quota.ts:50`.
- [ ] **Push extras: Inbox-only switch (#983), one ring per message (#1141), generic notification when the lookup fails (#1041), re-check on return (#1148)** — `P3` — `missing`/`partial` — Inbox-only done in 1821fda7, generic notification in f6e6f8f5, re-check on return in 327b2cae; not done: one ring per message (#1141) needs a change in the Android notification builder (BulwarkFcmModule.kt) and a device
- What RN does: `src/lib/notification-mutes.ts`, `src/lib/push-background-task.ts:468-488`, `:518`, `src/components/settings/NotificationSettings.tsx:115-134`.
- [ ] **Settings extras: language search, date locale override, font size everywhere, nav-bar icon style, sidebar apps never rendered, screenshot protection, Flat fields theme** — `P3` — `missing`/`partial` (old 08/09 items) — screenshot protection in 0ae33f3a; text size widened in c9b3bdfe
- What RN does: `src/components/settings/LanguageSettings.tsx:87`, `src/theme/dynamic.ts:48`, `src/lib/sidebar-apps.ts:10`, `src/theme/builtin-themes.ts:18`.
- [x] **Search snippets, size filter, mail folder sharing, global search (1.10.0)** — `P3` — `missing` — search snippets fixed in 1342e2a0, size filter in aac8a403 (live checks on 0.16 and 1.0 in 12b798c0); mail folder sharing in 417f1843 (the admin's share rights don't hide the write presets yet), global search in f15b7bb3 (other logins' file listing capped at about 500 items; the Pro search tab and saved searches not ported)
## Old open items (docs/parity 01-09, audit-2026-09)
Of 42 open boxes and "Missing vs webmail" items, 8 are done (84fef142 calendar UI strings, f62849d1 iOS push, 75299244 Tabler icons, 26a34d59 autocomplete groups, 5802cae contact deep link, status bar theming, birthday time zone, folder colours 548b647), 9 are no longer relevant (identity and template sync, settings sync, S/MIME is a plugin, #379 migration, stability banner, admin relay list, Android badge is iOS-only by design), and 25 are still open; those are folded into the rows above.
## Needs a decision
- Organisation policy on the phone: the app needs the webmail it was paired with and a webmail route that accepts a JMAP bearer token. Until then locked settings, session limits, external recipient confirm and label, the org signature and disclaimer, template locks and recovery limits can't be enforced on the phone.
- Webmail hand-off: replacing the password in the redirect with a one-time code needs the webmail side first.
+251
View File
@@ -0,0 +1,251 @@
# Webmail 1.19.0 and 1.19.1
## Summary
The last pass (file 10) covered webmail 1.10.0 to 1.18.0 and the commits after the 1.18.0 release up to early 2026-10-29 (b0d1a97bb..4aea0976a). This file walks the changelog sections 1.19.0 (2026-10-29) and 1.19.1 (2026-10-31) and every webmail commit since (`git log --since=2026-10-29`, 237 commits; `4aea0976a..4887ed457` gives 238, the extra one is the plugin page fix #1358). There is no Unreleased section; 1.19.1 is the latest release. Checked against native main at 7a42a15f. Web paths are relative to the webmail root, native paths to this repo.
Many 1.19.0 entries were in "Unreleased" when file 10 was written and already have rows there (organisation policy, delegates, recover deleted items, shared tags and team templates, Files trash). They are not repeated; the unticked file 10 rows they touch are listed at the end.
Counts: 42 rows, P1 0, P2 11, P3 31.
What stands out:
- A forged From still gets the real sender's favicon or contact photo. The webmail shows a warning shield in its place now.
- The 1.19.1 booking, parcel and order cards are missing. The webmail's `lib/structured-mail` is plain TypeScript and takes the HTML parser as a parameter, so it can be ported.
- The 1.19.0 accessibility work has little counterpart on the phone: form fields without names, toasts and Undo not read out on iOS, grey text at 2.56:1 in the light theme, a mail body fixed at 14 px, and a "Plain Text Only" switch that promises plain-text reading but doesn't change it.
- Calendar: a cancellation mail can't remove the meeting, and the event and task editors throw typed changes away.
Classification: `bug` = fixed in the webmail, still live in the app; `missing`; `partial`. Not applicable on mobile and left out (same list as file 10): admin panel, plugins and marketplace, command palette, keyboard shortcuts and keyboard navigation, print, Bulwark Lite, CSP/third-party switch as a server feature, Mountain View, desktop-only layout (drag and drop, hover, multi-pane), server routes the app doesn't use, Docker, docs, demo mode and translation-only entries.
## Findings
### Reading mail
- [x] **A forged From keeps the real sender's favicon and contact photo** — `P2` — `bug` — fixed in 0b0fc021
- What WEB does: when the sender check fails or is unverified, the avatar shows a warning shield instead of a photo, logo or initials, in the reader, the conversation and the list (`components/ui/avatar.tsx:323`, `hooks/use-sender-warning.ts:13`, `components/email/email-viewer.tsx:721`). The list fetch asks for `header:Authentication-Results:all` (`lib/jmap/client.ts:461`). 605a3983b (#1439).
- What RN does: MessageHeader works out the verdict (`src/components/email/MessageHeader.tsx:118`) but renders SenderAvatar with the claimed address (`:175`); the same in thread cards (`src/components/email/ThreadMessageCard.tsx:50`) and list rows (`src/screens/EmailListScreen.tsx:213`). SenderAvatar picks a contact photo or the domain favicon (`src/components/SenderAvatar.tsx:62-65`). List rows have no auth data (`src/api/email.ts:23-26`).
- Fix hint: pass a sender warning from getSenderVerification into SenderAvatar and draw a shield; for list rows add `header:Authentication-Results:all` to the list properties.
- [x] **No booking, parcel or order cards from schema.org markup** — `P2` — `missing` — fixed in 5f2ef4c1, a6066a09, cd677e77, 2055df8c, 982a30ef, c90a35c4, mounted in 90e4e230
- What WEB does: reads JSON-LD/microdata booking markup and attached Wallet passes and shows a card above the message (flight, train, bus, hotel, rental car, event, restaurant, parcel tracking, order status) with copy buttons and links that show where they lead. Bookings go into a calendar the user picks with stable UIDs, so a later mail updates the event and a cancellation removes it. Gate: the receiving server verified the From domain, not a forward, not a personal mailbox provider, not turned off by the admin (`lib/structured-mail/index.ts:36`, `trust.ts:104`, `pkpass.ts`, `calendar.ts:147`, `components/email/structured-mail/use-booking-calendar.ts:115`, `lib/structured-mail-gate.ts:7`). a529e6e57..859d368a1.
- What RN does: nothing; the only banners above the body are unsubscribe and invitation (`src/components/email/MessageContent.tsx:104`, `:112`).
- Fix hint: port `lib/structured-mail/` with an RN-side HTML parser passed through `parseHtml` (`lib/structured-mail/index.ts:4-5`, `:21`) and build RN cards. The admin switch needs the org policy, so default on.
- [x] **"Plain Text Only" says it also changes reading, but the reader ignores it** — `P2` — `bug` — fixed in 7dc45778
- What WEB does: reads HTML mail as plain text by default or per message (`lib/html-to-readable-text.ts:39`, `components/email/email-viewer.tsx`; 1e929b0a9).
- What RN does: the switch reads "Compose and read in plain text only" (`src/components/settings/ReadingSettings.tsx:208-210`), but only the composer and the placeholder check read plainTextMode (`src/lib/compose-html.ts:175`, `src/lib/placeholder-check.ts:157`); EmailBodyView never does (`src/components/EmailBodyView.tsx:529-537`).
- Fix hint: render the text part (or the HTML turned into text) when the setting is on, and add a per-message "Show as plain text".
- [x] **Mail body ignores the app's text size and iOS Dynamic Type** — `P2` — `missing` — fixed in 7b551fc3, a3650ebf
- What WEB does: bodies follow the chosen text size, with a separate mail text size and a minimum font size (`lib/mail-reading.ts:32`, `:42`; c07ba1a37).
- What RN does: the body is fixed at 14 px for HTML and plain text (`src/lib/email-html.ts:695`, `:979`); the app's text size scales only the app's own UI (`src/theme/dynamic.ts:48-69`). Android's WebView may follow the system font size (no textZoom set; unverified), iOS doesn't. Pinch zoom exists (`src/components/EmailBodyView.tsx:408`).
- Fix hint: scale the body's base font size by the app setting and `PixelRatio.getFontScale()`.
- [ ] **Reading aids: paused animated images, reduce motion in mail, reader view, read aloud, reading width** — `P3` — `missing` — reduce motion and paused animated images fixed in 6d701c4b; not done: reader view, read aloud and reading width are left to the OS
- What WEB does: animations in mail stop with reduce motion (24a13cc1f), animated images pause behind a play button (bb7869798), plus reader view, read aloud, reading mask and width (caed2d313, af3019ea2, a982c3f23, 8cc0f8f25, dcc0d600f).
- What RN does: none. The app reads reduce motion (`src/theme/dynamic.ts:92`) but doesn't pass it into mail.
- Fix hint: pass reduce motion into the body CSS (animation off, GIFs paused) first; read aloud and reader view are left to the OS.
- [ ] **No BIMI sender logos or verified-mark check** — `P3` — `missing` — partly: BIMI logos from the webmail in 83ddd137 (list, unified inbox and reader in 6b7e752c); no check mark for a verified mark
- What WEB does: when DMARC passes for the exact From domain the avatar shows the domain's BIMI logo before the favicon, with a check mark when a VMC/CMC backs it; DNS and certificate checks run on the webmail server (`lib/email-headers.ts:175`, `components/ui/avatar.tsx:292`, `app/api/bimi/route.ts:49`). cf0eb03c4.
- What RN does: contact photo, DuckDuckGo favicon, initials (`src/components/SenderAvatar.tsx:38`, `:62-65`; `src/lib/avatar-utils.ts:149`).
- Fix hint: use the paired webmail's `/api/bimi` once the app keeps the webmail URL (same blocker as the org policy, file 10).
- [x] **A DKIM pass reported only by `header.i` doesn't count (#1420)** — `P3` — `bug` — fixed in 49d43729
- What WEB does: takes the signer domain from `header.i` when `header.d` is missing (`lib/email-headers.ts:282`, `:346`). f8f2e93cd.
- What RN does: reads `header.d` only (`src/lib/email-headers.ts:397`); such mail shows as unverified and its invitations lose trust.
- Fix hint: port identityDomain.
- [x] **Reply and quick reply are offered on drafts and scheduled messages in a conversation** — `P3` — `bug` — fixed in 35297379
- What WEB does: hides Reply and Reply all on a conversation's drafts and scheduled messages (`components/email/email-viewer.tsx:881`). 8b29254a6 (#1404).
- What RN does: every card is replyable (`src/screens/EmailThreadScreen.tsx:1212`, `src/components/email/ThreadMessageCard.tsx:68-85`); quick reply targets the last member, which can be the user's own draft (`src/screens/EmailThreadScreen.tsx:1124`).
- Fix hint: `replyable={false}` for `$draft` and scheduled members; point quick reply at the newest message that is neither.
- [x] **Bounce and receipt report parts show as list chips and go along on forward** — `P3` — `bug` — fixed in 7772da84 (list chips, global- types), 482e9a20 (forward, compose)
- What WEB does: leaves `message/delivery-status`, `message/disposition-notification` and their `global-` forms out of list chips and forwards (`lib/attachment-visibility.ts:73`, `components/email/email-composer.tsx:985`). 8a09ba573.
- What RN does: the reader hides them (`src/lib/attachment-display.ts:56`, `:130`), list chips and forwards don't (`src/lib/list-attachments.ts:21`, `src/lib/reply-compose.ts:14-19`); isReportPart misses the `global-` types.
- Fix hint: filter isReportPart in both places and add the two global types.
- [x] **Attachment icons in the reader aren't coloured by file type** — `P3` — `partial` — fixed in 4849cf23
- What WEB does: reader chips use the list's per-type icon and colour (`components/email/attachment-chips.tsx`). a78d03a18.
- What RN does: list chips are typed (`src/components/email/ListAttachmentChips.tsx:96`), reader chips use a plain paperclip (`src/components/email/AttachmentChips.tsx:244`).
- Fix hint: reuse attachmentKind in AttachmentChips.
- [x] **Avatar initials are always white, even on light colours** — `P3` — `bug` — fixed in 6f66fa06
- What WEB does: picks black or white initials by contrast (`components/ui/avatar.tsx:174-182`; 2e13accad).
- What RN does: always `#ffffff` (`src/components/SenderAvatar.tsx:130-133`); several palette colours give 3.1 to 3.7:1 (`src/lib/avatar-utils.ts:19-20`, `:47-48`).
- Fix hint: port initialsColor.
### Mail list and search
- [x] **Search results can't be moved into the open folder (#1452)** — `P3` — `bug` — fixed in 9a4515f9
- What WEB does: a search across folders keeps the open folder selected, and hits from other folders can move into it (`lib/move-targets.ts:71`). dafdda534.
- What RN does: the move sheet greys out the current folder (`src/components/MoveSheet.tsx:110-115`) and the single-row pick returns on it (`src/screens/EmailListScreen.tsx:1904-1918`), even for rows from other folders.
- Fix hint: in search mode mark a folder current only when the rows are already in it.
- [x] **No Undo for read/unread, star and tag changes** — `P3` — `missing` — fixed in da1da0ec
- What WEB does: the toast offers Undo and puts back each message's previous keywords (`lib/email-action-toast.ts:86`). ee17c0dba.
- What RN does: UndoEntry covers archive, delete, move and spam only (`src/stores/email-store.ts:322`).
- Fix hint: a `keywords` undo kind with the original keywords per item.
- [x] **No Sort menu in the list header** — `P3` — `partial` — fixed in 1cbe28b1, beff8744
- What WEB does: Sort menu with the order presets in the list header (`components/email/mail-list-menus.tsx`). e3f1ccce7.
- What RN does: the header only flips the direction (`src/screens/EmailListScreen.tsx:1193`); presets live in Settings > Layout (`src/components/settings/LayoutSettings.tsx:88-104`).
- Fix hint: a sort button that opens the presets.
- [x] **Tag counts don't use Keyword/get where the server counts keywords (#1399)** — `P3` — `missing` — fixed in eb6bf6d3
- What WEB does: one Keyword/get when the capability has `supportsCounts`, else two Email/query per tag (`lib/jmap/client.ts:2721`, `:2737`).
- What RN does: always two queries per tag (`src/api/tag-counts.ts:22-40`).
- Fix hint: same check and fallback. Stalwart doesn't advertise counts, so low value.
- [x] **The folder list doesn't say which folder is open, and the unread count reads "Show unread"** — `P3` — `bug` — fixed in e3a34eb1
- What WEB does: says which folder is open and how many are unread (7c282f1f4, 80a2a212a).
- What RN does: folder rows have no selected state (`src/components/SidebarDrawer.tsx:170-180`); the count is a button labelled "Show unread" (`:126-133`).
- Fix hint: `accessibilityState.selected` and a label like "Inbox, 3 unread".
### Composer and sending
- [x] **Quick reply skips the placeholder, forgotten-attachment and confirm checks** — `P3` — `bug` — fixed in db8eb731
- What WEB does: quick reply runs the composer's checks (`lib/outgoing/quick-reply.ts:34`). 534049952.
- What RN does: the composer has them (`src/screens/ComposeScreen.tsx:2078`, `:2137`), QuickReplyBox sends straight away (`src/components/email/QuickReplyBox.tsx:78-140`).
- Fix hint: run unfilledPlaceholdersInMessage and the attachment keyword check before send.
- [x] **Quoted reply history isn't collapsed in the composer** — `P3` — `missing` — fixed in 9fd80993
- What WEB does: collapses the quote behind an ellipsis button (`components/email/reply-quote-collapse.ts:13`). 9e0089604 (#1405).
- What RN does: seeds the full quote into the editor (`src/screens/ComposeScreen.tsx:562-615`).
- Fix hint: hide the quoted block in the editor behind a toggle, keep it in the sent body.
- [x] **Tag picker can't make a tag; template picker can't open the template manager** — `P3` — `missing` — fixed in e09cd395 (new tag), 2c1f266f (template manager, compose)
- What WEB does: "New tag" in the tag picker (`components/email/tag-picker.tsx:214`), "Manage" in the template picker (`components/templates/template-picker.tsx:227`). 19676a14f.
- What RN does: tag sheets list existing tags only (`src/screens/EmailThreadScreen.tsx:1489`, `src/components/TagSheet.tsx`); the template sheet shows "No templates yet" with no way on (`src/components/TemplateSheet.tsx:148-150`).
- Fix hint: a "New tag" row and a "Manage templates" link.
- [x] **No optional confirm before sending, deleting or archiving** — `P3` — `missing` — fixed in db8eb731 (send, composer and quick reply), 9794dc03 (delete and archive)
- What WEB does: `sendConfirmation`, `confirmDelete`, `confirmArchive` (`components/email/email-composer.tsx:501`, `lib/mail-removal-confirm.ts:24`, `lib/accessibility-settings.ts:93-94`). 4d91d9b29, 374ae9c8d.
- What RN does: asks only before a permanent delete (`src/lib/delete-confirm.ts:27`).
- Fix hint: three settings and an Alert in the send, delete and archive paths, swipes and quick reply included.
- [ ] **No alt-text prompt for images, no default compose font** — `P3` — `missing` — alt-text prompt fixed in 303dea01; not done: default compose font and size, which needs a font setting with switches in the settings pages
- What WEB does: asks for alt text when an image goes in (`components/email/image-alt-dialog.tsx`; 53cb49d3c), starts new mail in a chosen font and size (1b24c1980), offers an accessible example template (420bf8f9a).
- What RN does: alt is the file name (`src/screens/ComposeScreen.tsx:1857`, `src/lib/editor-html.ts:413-419`); the editor font is fixed (`src/lib/editor-html.ts:160`).
- Fix hint: the alt prompt first.
### Accessibility
- [x] **No Accessibility page or presets** — `P2` — `missing` — fixed in c9b3bdfe
- What WEB does: Settings > Accessibility with presets (screen reader, low vision, dyslexia, focus, motor, motion, hearing) and text size 87.5% to 200% (`components/settings/accessibility-settings.tsx`, `lib/accessibility-presets.ts:23`, `lib/accessibility-settings.ts:13-22`; 8008393e5, 0731d24f6).
- What RN does: no page (`src/screens/SettingsScreen.tsx:150-174`); text size 0.92/1/1.12 (`src/theme/dynamic.ts:14-18`). Text follows the system font scale; reduce motion follows the system (`src/theme/dynamic.ts:88-120`).
- Fix hint: a page that groups the existing switches, widens the text size range and takes the presets that make sense on a phone.
- [x] **Form fields have no name for screen readers** — `P2` — `bug` — fixed in ff5a8c91 (settings and sign-in), 98b962a8 (composer, quick reply, folder name prompt, template search), 3c3274e4 (mail search, filter panel, unified search), 7ba00be6 (contact form, event editor, calendar, contacts and files sheets, auto-reply)
- What WEB does: every field has a label tied to it (`components/ui/field.tsx:42`; ef13556a0, 0e2a05e0e, dcc21acd0, 4e2affe40, f73643bbb).
- What RN does: the shared Input draws its label as separate Text (`src/components/Input.tsx:40-55`); the Security password and code fields have no label at all (`src/components/settings/AccountSecuritySettings.tsx:181`, `:191`, `:198`); contact fields rely on placeholders (`src/screens/ContactFormScreen.tsx:823-834`); the reminder field's only name is "30" (`src/components/calendar/EventModal.tsx:772`, `:817`).
- Fix hint: Input passes its label as accessibilityLabel; every bare TextInput gets one.
- [x] **Toasts and Undo are not read out on iOS; the Undo bar isn't read out anywhere** — `P2` — `bug` — fixed in d812caf0, 01dd7461
- What WEB does: live regions and announcements for toasts, loads, new mail, saved drafts and offline (`lib/announce.ts:124`, `components/ui/toast.tsx:471`; 36d13b76b, e2fc2c750, 23813be9b, d713d31af, 4c1f24947).
- What RN does: no `announceForAccessibility` call in src; ToastHost and OfflineBanner use accessibilityLiveRegion, which only Android honours (`src/components/ToastHost.tsx:194`, `src/components/OfflineBanner.tsx:33`); the Undo bar has neither (`src/components/UndoSnackbar.tsx:129-160`).
- Fix hint: announce through AccessibilityInfo on iOS, live region on the Undo bar.
- [x] **Grey text is below 4.5:1 contrast** — `P2` — `bug` — fixed in 11522995
- What WEB does: every built-in theme at 4.5:1 or more, with a contrast test (`lib/builtin-themes.ts:16`, `lib/__tests__/theme-contrast.test.ts`; 9f6c7b99c).
- What RN does: textMuted is `#94a3b8` on white in the light theme (2.56:1) and `#737373` in dark (4.18:1 on the background, 3.19:1 on surfaces) (`src/theme/tokens.ts:236`, `:140`); used in 456 places, also as placeholder colour.
- Fix hint: adjust both, add a contrast test over tokens.ts and builtin-themes.ts.
- [x] **The calendar grid reads poorly with a screen reader** — `P2` — `bug` — fixed in 294023e2
- What WEB does: events sit inside their day cell and day cells have full date names (0026abba4, 19e0acde9).
- What RN does: month day cells read only the number and the dots are silent (`src/components/calendar/MonthView.tsx:199-240`); event blocks have no role, label or date (`src/components/calendar/EventBlock.tsx:72-104`).
- Fix hint: label day cells with date and event count; event blocks get a button role and a label with title, date and time.
- [ ] **No reading fonts, text spacing, more contrast or underlined links** — `P3` — `missing` — partly: reading fonts in 4724c28f (iOS in cfca6b9c, untested on a device); text spacing, more contrast and underlined links not done
- What WEB does: Atkinson Hyperlegible Next, Lexend, OpenDyslexic, spacing settings, more contrast, underlined links (`lib/accessibility-settings.ts:53-68`; 8008393e5, da215b8c0).
- What RN does: none of these settings (`src/stores/settings-store.ts`).
- Fix hint: the dyslexia font and line spacing first.
- [ ] **No colour-blind palette and no names next to colours** — `P3` — `missing` — partly: colour-blind-safe palette for tags in c6501ec8 and calendars in 21dd5a24; no names next to colours
- What WEB does: a colour-blind-safe palette, a tag's letter in its dot, the calendar's name on events (cf4d02b49, d5d7d39ef, 396290003).
- What RN does: colours only (`src/components/calendar/EventBlock.tsx:72-104`, `src/components/calendar/MonthView.tsx:233-240`).
- [x] **Toast and Undo times are fixed and short** — `P3` — `missing` — fixed in c9b3bdfe
- What WEB does: one duration setting, 5 to 60 s or until closed (`lib/toast-duration.ts:7-15`; 8d2ebccf0, be5e2dbc5).
- What RN does: 5 s, errors 10 s (`src/stores/toast-store.ts:44`), Undo 5 s (`src/components/UndoSnackbar.tsx:15`).
- Fix hint: a duration setting and a longer default while a screen reader is on.
- [ ] **No plain language and no explanations for technical terms** — `P3` — `missing` — partly: plain language in 97b71e22; no explanations for technical terms
- What WEB does: simpler English and German wording (`i18n/plain-language.ts`; bb739c744, 9861af6be, 5ffecf692) and term explanations (`components/ui/term.tsx`; 724c45d6e).
- What RN does: none (`src/i18n/index.ts:79-140`).
- [x] **Accessibility statement, help and sign-language links aren't shown** — `P3` — `missing` — fixed in 9d977773
- What WEB does: shows the operator's links from the public `/api/config` (`app/api/config/route.ts:104`; 315e29270, 623c9e179).
- What RN does: About has only the app's own links (`src/lib/about-links.ts:5-28`); the webmail URL isn't kept (`src/lib/oauth-native.ts:93-108`).
- Fix hint: keep the paired webmail URL (also needed for the org policy) and read `/api/config`.
### Accounts and sign-in
- [x] **The two-factor prompt shows as a red error (#1422)** — `P3` — `bug` — fixed in b205c13a
- What WEB does: an info box with role status, no shake (`app/(main)/[locale]/login/page.tsx:1393`; 141190708).
- What RN does: TotpRequiredError sets a notice (`src/screens/LoginScreen.tsx:389-391`) shown in the error tone (`src/screens/login/PasswordStep.tsx:151`, `src/screens/login/LoginNotice.tsx:15-24`).
- Fix hint: `tone="info"` for the TOTP step.
- [x] **Two accounts with the same name look alike in the switcher** — `P3` — `bug` — fixed in 505fbc59 (Settings > Account and the shared accountRowText) and 97173d4a (the switcher rows in SidebarDrawer); the port waits for question 9
- What WEB does: the address becomes the title when another account shares the name (`lib/account-utils.ts:159`; 3bb9f7df8).
- What RN does: name as title, address below (`src/components/SidebarDrawer.tsx:884-891`, `src/components/settings/AccountSettings.tsx:186`). Report question 9 (port in the label) is the same area.
- Fix hint: port accountRowText.
- [ ] **An expired session drops the open composer without saving** — `P3` — `partial`
- What WEB does: open composers save their drafts before the session ends and the login page says if one couldn't be saved (`stores/auth-store.ts` saveDraftsBeforeExpiry; 842c369fa).
- What RN does: on a dead refresh the account is evicted with no draft flush (`src/stores/auth-store.ts:1138-1153`, `:1170-1180`). Read from the code, not tried on a device.
- Fix hint: flush the open composer's autosave before eviction.
### Settings
- [x] **The density sample shows previews while "Show preview text" is off** — `P3` — `bug` — fixed in c8f417dd
- What WEB does: c166651d1 (`components/settings/appearance-settings.tsx:14-16`).
- What RN does: showPreview comes from the density only (`src/components/settings/AppearanceSettings.tsx:11-15`, `:24-27`).
- [x] **The identity editor closes without asking about unsaved changes** — `P3` — `bug` — fixed in 7ecac4ab
- What WEB does: identity, rule and Sieve editors ask (`components/settings/use-discard-changes.tsx`; 9d3ae2203).
- What RN does: Close, Cancel and Back call closeEditor (`src/components/settings/IdentitySettings.tsx:122`, `:304`, `:311`, `:389`). FilterRuleModal already asks.
### Calendar
- [x] **A cancellation mail can't remove the meeting from the calendar (#1425)** — `P2` — `missing` — fixed in a19afe9b
- What WEB does: offers "Remove from calendar" for a trusted cancellation from the organizer, for the series or one occurrence, and says when it's outdated (`components/email/calendar-invitation-banner.tsx:627-635`, `:831`, `:1229`, `lib/calendar-cancellation.ts:49`; 357b6af3c).
- What RN does: only "This event was cancelled" (`src/components/email/CalendarInvitationBanner.tsx:201`, `:270-272`); it already looks up the server copy (`:122`).
- Fix hint: port planCancellationRemoval behind the banner's trust check.
- [x] **The event and task editors throw away typed changes on Close or Back** — `P2` — `bug` — fixed in 10e9f81d
- What WEB does: asks before closing with unsaved changes (`components/calendar/event-modal.tsx:530-547`, `components/calendar/task-modal.tsx`; 13f9f27c4).
- What RN does: close and Back call onClose (`src/components/calendar/EventModal.tsx:495`, `:500`); the task editor resets on the next open (`src/components/calendar/TasksSheet.tsx:135-146`, `:238-253`).
- Fix hint: compare with the initial form and ask, as ContactFormScreen does (`src/screens/ContactFormScreen.tsx:639`).
- [x] **A shared calendar's server colour is replaced by a random one (#1414)** — `P3` — `bug` — fixed in 64eb97f4
- What WEB does: auto-colours only shared calendars with neither a server colour nor an override (`lib/shared-calendar-colors.ts:79`; 2dbe5d024).
- What RN does: colours every shared calendar without an override (`src/screens/CalendarScreen.tsx:315-333`).
- Fix hint: skip calendars with `cal.color`.
### Notifications
- [x] **New mail can't play the Involved sound** — `P3` — `missing` — fixed in 6a27c596 (calendar reminders in b4f10829)
- What WEB does: Involved by default (`stores/settings-store.ts:949`, `lib/notification-sound.ts:11`). 5eabdd890.
- What RN does: the OS channel owns the sound (`src/stores/settings-store.ts:266-267`).
- Fix hint: optional; needs a new Android channel id and a bundled iOS sound.
### i18n
- [x] **No Thai or Estonian, and the vendored catalogs are stale** — `P3` — `missing` — fixed in d23af55e, e8bc54eb
- What WEB does: 29 languages, Thai new in 1.19.0 (#1379, be52744a5; `i18n/routing.ts:15`).
- What RN does: 27 (`src/i18n/index.ts:25-53`); `locales/et/common.json` is vendored but not registered; no `th`. German lacks 1283 of the webmail's keys (last sync 7c914183).
- Fix hint: `node scripts/sync-locales.mjs`, register th and et, add overlays.
- [x] **Some German phone strings still say "du"** — `P3` — `bug` — fixed in 25f9b810
- What WEB does: formal everywhere (7b720dd22).
- What RN does: 12 overlay strings (`locales/rn/de.json`, e.g. settings.privacy.*, settings.backup.description); 18 more in the stale vendored catalog, which the sync fixes.
- Fix hint: rewrite with "Sie".
## Already done in native
Return to the list after archive/move/spam (#1458), shared mailbox identity (#1450), Bulwark Mobile client (#1459, 7a42a15f), security settings after a TOTP sign-in (#1432), unique default alert ids (#1435), keep-alive while push is live (#1401), vCard colons and 4.0 export (#1438 9d547b9d, #1407), chip moves between To/Cc/Bcc, placeholder warning, send waits for autosave (#1394), clearing filters keeps the folder (#1430), translated folder names in search (#1429), invitation passing DMARC (#1403), attachment download errors (#1426, #1428), partly failed bulk delete (#1384), .eml preview attachments (#1395), Files move/zip/delete undo, Contacts move and long-press menus, no Edit/Delete on read-only events, reduce motion. Details with file:line are in the inventory drafts summarised in docs/native-sync-2026-11.md.
## Unticked file 10 rows touched by 1.19
Schedule presets (1.19.0 adds "This morning"/"This afternoon", #1390), undo after a cross-account move and in the unified inbox, search snippets and size filter, fixed-width tables (#1020), recover deleted items (#1319), delegates, external sender chip, external recipient confirm, org signature and disclaimer, org policy and session limits (the last five blocked on the policy route).
## Needs a decision
- BIMI and the operator's help links need the app to keep the paired webmail's URL, the same thing the org policy needs.
+3 -3
View File
@@ -364,7 +364,7 @@ carries the commit that fixed it.
- [x] **U-044 · P2 · PP (German, 1.3) · The toolbar overflows and pushes More off screen.** *(fixed in 975ed08)*
- "Stern hinzufügen" is long (`d_de_thread`).
- Fix: drop the toolbar labels when they don't fit; keep the a11y labels.
- [ ] **U-045 · P3 · German · Prev/Next read "Zurück"/"Weiter".** *(deferred: the strings come from the vendored webmail catalog (locales/de/common.json); the fix belongs in the webmail's German translation)*
- [x] **U-045 · P3 · German · Prev/Next read "Zurück"/"Weiter".** *(deferred: the strings come from the vendored webmail catalog (locales/de/common.json); the fix belongs in the webmail's German translation)* — fixed in 25f9b810
- "Zurück" reads as Back. "Allen antworten" wraps in the bottom bar.
- Fix: "Vorherige"/"Nächste" (locale), and a single line with shrinking text.
- [x] **U-046 · P2 · all · The tag sheet doesn't scroll.** *(fixed in b59d00f)*
@@ -407,7 +407,7 @@ carries the commit that fixed it.
with the usual "-- " delimiter and the composer adds its own separator above it (screenshot
`t_compose_panel`).
- Fix: don't add a separator to a signature that already starts with one.
- [ ] **U-069 · P2 · PL · Recipient suggestions can't show in phone landscape.** *(deferred: with the keyboard up a phone in landscape has about 100 dp left; suggestions need a different design (a full-screen picker), which is a feature)* With the keyboard
- [x] **U-069 · P2 · PL · Recipient suggestions can't show in phone landscape.** *(deferred: with the keyboard up a phone in landscape has about 100 dp left; suggestions need a different design (a full-screen picker), which is a feature)* With the keyboard — fixed in e1ad5c09
up, about 50 dp are left under the To field, so the suggestion list is hidden behind the
keyboard. Typing a full address still works.
### Calendar
@@ -510,7 +510,7 @@ carries the commit that fixed it.
- [x] **U-132 · P2 · all · The password step's return keys don't move on.** *(fixed in ae1ef5b)*
- Username has no `next`. With TOTP on, the password field shows Next and does nothing.
- Where: `PasswordStep.tsx:56,78`.
- [ ] **U-133 · P2 · all · There is no way out while "Signing in" hangs.** *(deferred: a cancel needs an abortable sign-in in the auth flow, which is more than a UI fix; see product questions)* Hardware back is
- [x] **U-133 · P2 · all · There is no way out while "Signing in" hangs.** — fixed in 55d008a2 *(deferred: a cancel needs an abortable sign-in in the auth flow, which is more than a UI fix; see product questions)* Hardware back is
swallowed. Where: `SigningInStep.tsx`, `LoginScreen.tsx:165`.
### Found in the re-walk
+6
View File
@@ -1,3 +1,9 @@
// First: wraps react-native's Text for the reading font before any module
// copies it (src/theme/reading-font-text).
import './src/theme/reading-font-install';
// WebCrypto for PGP and S/MIME (#58), before openpgp or pkijs can load.
// Loads nothing native until first used.
import './src/lib/crypto/install-webcrypto';
import { createElement } from 'react';
import { AppRegistry } from 'react-native';
import { registerRootComponent } from 'expo';
+1745 -203
View File
File diff suppressed because it is too large Load Diff
+1746 -204
View File
File diff suppressed because it is too large Load Diff
+1743 -201
View File
File diff suppressed because it is too large Load Diff
+1753 -211
View File
File diff suppressed because it is too large Load Diff
+1763 -221
View File
File diff suppressed because it is too large Load Diff
+785
View File
@@ -0,0 +1,785 @@
{
"login": {
"jmap_endpoint_cors_hint": "Der Mailserver muss Anmeldungen von dieser Website erlauben. Ihre Administration kann das einrichten.",
"reconnecting": "Die Verbindung ist weg. Wir versuchen, wieder zu verbinden…",
"error": {
"invalid_credentials": "Die E-Mail-Adresse oder das Passwort ist falsch. Prüfen Sie beides und versuchen Sie es noch einmal.",
"invalid_token": "Der Server hat dieses Zugriffstoken nicht angenommen. Vielleicht ist es nicht vollständig oder nicht mehr gültig. Kopieren Sie es noch einmal oder erstellen Sie ein neues.",
"connection_failed": "Wir erreichen den Mailserver nicht. Prüfen Sie Ihre Internetverbindung und versuchen Sie es noch einmal.",
"cors_blocked": "Der Mailserver läuft, erlaubt aber keine Anmeldung von dieser Website. Bitten Sie Ihre Administration, diese Website auf dem Server zu erlauben.",
"server_error": "Der Mailserver hat gerade ein Problem. Versuchen Sie es in ein paar Minuten noch einmal.",
"generic": "Etwas ist schiefgelaufen. Versuchen Sie es noch einmal. Wenn das öfter passiert, fragen Sie Ihre Administration.",
"totp_required": "Ihr Konto braucht zur Anmeldung einen zweiten Code. Öffnen Sie Ihre Authenticator-App und geben Sie unten den 6-stelligen Code ein.",
"totp_invalid": "Dieser Code ist falsch oder zu alt. Geben Sie den Code ein, den Ihre Authenticator-App gerade anzeigt.",
"oauth_discovery_failed": "Die Anmeldung über Ihre Organisation funktioniert gerade nicht. Versuchen Sie es später noch einmal oder fragen Sie Ihre Administration.",
"token_exchange_failed": "Passwort und Code sind richtig, aber der Mailserver hat diese App nicht hereingelassen. Bitten Sie Ihre Administration, die Anmelde-Einstellungen für diese App zu prüfen."
},
"totp_toggle": "Ich habe einen Code aus meiner Authenticator-App",
"token_hint": "Manche Mailanbieter geben Ihnen ein Token statt eines Passworts. Erstellen Sie es in den Einstellungen Ihres Mailanbieters, kopieren Sie es und fügen Sie es hier ein.",
"config_error": {
"title": "Das Webmail ist nicht richtig eingerichtet",
"fetch_failed": "Das Webmail konnte seine Einstellungen nicht laden. Versuchen Sie es in ein paar Minuten noch einmal.",
"server_not_configured": "Es ist noch kein Mailserver eingerichtet. Bitten Sie Ihre Administration, einen einzurichten."
},
"session_expired": "Sie wurden abgemeldet. Melden Sie sich wieder an, um weiterzumachen.",
"oauth_error": {
"title": "Die Anmeldung hat nicht geklappt",
"invalid_state": "Die Anmeldung wurde unterbrochen. Fangen Sie noch einmal an.",
"missing_params": "Es fehlen Anmeldedaten. Fangen Sie noch einmal an.",
"token_exchange_failed": "Wir konnten die Anmeldung nicht abschließen. Versuchen Sie es noch einmal.",
"access_denied": "Sie dürfen sich hier nicht anmelden. Bitten Sie Ihre Administration um Zugang."
}
},
"session_limits": {
"idle_message": "Sie haben eine Weile nichts getan. In {seconds, plural, one {# Sekunde} other {# Sekunden}} werden Sie abgemeldet. E-Mails, die Sie gerade schreiben, werden als Entwurf gespeichert.",
"max_message": "Ihre Organisation meldet Sie {hours, plural, one {# Stunde} other {# Stunden}} nach der Anmeldung ab. In {seconds, plural, one {# Sekunde} other {# Sekunden}} werden Sie abgemeldet. E-Mails, die Sie gerade schreiben, werden als Entwurf gespeichert.",
"reason_max": "Sie wurden abgemeldet, weil Sie {hours, plural, one {# Stunde} other {# Stunden}} angemeldet waren. Länger erlaubt es Ihre Organisation nicht.",
"drafts_not_saved": "Einige E-Mails, die Sie geschrieben haben, konnten nicht als Entwurf gespeichert werden."
},
"totp_reauth": {
"title": "Neuen Code eingeben",
"description": "Ihr letzter Code gilt nicht mehr. Öffnen Sie Ihre Authenticator-App und geben Sie den neuen Code ein, um weiterzumachen.",
"oauth_hint": "Diese Abfrage kommt immer wieder. Ihre Administration kann das abstellen, indem sie eine andere Anmeldeart (OAuth) einschaltet.",
"code_label": "Code aus Ihrer Authenticator-App"
},
"email_list": {
"permanent_delete_confirm_message": "Diese E-Mail wird endgültig gelöscht. Sie können sie nicht zurückholen.",
"permanent_delete_confirm_batch_message": "{count, plural, one {Diese E-Mail wird} other {Diese # E-Mails werden}} endgültig gelöscht. Sie können sie nicht zurückholen.",
"empty_folder": {
"confirm_message": "Alle E-Mails in diesem Ordner werden endgültig gelöscht. Sie können sie nicht zurückholen."
}
},
"email_viewer": {
"read_receipt": {
"send_failed": "Die Lesebestätigung konnte nicht gesendet werden"
},
"archive_mailbox_not_found": "Es gibt keinen Archiv-Ordner, deshalb kann die E-Mail nicht archiviert werden",
"source_load_failed": "Die Rohdaten der E-Mail konnten nicht geladen werden. Versuchen Sie es noch einmal oder speichern Sie die E-Mail als Datei (.eml).",
"spam": {
"error": "Die E-Mail konnte nicht als Spam markiert werden. Versuchen Sie es noch einmal.",
"error_not_spam": "Die E-Mail konnte nicht zurückgeholt werden. Versuchen Sie es noch einmal."
},
"unsubscribe_banner": {
"error": "Das Abbestellen hat nicht geklappt. Versuchen Sie es später noch einmal."
},
"calendar_invitation": {
"parse_error": "Diese Einladung kann nicht gelesen werden",
"action_failed": "Das hat in Ihrem Kalender nicht geklappt. Versuchen Sie es noch einmal."
},
"undo_send_failed": "Das Senden konnte nicht gestoppt werden. Die E-Mail ist vielleicht schon gesendet.",
"undo_send_signed_out": "Das Senden konnte nicht gestoppt werden, weil Sie bei dem Konto, das sie sendet, nicht mehr angemeldet sind.",
"send_cancelled_restore_failed": "Das Senden wurde gestoppt. Die E-Mail konnte aber nicht zurück in „Entwürfe“ gelegt werden. Sie liegt noch in „Gesendet“.",
"send_cancelled_remove_failed": "Das Senden wurde gestoppt. Die E-Mail konnte aber nicht aus „Gesendet“ entfernt werden.",
"cancel_scheduled_failed": "Das geplante Senden konnte nicht gestoppt werden. Versuchen Sie es noch einmal.",
"save_to_files": {
"failed": "Die Datei konnte nicht in „Dateien“ gespeichert werden. Versuchen Sie es noch einmal."
}
},
"email_composer": {
"files_picker": {
"load_failed": "Ihre Dateien konnten nicht geladen werden. Versuchen Sie es noch einmal."
},
"save_failed": "Der Entwurf konnte nicht gespeichert werden. Versuchen Sie es noch einmal.",
"validation": {
"invalid_recipients": "Nicht gesendet. Diese Adressen sind nicht richtig: {addresses}",
"attachment_upload_failed": "Nicht gesendet. Ein Anhang konnte nicht hochgeladen werden. Entfernen Sie ihn und versuchen Sie es noch einmal."
},
"upload_failed": "{filename} konnte nicht hochgeladen werden. Versuchen Sie es noch einmal.",
"send_failed": "Ihre E-Mail wurde nicht gesendet. Versuchen Sie es noch einmal.",
"send_offline": "Sie sind nicht mit dem Internet verbunden. Ihre E-Mail ist noch da. Senden Sie sie noch einmal, wenn Sie wieder online sind.",
"send_timeout": "Der Server hat nicht mehr geantwortet. Vielleicht wurde Ihre E-Mail trotzdem gesendet. Schauen Sie in Ihren Ordner „Gesendet“, bevor Sie sie noch einmal senden.",
"submission_limit": "Der Server hat die E-Mail nicht gesendet, weil Sie eine Grenze erreicht haben: {reason}",
"send_plugin_failed": "Nicht gesendet. Eine Erweiterung, die die E-Mail sendet, hat nicht funktioniert oder zu lange gebraucht. Schauen Sie in Ihren Ordner „Gesendet“, bevor Sie sie noch einmal senden.",
"schedule_send_invalid": "Geben Sie ein Datum und eine Uhrzeit ein.",
"schedule_send_cleanup_warning": "Die E-Mail wird zur gewählten Zeit gesendet. Der Entwurf konnte aber nicht entfernt werden und liegt noch in „Entwürfe“.",
"invalid_recipient": "Das ist keine richtige E-Mail-Adresse",
"send_filing_warning": "Gesendet. Ein alter Entwurf dieser E-Mail liegt aber vielleicht noch in „Entwürfe“.",
"send_guard_failed": "Nicht gesendet. Eine Erweiterung, die E-Mails vor dem Senden prüft, hat nicht funktioniert oder nicht geantwortet."
},
"common": {
"reconnecting": "Die Verbindung ist weg. Wir versuchen, wieder zu verbinden…"
},
"notifications": {
"error_archiving": "Die E-Mail konnte nicht archiviert werden. Versuchen Sie es noch einmal.",
"error_updating": "Die E-Mail konnte nicht geändert werden. Versuchen Sie es noch einmal.",
"error_sending": "Ihre E-Mail wurde nicht gesendet. Versuchen Sie es noch einmal.",
"error_deleting": "Die E-Mail konnte nicht gelöscht werden. Versuchen Sie es noch einmal.",
"error_loading": "Ihre E-Mails konnten nicht geladen werden. Versuchen Sie es noch einmal.",
"move_failed": "Verschieben hat nicht geklappt. Versuchen Sie es noch einmal.",
"undo_failed": "Rückgängig machen hat nicht geklappt. Versuchen Sie es noch einmal.",
"copy_failed": "Kopieren hat nicht geklappt. Versuchen Sie es noch einmal.",
"move_error": "Die E-Mails konnten nicht in diesen Ordner verschoben werden. Versuchen Sie es noch einmal.",
"tag_failed": "Das Label konnte nicht hinzugefügt werden. Versuchen Sie es noch einmal.",
"identity_create_failed": "Die Absenderadresse konnte nicht hinzugefügt werden: {error}",
"identity_update_failed": "Die Absenderadresse konnte nicht geändert werden: {error}",
"identity_delete_failed": "Die Absenderadresse konnte nicht gelöscht werden: {error}",
"identity_unauthorized": "Sie dürfen von dieser E-Mail-Adresse nicht senden. Fragen Sie Ihre Administration, wenn Sie sie brauchen.",
"vacation_save_failed": "Ihre automatische Antwort konnte nicht gespeichert werden. Versuchen Sie es noch einmal.",
"filters_save_failed": "Ihre Filterregeln konnten nicht gespeichert werden. Versuchen Sie es noch einmal.",
"export_email_error": "Die E-Mail konnte nicht als Datei gespeichert werden. Versuchen Sie es noch einmal.",
"import_email_error": "Die E-Mail konnte nicht aus der Datei hinzugefügt werden. Prüfen Sie die Datei und versuchen Sie es noch einmal.",
"rule_apply_failed": "Die Regel konnte nicht auf die E-Mails angewendet werden, die schon da sind",
"rule_folder_failed": "Der Ordner konnte nicht erstellt werden. Versuchen Sie es noch einmal.",
"vacation_filters_save_failed": "Ihre automatische Antwort ist gespeichert. Die Weiterleitung und die Antwortliste konnten aber nicht gespeichert werden. Versuchen Sie es noch einmal."
},
"settings": {
"reset_confirm": "Alle Einstellungen auf den Anfangszustand zurücksetzen?",
"themes": {
"description": "Wählen Sie ein Aussehen. Ihre Administration kann weitere Designs hinzufügen.",
"locked": "Ihre Administration hat das Design gewählt. Sie können es nicht ändern.",
"forced": "Ihre Administration hat das Design „{name}“ gewählt. Sie können es nicht ändern."
},
"appearance": {
"message_list_order": {
"description": "Die Reihenfolge der E-Mails in der Liste. Der Server sortiert den ganzen Ordner, nicht nur die E-Mails, die Sie sehen.",
"tiebreak_note": "Sie können bis zu drei Stufen wählen. Eine spätere Stufe zählt nur, wenn die früheren gleich sind. Am Ende kommen immer die neuesten E-Mails zuerst.",
"scope": {
"description": "Ist das aus, gilt die Reihenfolge nur im Posteingang. Andere Ordner zeigen die neuesten E-Mails zuerst."
},
"unsupported_note": "Ihr Mailserver kann nicht nach gelesen oder ungelesen, Stern oder Label sortieren. Diese Stufen fallen weg, und die neuesten E-Mails kommen zuerst."
},
"description": "Ändern Sie, wie das Webmail aussieht",
"theme": {
"description": "Wählen Sie helle oder dunkle Farben"
},
"font_size": {
"description": "Machen Sie den Text größer oder kleiner"
},
"list_density": {
"description": "Wie viel Platz zwischen den Einträgen ist"
},
"animations": {
"description": "Dinge beim Öffnen und Schließen sanft bewegen"
},
"toolbar_position": {
"description": "Wo die Knöpfe für eine E-Mail stehen, etwa Antworten, Archivieren oder Löschen"
},
"toolbar_labels": {
"description": "Wörter neben den Symbolen der Knöpfe zeigen. Schalten Sie das aus, um Platz zu sparen, wenn Sie die Symbole kennen."
},
"hide_account_switcher": {
"description": "Die Kontoauswahl oben in der Ordnerliste ausblenden. Sie können das Konto weiterhin unten in der linken Leiste wechseln."
},
"show_rail_account_list": {
"label": "Ihre Konten in der linken Leiste zeigen",
"description": "Unten in der linken Leiste für jedes Konto einen Kreis zeigen, damit Sie schnell wechseln können. Darunter ist ein Knopf zum Abmelden."
},
"unified_mailbox": {
"description": "Ordner zeigen, die die E-Mails mehrerer Konten zusammenfassen, etwa einen Posteingang für alle. Dafür brauchen Sie mehr als ein Konto (oder ein Gruppenpostfach unten).",
"cross_account": {
"description": "Die E-Mails aller Ihrer Konten zusammenfassen, nicht nur des Kontos, das Sie gerade nutzen."
},
"include_group": {
"description": "Auch die Postfächer hinzufügen, die Sie mit einer Gruppe teilen."
}
},
"all_mail": {
"folders_description": "Wählen Sie, welche Ordner dieses Kontos zu den Listen „Alle Mails“, „Ungelesen“ und „Markiert“ gehören."
},
"colorful_sidebar_icons": {
"description": "Ordner- und Label-Symbole farbig zeigen (blauer Posteingang, roter Spam, grünes Gesendet und so weiter). Schalten Sie das aus für graue Symbole."
},
"tint_list_rows": {
"description": "Jede E-Mail in der Liste in der Farbe ihres ersten Labels einfärben. Ist das aus, zeigen die kleinen Punkte die Label-Farbe trotzdem."
},
"show_folder_total_count": {
"description": "Zeigen, wie viele E-Mails ein Ordner oder Label insgesamt hat, neben der Zahl der ungelesenen."
},
"favicon_unread_badge": {
"description": "Die Zahl der ungelesenen E-Mails auf dem Browser-Tab und dem App-Symbol zeigen. So sehen Sie neue E-Mails, auch wenn Sie gerade einen anderen Tab nutzen."
},
"show_shared_mail_accounts": {
"description": "Die Ordner von Postfächern, die andere mit Ihnen teilen, in der Ordnerliste unter „Geteilt“ zeigen. Schalten Sie das aus, um alle auszublenden, oder blenden Sie unten einzelne Konten aus. Kalender und Kontakte bleiben."
},
"pro_interface": {
"description": "Eine Ansicht für große Bildschirme mit vielen Tabs und mehreren Konten gleichzeitig. Sie können jederzeit zur normalen Ansicht zurück."
},
"account_marker": {
"description": "In den zusammengefassten Ordnern jede E-Mail mit der Farbe ihres Kontos markieren."
}
},
"keywords": {
"description": "Erstellen Sie Labels, um Ihre E-Mails zu ordnen. Die Labels werden mit Ihren E-Mails auf dem Server gespeichert.",
"migration_error": "Das Label konnte bei Ihren E-Mails nicht geändert werden. Versuchen Sie es noch einmal.",
"filters_migration_error": "Manche Filterregeln nutzen noch das alte Label. Ändern Sie sie selbst.",
"nesting": {
"description": "Labels in andere Labels legen, wie Ordner. Die Seitenleiste zeigt sie als Baum."
},
"has_children_locked": "In diesem Label liegen andere Labels. Deshalb können Sie es nicht umbenennen oder verschieben. Verschieben oder löschen Sie zuerst die Labels darin.",
"has_children_delete": "Löschen Sie zuerst die Labels in diesem Label",
"unrecognized": {
"description": "Dieser Browser speichert die Namen und Farben Ihrer Labels. Die Labels selbst sind an Ihren E-Mails gespeichert. Suchen Sie in Ihren E-Mails nach Labels, die hier fehlen, und fügen Sie sie wieder hinzu.",
"partial": "Nur Ihre {scanned} neuesten von {total} E-Mails wurden durchsucht. Labels, die nur an älteren E-Mails hängen, fehlen vielleicht.",
"error": "Ihre E-Mails konnten nicht nach Labels durchsucht werden. Versuchen Sie es noch einmal."
}
},
"notifications": {
"push": {
"description": "Erhalten Sie eine Meldung auf Ihrem Bildschirm, wenn neue E-Mails ankommen, auch wenn diese Website geschlossen ist. Der Dienst, der die Meldung weitergibt, sieht Ihre E-Mails nie.",
"relay_label": "Benachrichtigungsdienst",
"relay_desc": "Der Dienst, der Benachrichtigungen an Ihr Gerät weitergibt. Ihre Administration legt fest, welche Sie wählen können.",
"relay_locked_desc": "Ihre Administration hat den Benachrichtigungsdienst gewählt. Sie können ihn nicht ändern.",
"status_unsupported": "Dieser Browser kann keine Benachrichtigungen zeigen, wenn die Website geschlossen ist",
"status_lite": "Diese Version des Webmails kann keine Benachrichtigungen senden, wenn sie geschlossen ist.",
"reenable": "Wieder einschalten",
"ios_hint": "Auf iPhone und iPad fügen Sie diese Website zuerst zum Home-Bildschirm hinzu. Safari sendet Benachrichtigungen nur für Websites auf dem Home-Bildschirm.",
"devices_title": "Geräte, die Benachrichtigungen bekommen",
"devices_desc": "Alle Geräte, die Benachrichtigungen für dieses Konto bekommen. Wenn Sie ein Gerät entfernen, bekommt es sofort keine mehr.",
"devices_error": "Die Liste der Geräte konnte nicht geladen werden. Versuchen Sie es noch einmal.",
"device_status_unknown": "Der Benachrichtigungsdienst kennt dieses Gerät nicht",
"revoke": "Entfernen",
"confirm_revoke_title": "Dieses Gerät entfernen?",
"confirm_revoke_message": "Es bekommt sofort keine Benachrichtigungen mehr. Um sie wieder zu bekommen, schalten Sie die Benachrichtigungen auf diesem Gerät wieder ein.",
"confirm_revoke_message_this": "Dieses Gerät bekommt sofort keine Benachrichtigungen mehr.",
"shared_mailboxes_no_push": "Für Postfächer, die andere mit Ihnen teilen, bekommen Sie keine Benachrichtigungen.",
"error_forbidden": "Der Mailserver erlaubt für dieses Konto keine Benachrichtigungen.",
"error_over_quota": "Zu viele Geräte bekommen Benachrichtigungen für dieses Konto (meist sind höchstens 15 erlaubt). Entfernen Sie Geräte, die Sie nicht mehr nutzen, und versuchen Sie es dann noch einmal."
},
"push_all": {
"description": "Benachrichtigungen für mehrere Konten auf einmal ein- oder ausschalten.",
"result_partial": "Für {enabled} Konten eingeschaltet. Bei {failed} Konten hat es nicht geklappt."
},
"sound_selection": {
"choose_desc": "Wählen Sie einen Ton. Klicken Sie auf den Lautsprecher, um ihn anzuhören."
},
"email": {
"description": "Wählen Sie, wie Sie von neuen E-Mails erfahren",
"inbox_only_desc": "Nur bei E-Mails im Posteingang melden, nicht bei E-Mails, die ein Filter in einen anderen Ordner legt"
},
"calendar": {
"description": "Wählen Sie, wie Sie an Ihre Termine erinnert werden"
}
},
"language_region": {
"description": "Sprache, wie Datum und Uhrzeit aussehen, und andere Dinge für Ihr Land",
"date_locale": {
"description": "Die Reihenfolge von Tag, Monat und Jahr im Datum"
},
"time_format": {
"description": "Uhrzeiten wie 3:00 PM (12 Stunden) oder wie 15:00 (24 Stunden) zeigen"
},
"time_zone": {
"description": "Die Zeiten von E-Mails und Terminen in dieser Zeitzone zeigen. Wenn Sie keine wählen, gilt die Zeitzone Ihres Browsers."
}
},
"email_behavior": {
"inbox_type": {
"description": "Bei „Ungelesene zuerst“ stehen Ihre 25 neuesten ungelesenen E-Mails oben im Posteingang"
},
"clear_search_on_folder_change": {
"description": "Wenn Sie einen anderen Ordner öffnen, endet Ihre Suche und Sie sehen den ganzen Ordner. Ist das aus, sucht die Suche im neuen Ordner weiter."
},
"request_read_receipt": {
"label": "Immer eine Lesebestätigung anfordern",
"description": "Neue E-Mails bitten den Empfänger, Ihnen zu melden, wenn er die E-Mail geöffnet hat. Sie können das bei jeder E-Mail ausschalten."
},
"read_receipt_response": {
"label": "Wenn jemand eine Lesebestätigung möchte",
"description": "Manche Absender möchten erfahren, wann Sie ihre E-Mail öffnen. Wählen Sie, was dann passiert."
},
"description": "Legen Sie fest, was mit Ihren E-Mails passiert",
"mark_read": {
"description": "Wann eine geöffnete E-Mail als gelesen gilt"
},
"delete_action": {
"warning": "Gelöschte E-Mails sind dann endgültig weg. Sie können sie nicht zurückholen."
},
"message_spacing": {
"description": "Der Abstand um den Text einer geöffneten E-Mail"
},
"archive_mode": {
"description": "Wie archivierte E-Mails in Ordner sortiert werden"
},
"permanently_delete_junk": {
"label": "Spam endgültig löschen",
"description": "Wenn Sie eine E-Mail im Spam-Ordner löschen, ist sie sofort endgültig weg. Sie kommt nicht erst in den Papierkorb."
},
"confirm_bulk_delete": {
"description": "Fragen, bevor mehrere ausgewählte E-Mails in den Papierkorb kommen. Beim endgültigen Löschen wird immer gefragt."
},
"mail_layout": {
"description": "Wählen Sie, wie die E-Mail-Liste und die geöffnete E-Mail auf dem Bildschirm stehen.",
"split_description": "Die E-Mail-Liste und die geöffnete E-Mail stehen nebeneinander.",
"focus_description": "Jede E-Mail hat eine Zeile. Eine geöffnete E-Mail nutzt die ganze Breite. Die Ordnerliste bleibt sichtbar.",
"horizontal_description": "Die E-Mail-Liste steht oben. Die geöffnete E-Mail steht darunter."
},
"list_columns": {
"description": "Die Liste als Tabelle mit den Spalten Von, Betreff, Datum und Größe zeigen."
},
"show_verification_codes": {
"description": "Wenn eine E-Mail einen Anmelde- oder Bestätigungscode enthält, gibt es einen Knopf zum Kopieren. In der Liste bleibt der Knopf einen Tag lang."
},
"plain_text_mode": {
"description": "Alle E-Mails als reinen Text schreiben, ohne Fettdruck, Farben oder Bilder. Das gilt auch für Antworten und Weiterleitungen."
},
"auto_select_reply_identity": {
"description": "Wenn Sie antworten, geht die Antwort von der Adresse aus, an die die E-Mail ging. Das klappt mit all Ihren eigenen Adressen. Weiter unten wählen Sie, ob auch andere Adressen Ihrer Domain zählen."
},
"reply_identity_match": {
"label": "Welche Adressen als Ihre gelten",
"description": "„Nur exakte Adresse“ nutzt nur die Adressen, die Sie eingerichtet haben. „Jede Adresse auf meinen Domains“ antwortet auch von anderen Adressen Ihrer Domains. Wählen Sie die exakte Adresse, wenn diese anderen Adressen Verteilerlisten sind."
},
"signature_position": {
"description": "Wo Ihre Signatur steht, wenn Sie antworten oder weiterleiten. Über dem zitierten Text schließt sie Ihre Antwort ab. Darunter bleibt die alte E-Mail an einem Stück."
},
"signature_separator": {
"label": "Linie vor der Signatur",
"description": "Die übliche Zeile „-- “ vor Ihre Signatur setzen. Viele Mail-Apps erkennen die Signatur daran."
},
"sub_address_delimiter": {
"label": "Zeichen vor dem Adress-Stichwort",
"description": "Das Zeichen zwischen Ihrem Namen und dem Stichwort in einer Adresse, etwa user{delimiter}tag@domain.com. Nehmen Sie dasselbe Zeichen wie Ihr Mailserver."
},
"attachment_click_action": {
"label": "Wenn Sie auf einen Anhang klicken",
"description": "Wählen Sie, ob ein Klick auf einen Anhang ihn anzeigt oder auf Ihrem Gerät speichert"
},
"attachment_position": {
"description": "Wo Anhänge in einer geöffneten E-Mail stehen"
},
"always_light_mode": {
"description": "E-Mails mit hellem Hintergrund zeigen, auch wenn die App dunkel ist. Manche E-Mails sehen falsch aus, wenn sie dunkel dargestellt werden."
},
"external_content": {
"label": "Bilder aus dem Internet",
"description": "Wählen Sie, ob Bilder gezeigt werden, die eine E-Mail aus dem Internet lädt. Das Laden kann dem Absender verraten, dass Sie die E-Mail geöffnet haben."
},
"trusted_senders": {
"description": "Absender, deren Bilder immer gezeigt werden",
"empty_description": "Wenn eine E-Mail blockierte Bilder hat, klicken Sie auf „Diesem Absender immer vertrauen“, um den Absender hier hinzuzufügen.",
"save_error": "Die Liste konnte nicht gespeichert werden. Versuchen Sie es noch einmal.",
"use_address_book_label": "Liste im Adressbuch speichern",
"use_address_book_description": "Vertrauenswürdige Absender in einem eigenen Adressbuch speichern. Dann ist die Liste auf allen Ihren Geräten gleich."
},
"hover_actions": {
"description": "Die Knöpfe, die Sie sehen, wenn Sie mit der Maus über eine E-Mail in der Liste fahren"
},
"default_mail_program": {
"description": "E-Mail-Links auf Websites (mailto:-Links) in {appName} öffnen",
"error": "Ihr Browser kann das nicht"
},
"attachment_reminder": {
"description": "Wenn Ihre E-Mail von einem Anhang spricht, aber nichts angehängt ist, werden Sie vor dem Senden gewarnt",
"keywords_label": "Wörter, bei denen die Erinnerung kommt",
"keywords_description": "Wenn Ihre E-Mail eines dieser Wörter enthält, kommt die Erinnerung"
},
"placeholder_warning": {
"description": "Vor dem Senden fragen, wenn die E-Mail noch Lücken hat, etwa [TIME] oder [Ihr Name], leere Vorlagenfelder oder Notizen wie TODO."
},
"recipient_mentions": {
"description": "Tippen Sie @ in Ihrer E-Mail, um einen Empfänger auszuwählen. Sein Vorname kommt in den Text. Mit Enter oder Tab wählen Sie den markierten Namen."
},
"mention_adds_recipient": {
"description": "Wenn Sie @ tippen, können Sie auch Personen aus Ihren Kontakten wählen. Sind sie noch keine Empfänger, werden sie zur E-Mail hinzugefügt."
},
"hide_inline_image_attachments": {
"label": "Bilder im Text nicht als Anhang zeigen",
"description": "Bilder, die zum Text der E-Mail gehören, stehen nicht noch einmal in der Liste der Anhänge"
},
"attachment_image_previews": {
"description": "Kleine Vorschaubilder von angehängten Bildern statt eines Datei-Symbols zeigen"
},
"send_delay": {
"description": "Ihre E-Mails warten ein paar Sekunden, bevor sie gesendet werden. In dieser Zeit können Sie sie noch stoppen.",
"unsupported": "Der Mailserver dieses Kontos kann mit dem Senden nicht warten. Die Einstellung wird trotzdem für Ihre anderen Konten gespeichert."
},
"swipe_right_action": {
"description": "Auf einem Touchscreen: was passiert, wenn Sie eine E-Mail nach rechts wischen."
},
"swipe_left_action": {
"description": "Auf einem Touchscreen: was passiert, wenn Sie eine E-Mail nach links wischen."
},
"rtl_editing": {
"label": "Schreiben von rechts nach links",
"description": "Fügt dem Editor einen Knopf hinzu. Damit schreiben Sie einen Absatz von links nach rechts oder von rechts nach links."
}
},
"account": {
"description": "Sehen Sie die Angaben zu Ihrem Konto",
"auth_method_label": "So melden Sie sich an",
"auth_method_oauth": "Anmeldeseite Ihrer Organisation",
"server": {
"label": "Adresse des Mailservers"
},
"accounts": {
"description": "Ziehen Sie die Konten, um ihre Reihenfolge in der Kontoliste zu ändern"
},
"backup": {
"description": "Speichern Sie die Liste Ihrer Konten auf dem Server und holen Sie sie auf einem anderen Gerät zurück. Ein Passwort, das nur Sie kennen, schützt die Liste. Die Passwörter der Postfächer werden nicht gespeichert. Sie melden sich also bei jedem Konto neu an.",
"description_passwords": "Speichern Sie die Liste Ihrer Konten auf dem Server und holen Sie sie auf einem anderen Gerät zurück. Ein Passwort, das nur Sie kennen, schützt die Liste. Sie können auch die Passwörter der Postfächer speichern. Dann melden sich die Konten von selbst an.",
"password_hint": "Mindestens 10 Zeichen. Niemand kann dieses Passwort zurücksetzen. Wenn Sie es vergessen, speichern Sie die Liste noch einmal mit einem neuen Passwort.",
"restore_hint": "Das Passwort, das Sie beim Speichern der Liste gewählt haben.",
"include_passwords_hint": "Wenn Sie die Liste zurückholen, melden sich diese Konten von selbst an. Aber Vorsicht: Wer das Passwort der Sicherung kennt, kann jedes Postfach in der Liste öffnen.",
"skipped": "{count, plural, one {# Konto wurde} other {# Konten wurden}} nicht hinzugefügt, weil Sie schon die höchste erlaubte Zahl an Konten haben.",
"sign_in_failed": "{count, plural, one {# gespeichertes Passwort hat nicht funktioniert} other {# gespeicherte Passwörter haben nicht funktioniert}}. Melden Sie sich in der Kontoliste selbst bei diesen Konten an.",
"errors": {
"unlock_failed": "Das Passwort ist falsch, oder diese Liste gehört zu einem anderen Konto.",
"owner_signin_required": "Melden Sie sich noch einmal bei diesem Konto an. Versuchen Sie es dann erneut.",
"https_required": "Das geht nur über eine sichere Verbindung (die Adresse beginnt mit https).",
"storage_failed": "Der Server konnte die Liste nicht speichern oder öffnen. Versuchen Sie es später noch einmal."
}
},
"shared_accounts": {
"description": "Konten, die Sie mit anderen teilen und verwalten dürfen. Wählen Sie eines aus, um seine Filter, automatischen Antworten, Kalender und Kontakte zu ändern."
}
},
"security": {
"description": "Ändern Sie Ihr Passwort und schützen Sie Ihr Konto",
"not_available": "Die Sicherheitseinstellungen für diesen Mailserver können Sie hier nicht ändern. Vielleicht erlaubt Ihr Server das nicht. Mehr dazu steht in der <a href=\"https://bulwarkmail.org/docs/guides/account-security\" class=\"underline hover:opacity-80\" target=\"_blank\">Dokumentation</a>.",
"password": {
"error_title": "Ihr Passwort wurde nicht geändert",
"error_mismatch": "Die beiden neuen Passwörter sind nicht gleich. Geben Sie sie noch einmal ein.",
"error_min_length": "Ihr Passwort braucht mindestens 8 Zeichen",
"error_generic": "Ihr Passwort konnte nicht geändert werden. Versuchen Sie es noch einmal."
},
"display_name": {
"description": "Der Name, den der Mailserver für Sie anzeigt",
"error": "Ihr Name konnte nicht geändert werden. Versuchen Sie es noch einmal."
},
"totp": {
"label": "Code aus einer Authenticator-App",
"description": "Schützen Sie Ihr Konto besser: Bei der Anmeldung geben Sie zusätzlich einen Code aus einer App auf Ihrem Handy ein",
"enable_error": "Die Zwei-Faktor-Authentifizierung konnte nicht eingeschaltet werden. Versuchen Sie es noch einmal.",
"disable_error": "Die Zwei-Faktor-Authentifizierung konnte nicht ausgeschaltet werden. Versuchen Sie es noch einmal.",
"setup_instructions": "Öffnen Sie Ihre Authenticator-App (zum Beispiel Google Authenticator oder Authy) und fügen Sie diesen Link dort hinzu:",
"verification_code": "Code aus der App",
"disable_confirm_prompt": "Um die Zwei-Faktor-Authentifizierung auszuschalten, geben Sie Ihr Passwort ein.",
"password_required": "Geben Sie Ihr Passwort ein",
"code_required": "Geben Sie den Code aus der App ein",
"code_invalid": "Dieser Code ist falsch oder zu alt. Geben Sie den Code ein, den die App gerade anzeigt."
},
"app_passwords": {
"description": "Zusätzliche Passwörter für Mail-Apps, die keinen zweiten Code abfragen können, etwa manche Mail-Apps auf dem Handy",
"allowed_ips_label": "Nur von diesen Internetadressen (optional)",
"allowed_ips_hint": "Trennen Sie die Adressen mit Komma oder Leerzeichen. Lassen Sie das Feld leer, um alle Adressen zu erlauben.",
"copy_now_warning": "Kopieren Sie dieses Passwort jetzt. Sie sehen es nicht noch einmal.",
"add_error": "Das App-Passwort konnte nicht erstellt werden. Versuchen Sie es noch einmal.",
"remove_error": "Das App-Passwort konnte nicht gelöscht werden. Versuchen Sie es noch einmal.",
"delete_message": "Apps, die {name} nutzen, können sich dann nicht mehr anmelden."
},
"api_keys": {
"description": "Schlüssel für Programme und Skripte, die ohne Sie mit Ihrem Postfach arbeiten, zum Beispiel eine Sicherung",
"copy_now_warning": "Kopieren Sie diesen Schlüssel jetzt. Sie sehen ihn nicht noch einmal.",
"add_error": "Der Schlüssel konnte nicht erstellt werden. Versuchen Sie es noch einmal.",
"remove_error": "Der Schlüssel konnte nicht gelöscht werden. Versuchen Sie es noch einmal.",
"delete_message": "Programme und Skripte, die {name} nutzen, funktionieren dann nicht mehr."
},
"encryption": {
"label": "E-Mails verschlüsselt speichern",
"description": "Der Server speichert Ihre E-Mails verschlüsselt. Nur Ihr Schlüssel kann sie wieder lesbar machen.",
"error": "Die Verschlüsselung konnte nicht geändert werden. Versuchen Sie es noch einmal.",
"algorithm_label": "Art der Verschlüsselung",
"encrypt_on_append": "Auch E-Mails verschlüsseln, die Apps in Ihr Postfach legen",
"allow_spam_training": "Den Spamfilter aus neuen E-Mails lernen lassen, bevor sie verschlüsselt werden",
"disable_message": "Ab jetzt speichert der Server neue E-Mails ohne Verschlüsselung."
},
"public_keys": {
"key_label": "Öffentlicher Schlüssel (S/MIME oder PGP, als Text)",
"add_error": "Der öffentliche Schlüssel konnte nicht hinzugefügt werden. Prüfen Sie, ob Sie ihn ganz eingefügt haben.",
"remove_error": "Der öffentliche Schlüssel konnte nicht gelöscht werden. Versuchen Sie es noch einmal."
},
"email_client": {
"description": "Mit diesen Angaben richten Sie E-Mail auf Ihrem Computer oder Handy ein, zum Beispiel in Thunderbird, Apple Mail oder Outlook",
"jmap_username_label": "Benutzername für Mail-Apps",
"password_instructions": "Nutzen Sie in Ihrer Mail-App den Benutzernamen oben und ein App-Passwort. Ein App-Passwort erstellen Sie weiter unten auf dieser Seite."
},
"link_device": {
"description": "Melden Sie sich in der Bulwark-Mail-App auf Ihrem Handy an, ohne Ihr Passwort einzutippen. Ihr Handy bekommt eine eigene Anmeldung. Wenn Sie sich hier abmelden, bleibt Ihr Handy angemeldet.",
"error": "Der Code konnte nicht erstellt werden. Versuchen Sie es noch einmal.",
"password_prompt": "Geben Sie Ihr Passwort ein, damit wir wissen, dass Sie es sind. Ihr Passwort wird nicht an das Handy geschickt.",
"error_not_signed_in": "Sie wurden abgemeldet. Melden Sie sich wieder an und verbinden Sie dann Ihr Handy.",
"error_impersonation": "Sie sehen gerade das Postfach einer anderen Person. Von hier aus können Sie kein Handy verbinden.",
"error_too_many_attempts": "Sie haben es zu oft versucht. Warten Sie kurz und versuchen Sie es dann noch einmal.",
"error_session_secret": "Das Verbinden eines Handys ist auf diesem Server nicht eingerichtet. Fragen Sie Ihre Administration.",
"error_server_unreachable": "Wir erreichen Ihren Mailserver nicht. Versuchen Sie es noch einmal.",
"error_pairing_unavailable": "Ihr Mailserver erlaubt das Verbinden eines Handys auf diese Weise nicht. Fragen Sie Ihre Administration.",
"error_insecure_server": "Die Handy-App braucht eine sichere Verbindung zum Mailserver (https). Dieser Server hat keine."
},
"sections": {
"devices_description": "Lesen Sie Ihre E-Mails in anderen Apps und auf anderen Geräten."
}
},
"identities": {
"description": "Die E-Mail-Adressen, von denen Sie senden können",
"identities_count": {
"label": "Ihre Absenderadressen",
"description": "Adressen, von denen Sie E-Mails senden können"
},
"sub_addressing": {
"label": "Adressen mit Stichwort",
"description": "Hängen Sie ein Stichwort an Ihre Adresse, etwa user+shop@domain.com. E-Mails daran kommen trotzdem bei Ihnen an, und Sie können sie nach dem Stichwort sortieren."
}
},
"vacation": {
"description": "Neue E-Mails automatisch beantworten, während Sie weg sind",
"not_supported": "Ihr Mailserver kann keine automatischen Antworten senden.",
"fetch_error": "Ihre automatische Antwort konnte nicht geladen werden. Versuchen Sie es noch einmal.",
"status": {
"description": "Alle, die Ihnen schreiben, bekommen eine automatische Antwort"
},
"date_range": {
"start_description": "Leer lassen, um sofort zu beginnen"
},
"message": {
"subject_description": "Der Betreff der automatischen Antwort",
"html_description": "Fügen Sie eine Version mit Links und Formatierung hinzu. Wer sie in seiner Mail-App nicht sehen kann, bekommt den einfachen Text oben."
},
"warnings": {
"invalid_date": "Geben Sie ein Start- und Enddatum mit Uhrzeit ein oder lassen Sie das Feld leer",
"empty_body": "Die Nachricht ist leer. Die Leute würden eine Antwort ohne Text bekommen.",
"subject_too_long": "Der Betreff ist zu lang für den Server. Kürzen Sie ihn (höchstens {max} Byte).",
"body_too_long": "Die Nachricht ist zu lang für den Server. Kürzen Sie sie (höchstens {max} Byte, mit Formatierung).",
"forward_address": "Geben Sie die E-Mail-Adresse ein, an die weitergeleitet wird",
"not_running": "Weiterleitung und Antwortliste sind gespeichert, aber gerade aus. Klicken Sie auf Speichern, um sie wieder einzuschalten."
},
"forward": {
"description": "Neue E-Mails in der Zeit oben an eine andere Adresse weiterschicken, mit oder ohne automatische Antwort. Ohne Enddatum läuft das, bis Sie es ausschalten. Spam wird nicht weitergeleitet.",
"keep_description": "Ist das aus, wird die E-Mail nur weitergeleitet. Sie bleibt nicht in diesem Postfach, und Ihre Filterregeln laufen nicht darauf."
}
},
"folders": {
"description": "Verwalten Sie Ihre Ordner und legen Sie fest, wofür welcher Ordner dient",
"folder_list_description": "Klicken Sie auf das Symbol eines Ordners, um es zu ändern",
"standard_roles_description": "Legen Sie fest, welcher Ordner Ihr Posteingang, Gesendet, Papierkorb und so weiter ist.",
"reorder": "Ziehen Sie nach oben oder unten, um die Reihenfolge zu ändern. Ziehen Sie nach links oder rechts, um den Ordner in einen anderen Ordner zu legen.",
"move_partial_error": "{count, plural, one {# Ordner konnte} other {# Ordner konnten}} nicht verschoben werden. Versuchen Sie es noch einmal.",
"confirm_delete": "„{name}“ löschen? Die E-Mails in diesem Ordner kommen in den Papierkorb.",
"cannot_delete_role": "Dieser Ordner hat eine besondere Aufgabe, etwa Posteingang oder Papierkorb. Entfernen Sie zuerst die Aufgabe, dann können Sie ihn löschen.",
"error_delete_has_children": "Der Ordner wurde nicht gelöscht, weil andere Ordner darin liegen. Löschen oder verschieben Sie diese zuerst.",
"error_delete_has_email": "Der Ordner wurde nicht gelöscht, weil noch E-Mails darin liegen. Verschieben oder löschen Sie diese zuerst.",
"error_role": "Die Aufgabe des Ordners konnte nicht geändert werden. Versuchen Sie es noch einmal."
},
"advanced": {
"debug_mode": {
"description": "Mehr Einzelheiten darüber aufzeichnen, was die App tut. Das hilft, Fehler zu finden."
},
"settings_sync": {
"label": "Gleiche Einstellungen auf allen Geräten",
"description": "Ihre Einstellungen auf dem Server speichern, damit alle Ihre Browser und Geräte sie nutzen"
},
"sender_favicons": {
"description": "Bei Firmen das Symbol ihrer Website als Bild des Absenders zeigen",
"refresh_description": "Die Website-Symbole neu laden, zum Beispiel wenn eine Firma ein neues hat"
},
"show_avatars_in_junk": {
"label": "Bilder der Absender im Spam-Ordner zeigen",
"description": "Das Bild oder Website-Symbol des Absenders auch im Spam-Ordner zeigen. Das ist anfangs aus, weil gefälschte E-Mails mit Bild echter wirken."
},
"keyboard_shortcuts": {
"description": "Alle Tasten ansehen, die Sie statt der Maus nutzen können"
},
"refresh_cache": {
"label": "Daten neu vom Server laden",
"description": "Ihre Kontakte, Kalender und Ordner neu vom Server laden. Nutzen Sie das, wenn etwas alt oder falsch aussieht. Sie bleiben angemeldet.",
"confirm_title": "Daten neu vom Server laden?"
},
"reset_settings": {
"description": "Alle Einstellungen auf den Anfangszustand zurücksetzen"
},
"export_settings": {
"description": "Ihre Einstellungen als Datei auf Ihrem Gerät speichern"
},
"import_settings": {
"description": "Ihre Einstellungen aus einer früher gespeicherten Datei laden"
},
"sections": {
"backup_description": "Dieselben Einstellungen in anderen Browsern und auf anderen Geräten nutzen.",
"fix_description": "Wenn etwas alt oder falsch aussieht oder Sie neu anfangen möchten."
}
},
"sidebar_apps": {
"description": "Fügen Sie der linken Leiste eigene Apps und Links hinzu",
"keep_loaded_description": "Apps im Hintergrund offen lassen, wenn Sie wechseln, damit sie nicht neu laden müssen",
"managed_description": "Ihre Administration hat diese Apps für alle eingerichtet. Sie können sie nicht ändern."
},
"contacts": {
"group_by_letter_description": "In der Kontaktliste einen Buchstaben über jede Gruppe von Namen setzen",
"sort_by_last_name_description": "Kontakte nach Nachnamen sortieren, damit Familien nebeneinander stehen",
"import_description": "Kontakte aus einer Kontaktdatei (.vcf) hinzufügen",
"export_description": "Alle Kontakte in einer Kontaktdatei (.vcf) speichern"
},
"downloads": {
"description": "Legen Sie die Dateinamen für E-Mails und Anhänge fest, die Sie speichern.",
"email_template": {
"description": "Der Dateiname, wenn Sie eine E-Mail speichern oder in einen Ordner auf Ihrem Computer ziehen. Die Endung .eml wird automatisch angehängt."
},
"attachment_template": {
"description": "Der Dateiname, wenn Sie einen Anhang speichern oder herausziehen. Wenn Sie '{filename}' und '{ext}' weglassen, behält die Datei ihre Endung."
},
"bundle_template": {
"description": "Der Dateiname, wenn Sie mehrere E-Mails zusammen in einer .zip-Datei speichern. Die Endung .zip wird automatisch angehängt."
},
"spaces": {
"description": "Im Dateinamen ein anderes Zeichen statt Leerzeichen nutzen."
},
"lowercase": {
"description": "Den ganzen Dateinamen kleinschreiben."
},
"strip_diacritics": {
"description": "Buchstaben mit Akzenten in einfache Buchstaben ändern (ä → a, é → e). Manche Programme kommen mit Akzenten nicht klar."
},
"collapse_separators": {
"description": "Wenn mehrere Leerzeichen, _ oder - hintereinander stehen, nur eines behalten."
},
"after_export": {
"description": "Sie können die E-Mail in einen anderen Ordner verschieben, nachdem Sie sie als Datei gespeichert haben."
}
},
"filters": {
"description": "Regeln, die neue E-Mails von selbst sortieren, zum Beispiel in einen Ordner",
"no_rules_description": "Erstellen Sie eine Regel, dann werden neue E-Mails von selbst sortiert",
"vacation_active_description": "Neue E-Mails bekommen eine automatische Antwort",
"delete_confirm": "Diese Regel löschen?",
"raw_editor": "Regeln als Code bearbeiten (Sieve)",
"raw_editor_warning": "Wenn Sie den Code ändern, können Sie die Regeln hier vielleicht nicht mehr mit dem normalen Editor bearbeiten. Ihre Änderungen im Code ersetzen die normalen Regeln.",
"not_supported": "Ihr Mailserver kann keine Filterregeln nutzen.",
"match_all": "Alle Bedingungen müssen zutreffen",
"match_any": "Eine Bedingung genügt",
"stop_processing": "Die Regeln darunter nicht mehr prüfen",
"condition_fields": {
"header": "Andere Kopfzeile",
"body": "Text der E-Mail"
},
"comparators": {
"matches": "passt zum Muster (* = beliebiger Text)"
},
"forward_limit": "Dieser Server leitet eine E-Mail an höchstens {count} Adressen weiter. Weitere Weiterleitungen fallen weg.",
"forward_unavailable": "Dieser Server erlaubt keine Weiterleitung. Regeln, die E-Mails weiterleiten, tun nichts.",
"header_name": "Name der Kopfzeile",
"opaque_warning": "Diese Regeln wurden in einer anderen App geändert. Sie können sie hier nur als Code bearbeiten.",
"open_sieve_editor": "Regeln als Code bearbeiten (Sieve)",
"expanded_view_description": "Jede Regel mit allen Bedingungen und Aktionen zeigen",
"reset_warning": "Damit werden alle Ihre jetzigen Regeln gelöscht und Sie fangen ohne Regeln neu an.",
"validation_empty_conditions": "Fügen Sie mindestens eine Bedingung hinzu und füllen Sie ihren Wert aus",
"validation_empty_actions": "Fügen Sie mindestens eine Aktion hinzu",
"validation_header_name": "Verwenden Sie im Namen der Kopfzeile nur die Buchstaben A bis Z, Ziffern, - und _",
"sieve_editor": {
"title": "Filterregeln als Code (Sieve)",
"warning": "Wenn Sie den Code ändern, können Sie die Regeln hier vielleicht nicht mehr mit dem normalen Editor bearbeiten. Ihre Änderungen im Code ersetzen die normalen Regeln.",
"script_content": "Regeln als Code",
"valid": "Der Code hat keine Fehler",
"invalid": "Der Code hat Fehler",
"validating": "Wird geprüft…",
"validate": "Prüfen",
"overwrite_message": "Dieser Code enthält die Regeln aus dem normalen Editor nicht mehr. Wenn Sie speichern, ersetzt er sie. Das können Sie nicht rückgängig machen.",
"validation_failed": "Der Code konnte nicht geprüft werden. Versuchen Sie es noch einmal."
},
"managed_by_tooltip": "{source} verwaltet das. Ändern Sie es in dieser App oder bearbeiten Sie die Regeln als Code.",
"apply_existing_unsupported": "Nur manche Regeln können auch für E-Mails laufen, die schon da sind: Regeln, die Absender, Empfänger, Betreff oder Kopfzeilen prüfen und die verschieben, kopieren, als gelesen markieren, mit Stern markieren oder mit Label versehen.",
"period_hint": "Die Regel gilt für E-Mails, die in dieser Zeit ankommen. Lassen Sie ein Feld leer, wenn es keinen Start oder kein Ende gibt.",
"validation_period_invalid": "Geben Sie ein Datum mit Uhrzeit ein oder lassen Sie das Feld leer",
"apply_existing_period": "Eine Regel mit Zeitraum gilt nur für E-Mails, die in dieser Zeit ankommen. Deshalb kann sie nicht für E-Mails laufen, die schon da sind."
},
"templates": {
"description": "Speichern Sie E-Mails, die Sie oft schreiben, und nutzen Sie sie wieder. Sie können Lücken haben, die Sie jedes Mal ausfüllen.",
"export_import_description": "Speichern Sie Ihre Vorlagen als Datei oder bringen Sie sie auf ein anderes Gerät",
"validation": {
"too_long": "Der Name darf höchstens 200 Zeichen haben"
}
},
"files": {
"sort_direction": {
"description": "Von A bis Z oder von Z bis A"
},
"show_thumbnails": {
"description": "Von jedem Bild ein kleines Vorschaubild statt eines Symbols zeigen"
},
"show_hidden": {
"description": "Versteckte Dateien und Ordner zeigen (ihr Name beginnt mit einem Punkt)"
}
},
"reading_page": {
"sections": {
"images_description": "Bilder aus dem Internet können dem Absender verraten, dass Sie die E-Mail geöffnet haben."
}
}
},
"errors": {
"page_error_description": "Diese Seite funktioniert gerade nicht. Versuchen Sie es noch einmal oder gehen Sie zurück zum Posteingang.",
"sidebar_error": "Ihre Ordner konnten nicht geladen werden",
"email_list_error": "Ihre E-Mails konnten nicht geladen werden",
"viewer_error_title": "Diese E-Mail kann nicht angezeigt werden",
"viewer_error_description": "Etwas in dieser E-Mail verhindert, dass sie hier angezeigt wird. Vielleicht nutzt sie ein Format, das wir nicht unterstützen.",
"composer_error": "Das Fenster für neue E-Mails konnte nicht geöffnet werden",
"settings_error_title": "Die Einstellungen konnten nicht geöffnet werden",
"settings_error_description": "Ihre Einstellungen konnten nicht geladen werden. Was Sie jetzt ändern, wird vielleicht nicht gespeichert."
},
"identities": {
"delete_confirm": "Diese Absenderadresse löschen? Das können Sie nicht rückgängig machen.",
"cannot_delete": "Diese Absenderadresse können Sie nicht löschen",
"validation_errors": {
"invalid_emails": "Diese E-Mail-Adressen sind nicht richtig: {emails}"
},
"form": {
"email_invalid": "Geben Sie eine richtige E-Mail-Adresse ein",
"email_immutable": "Die E-Mail-Adresse können Sie nach dem Hinzufügen nicht mehr ändern",
"signature_byte_limit_reached": "Die Signatur ist zu lang für den Server"
},
"sub_address": {
"invalid_tag": "Verwenden Sie im Stichwort nur Buchstaben, Ziffern und Bindestriche (-)"
}
},
"calendar": {
"import": {
"url_description": "Geben Sie die Webadresse einer Kalenderdatei (.ics) ein, um ihre Termine hinzuzufügen."
},
"management": {
"description": "Erstellen Sie neue Kalender, benennen Sie sie um und ändern Sie ihre Farbe. Sie können auch mit der rechten Maustaste auf einen Kalender in der Seitenleiste klicken, um die Farbe zu ändern.",
"description_touch": "Erstellen Sie neue Kalender, benennen Sie sie um und ändern Sie ihre Farbe.",
"confirm_delete": "„{name}“ löschen? Alle Termine in diesem Kalender werden auch gelöscht.",
"confirm_clear": "Alle Termine in „{name}“ löschen? Das können Sie nicht rückgängig machen.",
"kind_hint": "Das legt fest, welche Apps auf Ihren anderen Geräten diesen Kalender zeigen. Sie können es später nicht ändern.",
"default_alerts_hint": "Neue Termine in diesem Kalender bekommen diese Erinnerungen, außer Sie ändern sie im Termin. Eine Änderung hier ändert keine Termine, die Sie schon haben. Erinnerungen per E-Mail schickt der Mailserver."
},
"subscription": {
"description": "Folgen Sie einem Kalender aus dem Internet, zum Beispiel mit Feiertagen. Seine Termine werden von selbst aktualisiert und in einem eigenen Kalender gezeigt. Die Adresse beginnt mit https:// oder webcal://.",
"confirm_delete": "„{name}“ nicht mehr folgen? Der Kalender und alle seine Termine werden entfernt."
}
},
"sharing": {
"description": "Erlauben Sie anderen Personen oder Gruppen auf diesem Server den Zugriff. Die Änderung gilt sofort.",
"directory_restricted": "Ihr Server zeigt keine Liste aller Benutzer. Deshalb sehen Sie nur Ihre Gruppen und Personen, die etwas mit Ihnen teilen. Ihre Administration kann das ändern.",
"directory_not_browsable": "Ihr Server zeigt keine Liste aller Benutzer.",
"directory_failed": "Die Liste der Personen und Gruppen konnte nicht geladen werden. Versuchen Sie es noch einmal.",
"share_failed": "Die Freigabe konnte nicht geändert werden. Versuchen Sie es noch einmal."
},
"delegates": {
"description": "Erlauben Sie jemandem in einem Schritt den Zugriff auf Ihre E-Mails, Kalender und Kontakte. Sehen Sie, wer worauf Zugriff hat.",
"read_failed": "Ein Teil Ihrer Freigaben konnte nicht geladen werden. Sie können Zugriffe ändern oder entfernen, sobald alles geladen ist.",
"remove_message": "{name} hat dann keinen Zugriff mehr auf alles, was Sie aus Ihrem Konto geteilt haben: E-Mails, Kalender, Kontakte und Dateien.",
"not_found": "Auf diesem Server gibt es niemanden mit der Adresse {address}.",
"lookup_failed": "Diese Adresse wurde nicht gefunden. Prüfen Sie sie und versuchen Sie es noch einmal.",
"known_only": "Sie sehen nur Ihre Gruppen und Personen, die etwas mit Ihnen geteilt haben.",
"directory_off_note": "Um jemand Neues hinzuzufügen, muss Ihre Administration zuerst die Suche nach Benutzern auf dem Server erlauben.",
"directory_failed": "Die Liste der Personen und Gruppen konnte nicht geladen werden. Versuchen Sie es noch einmal.",
"nothing_to_change": "Es hat sich nichts geändert. Wählen Sie, was die Person bekommen soll, oder klicken Sie auf „Zugriff entfernen“.",
"change_note": "Ordner, Kalender und Adressbücher, die Sie nicht auswählen, sind nicht mehr geteilt. Geteilte Dateien bleiben geteilt. „Zugriff entfernen“ nimmt auch diese weg.",
"rights_keep_hint": "Was die Person schon hat, bleibt gleich. Alles Neue bekommt dieselben Rechte wie der Rest dieser Art, oder nur Lesen.",
"send_note": "Personen mit Zugriff können Ihre E-Mails und Ihren Kalender lesen und ordnen. Sie können keine E-Mails als Sie oder für Sie senden. Wenn mehrere Personen eine Adresse brauchen, fragen Sie Ihre Administration nach einem geteilten Postfach.",
"partial_title": "Einiges konnte nicht geändert werden. Alles andere ist erledigt."
},
"smime": {
"your_certificates_desc": "Fügen Sie Ihre Zertifikate hinzu und verwalten Sie sie. Sie brauchen eines, um E-Mails mit S/MIME zu signieren oder zu verschlüsseln.",
"recipient_certificates_desc": "Zertifikate anderer Personen. Damit können Sie diesen Personen verschlüsselte E-Mails senden.",
"identity_bindings_desc": "Wählen Sie, welches Zertifikat zu welcher Ihrer Absenderadressen gehört",
"defaults_desc": "Wählen Sie, ob neue E-Mails von Anfang an signiert oder verschlüsselt werden",
"encrypt_by_default_desc": "Eine E-Mail von selbst verschlüsseln, wenn Sie für jeden Empfänger ein Zertifikat haben",
"remember_unlocked_desc": "Ihre Schlüssel entsperrt lassen, bis Sie diesen Browser schließen",
"p12_passphrase_desc": "Geben Sie das Passwort dieser Zertifikatsdatei ein",
"storage_passphrase_desc": "Wählen Sie ein Passwort. Es schützt diesen Schlüssel, solange er in Ihrem Browser gespeichert ist.",
"unlock_key_desc": "Geben Sie das Passwort dieses Schlüssels ein, um E-Mails zu signieren oder zu öffnen",
"cert_expired": "Dieses Zertifikat ist nicht mehr gültig",
"status_encrypted_no_key": "Diese E-Mail ist verschlüsselt, aber Ihnen fehlt der Schlüssel, um sie zu öffnen",
"status_encrypted_failed": "Diese E-Mail ist verschlüsselt und konnte nicht entschlüsselt werden. Vielleicht fehlt Ihnen der passende Schlüssel.",
"status_signed_invalid": "Die Signatur ist ungültig. Die E-Mail wurde vielleicht verändert.",
"status_signed_expired_cert": "Mit einem Zertifikat signiert, das nicht mehr gültig ist",
"status_signed_self_signed": "Die Signatur ist gültig. Der Absender hat das Zertifikat aber selbst erstellt, also hat niemand geprüft, wer er ist.",
"status_signed_mismatch": "Die Signatur ist gültig, gehört aber zu jemand anderem als dem Absender",
"auto_import_signer_certs_desc": "Wenn eine signierte E-Mail geprüft ist, das Zertifikat des Absenders speichern. Dann können Sie ihm später verschlüsselte E-Mails senden.",
"export_passphrase_desc": "Wählen Sie ein Passwort, um die gespeicherte Datei zu schützen",
"export_storage_desc": "Geben Sie das Passwort dieses Schlüssels ein, um ihn als Datei zu speichern"
},
"recover_deleted": {
"load_failed": "Gelöschte E-Mails konnten nicht geladen werden. Versuchen Sie es noch einmal."
}
}
+2049 -507
View File
File diff suppressed because it is too large Load Diff
+793
View File
@@ -0,0 +1,793 @@
{
"login": {
"jmap_endpoint_cors_hint": "The mail server must allow sign-ins from this website. Your administrator can set this up.",
"reconnecting": "The connection is lost. We are trying to connect again…",
"error": {
"invalid_credentials": "The email address or password is wrong. Check both and try again.",
"invalid_token": "The server did not accept this access token. Maybe it is not complete, or it is no longer valid. Copy it again or create a new one.",
"connection_failed": "We can't reach the mail server. Check your internet connection and try again.",
"cors_blocked": "The mail server works, but it does not allow sign-ins from this website. Ask your administrator to allow this website on the server.",
"server_error": "The mail server has a problem right now. Try again in a few minutes.",
"generic": "Something went wrong. Try again. If it keeps happening, ask your administrator for help.",
"totp_required": "Your account needs a second code to sign in. Open your authenticator app and type the 6-digit code below.",
"totp_invalid": "This code is wrong or too old. Type the code your authenticator app shows right now.",
"oauth_discovery_failed": "Sign-in through your organisation does not work right now. Try again later or ask your administrator.",
"token_exchange_failed": "Your password and code are correct, but the mail server did not let this app in. Ask your administrator to check the sign-in settings for this app."
},
"totp_toggle": "I have a code from my authenticator app",
"token_hint": "Some mail providers give you a token instead of a password. Create one in your mail provider's settings, copy it, and paste it here.",
"config_error": {
"title": "The webmail is not set up correctly",
"fetch_failed": "The webmail could not load its settings. Try again in a few minutes.",
"server_not_configured": "No mail server is set up yet. Ask your administrator to set one up."
},
"session_expired": "You were signed out. Sign in again to continue.",
"oauth_error": {
"title": "Sign-in did not work",
"invalid_state": "The sign-in was interrupted. Start again.",
"missing_params": "Some sign-in data is missing. Start again.",
"token_exchange_failed": "We could not finish signing you in. Try again.",
"access_denied": "You are not allowed to sign in here. Ask your administrator for access."
}
},
"session_limits": {
"idle_message": "You haven't done anything for a while. You will be signed out in {seconds, plural, one {# second} other {# seconds}}. Messages you are writing are saved as drafts.",
"max_message": "Your organisation signs you out {hours, plural, one {# hour} other {# hours}} after you sign in. You will be signed out in {seconds, plural, one {# second} other {# seconds}}. Messages you are writing are saved as drafts.",
"reason_max": "You were signed out because you were signed in for {hours, plural, one {# hour} other {# hours}}. That is the longest time your organisation allows.",
"drafts_not_saved": "Some messages you were writing could not be saved as drafts."
},
"totp_reauth": {
"title": "Enter a new code",
"description": "Your last code is no longer valid. Open your authenticator app and type the new code to continue.",
"oauth_hint": "This question comes back from time to time. Your administrator can stop it by turning on a different way to sign in (OAuth).",
"code_label": "Code from your authenticator app"
},
"email_list": {
"permanent_delete_confirm_message": "This message will be deleted for good. You can't get it back.",
"permanent_delete_confirm_batch_message": "{count, plural, one {This message} other {These # messages}} will be deleted for good. You can't get {count, plural, one {it} other {them}} back.",
"empty_folder": {
"confirm_message": "All messages in this folder will be deleted for good. You can't get them back."
}
},
"email_viewer": {
"read_receipt": {
"send_failed": "The read receipt could not be sent"
},
"archive_mailbox_not_found": "There is no Archive folder, so the message can't be archived",
"source_load_failed": "The raw message could not be loaded. Try again, or save the message as a file (.eml).",
"spam": {
"error": "The message could not be marked as spam. Try again.",
"error_not_spam": "The message could not be moved back. Try again."
},
"unsubscribe_banner": {
"error": "Unsubscribing did not work. Try again later."
},
"calendar_invitation": {
"parse_error": "This invitation can't be read",
"action_failed": "That did not work in your calendar. Try again."
},
"undo_send_failed": "Sending could not be stopped. The message may already be sent.",
"undo_send_signed_out": "Sending could not be stopped, because you are no longer signed in to the account that sends it.",
"send_cancelled_restore_failed": "Sending was stopped. But the message could not be put back into Drafts. It is still in Sent.",
"send_cancelled_remove_failed": "Sending was stopped. But the message could not be removed from Sent.",
"cancel_scheduled_failed": "The planned sending could not be stopped. Try again.",
"save_to_files": {
"failed": "The file could not be saved in Files. Try again."
}
},
"email_composer": {
"files_picker": {
"load_failed": "Your files could not be loaded. Try again."
},
"save_failed": "The draft could not be saved. Try again.",
"validation": {
"invalid_recipients": "Not sent. These addresses are not correct: {addresses}",
"attachment_upload_failed": "Not sent. An attachment could not be uploaded. Remove it and try again."
},
"upload_failed": "{filename} could not be uploaded. Try again.",
"send_failed": "Your message was not sent. Try again.",
"send_offline": "You are not connected to the internet. Your message is still here. Send it again when you are back online.",
"send_timeout": "The server stopped answering. Your message may have been sent anyway. Look in your Sent folder before you send it again.",
"submission_limit": "The server did not send the message because you reached a limit: {reason}",
"send_plugin_failed": "Not sent. An add-on that sends the message did not work or took too long. Look in your Sent folder before you send it again.",
"schedule_send_invalid": "Enter a date and a time.",
"schedule_send_cleanup_warning": "The message will be sent at the chosen time. But the draft could not be removed, so it is still in Drafts.",
"invalid_recipient": "This is not a correct email address",
"send_filing_warning": "Sent. But an old draft of this message may still be in Drafts.",
"send_guard_failed": "Not sent. An add-on that checks messages before sending did not work or did not answer.",
"attachment_upload_failed": "Could not upload"
},
"common": {
"reconnecting": "The connection is lost. We are trying to connect again…"
},
"notifications": {
"error_archiving": "The message could not be archived. Try again.",
"error_updating": "The message could not be changed. Try again.",
"error_sending": "Your message was not sent. Try again.",
"error_deleting": "The message could not be deleted. Try again.",
"error_loading": "Your messages could not be loaded. Try again.",
"move_failed": "Could not move. Try again.",
"undo_failed": "Could not undo. Try again.",
"copy_failed": "Could not copy. Try again.",
"move_error": "The messages could not be moved to this folder. Try again.",
"tag_failed": "The label could not be added. Try again.",
"identity_create_failed": "The sender address could not be added: {error}",
"identity_update_failed": "The sender address could not be changed: {error}",
"identity_delete_failed": "The sender address could not be deleted: {error}",
"identity_unauthorized": "You are not allowed to send from this email address. Ask your administrator if you need it.",
"vacation_save_failed": "Your automatic reply could not be saved. Try again.",
"filters_save_failed": "Your filter rules could not be saved. Try again.",
"export_email_error": "The message could not be saved as a file. Try again.",
"import_email_error": "The message could not be added from the file. Check the file and try again.",
"rule_apply_failed": "The rule could not be used for the messages that are already here",
"rule_folder_failed": "The folder could not be created. Try again.",
"vacation_filters_save_failed": "Your automatic reply is saved. But the forwarding and the reply list could not be saved. Try again."
},
"settings": {
"reset_confirm": "Put all settings back to how they were at the start?",
"themes": {
"description": "Choose a look. Your administrator may add more themes.",
"locked": "Your administrator chose the theme. You can't change it.",
"forced": "Your administrator chose the theme \"{name}\". You can't change it."
},
"appearance": {
"message_list_order": {
"description": "The order of the messages in the list. The server sorts the whole folder, not only the messages you can see.",
"tiebreak_note": "You can choose up to three steps. A later step only counts when the earlier steps are the same. At the end, the newest messages always come first.",
"scope": {
"description": "When this is off, the order is only used in the Inbox. Other folders show the newest messages first."
},
"unsupported_note": "Your mail server can't sort by read or unread, star or tag. Those steps are left out, and the newest messages come first."
},
"description": "Change how the webmail looks",
"theme": {
"description": "Choose light or dark colours"
},
"font_size": {
"description": "Make the text bigger or smaller"
},
"list_density": {
"description": "How much space there is between items"
},
"animations": {
"description": "Let things move softly when they open or close"
},
"toolbar_position": {
"description": "Where the buttons for a message are, like Reply, Archive or Delete"
},
"toolbar_labels": {
"description": "Show words next to the button icons. Turn this off to save space when you know the icons."
},
"hide_account_switcher": {
"description": "Hide the account list at the top of the folder list. You can still change accounts at the bottom of the left bar."
},
"show_rail_account_list": {
"label": "Show your accounts in the left bar",
"description": "Show a circle for each account at the bottom of the left bar, so you can change accounts quickly. A button to sign out is below them."
},
"unified_mailbox": {
"description": "Show folders that combine the mail of several accounts, like one Inbox for all. You need more than one account (or a group inbox below) to see them.",
"cross_account": {
"description": "Combine the mail of all your accounts, not only the account you use now."
},
"include_group": {
"description": "Also add the inboxes you share with a group."
}
},
"all_mail": {
"folders_description": "Choose which folders of this account are part of the lists All mail, Unread and Starred."
},
"colorful_sidebar_icons": {
"description": "Give folder and tag icons colours (blue Inbox, red Spam, green Sent and so on). Turn this off for grey icons."
},
"tint_list_rows": {
"description": "Colour each message in the list with the colour of its first tag. The tag colour still shows in the small dots when this is off."
},
"show_folder_total_count": {
"description": "Show how many messages a folder or tag has in total, next to the number of unread ones."
},
"favicon_unread_badge": {
"description": "Show the number of unread messages on the browser tab and on the app icon. Then you see new mail even when you use another tab."
},
"show_shared_mail_accounts": {
"description": "Show the folders of mailboxes that others share with you, under Shared in the folder list. Turn this off to hide them all, or hide single accounts below. Their calendars and contacts stay."
},
"pro_interface": {
"description": "A layout for large screens with many tabs and several accounts at once. You can go back to the normal layout at any time."
},
"account_marker": {
"description": "In the combined folders, mark each message with the colour of its account."
}
},
"keywords": {
"description": "Make tags to sort your messages. The tags are saved with your messages on the server.",
"migration_error": "The tag could not be changed on your messages. Try again.",
"filters_migration_error": "Some filter rules still use the old tag. Change them yourself.",
"nesting": {
"description": "Put tags inside other tags, like folders. The sidebar shows them as a tree."
},
"has_children_locked": "Other tags are inside this tag. So you can't rename or move it. First move or delete the tags inside it.",
"has_children_delete": "First delete the tags inside this tag",
"unrecognized": {
"description": "This browser keeps the names and colours of your tags. The tags themselves are saved on your messages. Search your mail for tags that are missing here and add them again.",
"partial": "Only your {scanned} newest messages out of {total} were searched. Tags that are only on older mail may be missing.",
"error": "Your mail could not be searched for tags. Try again."
}
},
"notifications": {
"push": {
"description": "Get a message on your screen about new mail, even when this website is closed. The service that passes on the message never sees your mail.",
"relay_label": "Notification service",
"relay_desc": "The service that passes notifications on to your device. Your administrator decides which ones you can choose.",
"relay_locked_desc": "Your administrator chose the notification service. You can't change it.",
"status_unsupported": "This browser can't show notifications when the website is closed",
"status_lite": "This version of the webmail can't send notifications when it is closed.",
"reenable": "Turn on again",
"ios_hint": "On iPhone and iPad, first add this website to your home screen. Safari only sends notifications for websites on the home screen.",
"devices_title": "Devices that get notifications",
"devices_desc": "All devices that get notifications for this account. If you remove a device, it stops getting them at once.",
"devices_error": "The list of devices could not be loaded. Try again.",
"device_status_unknown": "The notification service does not know this device",
"revoke": "Remove",
"confirm_revoke_title": "Remove this device?",
"confirm_revoke_message": "It stops getting notifications at once. To get them again, turn notifications on again on that device.",
"confirm_revoke_message_this": "This device stops getting notifications at once.",
"shared_mailboxes_no_push": "You don't get notifications for mailboxes that others share with you.",
"error_forbidden": "The mail server does not allow notifications for this account.",
"error_over_quota": "Too many devices get notifications for this account (the limit is usually 15). Remove devices you don't use any more, then try again."
},
"push_all": {
"description": "Turn notifications on or off for several accounts at once.",
"result_partial": "Turned on for {enabled} accounts. For {failed} accounts, it did not work."
},
"sound_selection": {
"choose_desc": "Choose a sound. Click the speaker to hear it."
},
"email": {
"description": "Choose how you hear about new mail",
"inbox_only_desc": "Only tell me about mail in the Inbox, not about mail that a filter puts into another folder"
},
"calendar": {
"description": "Choose how you hear about your appointments"
}
},
"language_region": {
"description": "Language, how dates and times look, and other things for your country",
"date_locale": {
"description": "The order of day, month and year in dates"
},
"time_format": {
"description": "Show times like 3:00 PM (12 hours) or like 15:00 (24 hours)"
},
"time_zone": {
"description": "Show the times of mail and appointments in this time zone. If you don't choose one, the time zone of your browser is used."
}
},
"email_behavior": {
"inbox_type": {
"description": "With \"Unread first\", your 25 newest unread messages are shown at the top of the inbox"
},
"clear_search_on_folder_change": {
"description": "When you open another folder, your search ends and you see the whole folder. When this is off, the search continues in the new folder."
},
"request_read_receipt": {
"label": "Always ask for a read receipt",
"description": "New messages ask the recipient to tell you when they have opened the message. You can turn this off for each message."
},
"read_receipt_response": {
"label": "When someone asks for a read receipt",
"description": "Some senders ask to be told when you open their message. Choose what to do then."
},
"description": "Choose what happens with your messages",
"mark_read": {
"description": "When an opened message counts as read"
},
"delete_action": {
"description": "What happens when you delete a message",
"warning": "Deleted messages are gone for good. You can't get them back."
},
"message_spacing": {
"description": "The space around the text of an open message"
},
"archive_mode": {
"description": "How archived messages are put into folders"
},
"permanently_delete_junk": {
"label": "Delete spam for good",
"description": "When you delete a message in the Spam folder, it is gone for good. It does not go to the Trash first."
},
"confirm_bulk_delete": {
"description": "Ask before several selected messages go to the Trash. Deleting for good always asks."
},
"mail_layout": {
"description": "Choose how the message list and the open message are placed on the screen.",
"split_description": "The message list and the open message are side by side.",
"focus_description": "Each message takes one line. An open message fills the whole width. The folder list stays visible.",
"horizontal_description": "The message list is at the top. The open message is shown below it."
},
"list_columns": {
"description": "Show the list as a table with the columns From, Subject, Date and Size."
},
"show_verification_codes": {
"description": "When a message contains a sign-in or confirmation code, show a button to copy it. In the list, the button stays for one day."
},
"plain_text_mode": {
"description": "Write all messages as plain text, without bold, colours or pictures. This also applies to replies and forwards."
},
"auto_select_reply_identity": {
"description": "When you reply, the reply is sent from the address the message was sent to. This works for all your own addresses. Below, you can choose if other addresses on your domain count too."
},
"reply_identity_match": {
"label": "Which addresses count as yours",
"description": "\"Exact address only\" uses only the addresses you set up. \"Any address on my domains\" also replies from other addresses on your domains. Choose the exact address if those other addresses are group lists."
},
"signature_position": {
"description": "Where your signature goes when you reply or forward. Above the quoted text, it ends your reply. Below, the old message stays in one piece."
},
"signature_separator": {
"label": "Line before the signature",
"description": "Put the usual \"-- \" line before your signature. Many mail apps use it to recognise the signature."
},
"sub_address_delimiter": {
"label": "Character before the address tag",
"description": "The character between your name and the tag in an address, like user{delimiter}tag@domain.com. Use the same character as your mail server."
},
"attachment_click_action": {
"label": "When you click an attachment",
"description": "Choose if a click on an attachment shows it or saves it to your device"
},
"attachment_position": {
"description": "Where attachments are shown in an open message"
},
"always_light_mode": {
"description": "Show messages with a light background even when the app is dark. Some messages look wrong when they are made dark."
},
"external_content": {
"label": "Pictures from the internet",
"description": "Choose if pictures that a message loads from the internet are shown. Loading them can tell the sender that you opened the message."
},
"trusted_senders": {
"description": "Senders whose pictures are always shown",
"empty_description": "When a message has blocked pictures, click \"Always trust this sender\" to add the sender here.",
"save_error": "The list could not be saved. Try again.",
"use_address_book_label": "Keep the list in your address book",
"use_address_book_description": "Save trusted senders in their own address book. Then the list is the same on all your devices."
},
"hover_actions": {
"description": "The buttons you see when you move the mouse over a message in the list"
},
"default_mail_program": {
"description": "Open email links on websites (mailto: links) in {appName}",
"error": "Your browser can't do this"
},
"attachment_reminder": {
"description": "If your message talks about an attachment but nothing is attached, you get a warning before it is sent",
"keywords_label": "Words that start the reminder",
"keywords_description": "If your message contains one of these words, you get the reminder"
},
"placeholder_warning": {
"description": "Ask before sending if the message still has gaps to fill in, like [TIME] or [Your Name], empty template fields, or notes like TODO."
},
"recipient_mentions": {
"description": "Type @ in your message to pick one of its recipients. Their first name is put into the text. Press Enter or Tab to pick the marked name."
},
"mention_adds_recipient": {
"description": "When you type @, you can also pick people from your contacts. If they are not a recipient yet, they are added to the message."
},
"hide_inline_image_attachments": {
"label": "Don't list pictures in the text as attachments",
"description": "Pictures that are part of the message text are not shown again in the list of attachments"
},
"attachment_image_previews": {
"description": "Show small pictures of attached images instead of a file icon"
},
"send_delay": {
"description": "Your messages wait a few seconds before they are sent. In that time, you can still stop them.",
"unsupported": "This account's mail server can't wait before sending. The setting is still saved for your other accounts."
},
"swipe_right_action": {
"description": "On a touch screen: what happens when you swipe a message to the right."
},
"swipe_left_action": {
"description": "On a touch screen: what happens when you swipe a message to the left."
},
"rtl_editing": {
"label": "Writing from right to left",
"description": "Adds a button to the editor. With it, you can write a paragraph from left to right or from right to left."
}
},
"account": {
"description": "See the details of your account",
"auth_method_label": "How you sign in",
"auth_method_oauth": "Sign-in page of your organisation",
"server": {
"label": "Mail server address"
},
"accounts": {
"description": "Drag the accounts to change their order in the account list"
},
"backup": {
"description": "Save the list of your accounts on the server and get it back on another device. A password that only you know protects the list. Mailbox passwords are not saved, so you sign in to each account again.",
"description_passwords": "Save the list of your accounts on the server and get it back on another device. A password that only you know protects the list. You can also save the mailbox passwords. Then the accounts sign in by themselves.",
"password_hint": "Use at least 10 characters. Nobody can reset this password. If you forget it, save the list again with a new password.",
"restore_hint": "The password you chose when you saved the list.",
"include_passwords_hint": "After you get the list back, these accounts sign in by themselves. But be careful: anyone who knows the backup password can open every mailbox in the list.",
"skipped": "{count, plural, one {# account was} other {# accounts were}} not added because you already have the most accounts allowed.",
"sign_in_failed": "{count, plural, one {# saved password did not work} other {# saved passwords did not work}}. Sign in to these accounts yourself from the account list.",
"errors": {
"unlock_failed": "The password is wrong, or this list belongs to a different account.",
"owner_signin_required": "Sign in to this account again. Then try once more.",
"https_required": "This only works on a secure connection (the address starts with https).",
"storage_failed": "The server could not save or open the list. Try again later."
}
},
"shared_accounts": {
"description": "Accounts that you share with others and may manage. Choose one to change its filters, its automatic replies, its calendars and its contacts."
}
},
"security": {
"description": "Change your password and protect your account",
"not_available": "You can't change the security settings for this mail server here. Your server may not allow it. The <a href=\"https://bulwarkmail.org/docs/guides/account-security\" class=\"underline hover:opacity-80\" target=\"_blank\">documentation</a> explains more.",
"password": {
"error_title": "Your password was not changed",
"error_mismatch": "The two new passwords are not the same. Type them again.",
"error_min_length": "Your password needs at least 8 characters",
"error_generic": "Your password could not be changed. Try again."
},
"display_name": {
"description": "The name the mail server shows for you",
"error": "Your name could not be changed. Try again."
},
"totp": {
"label": "Code from an authenticator app",
"description": "Protect your account better: when you sign in, you also type a code from an app on your phone",
"enable_error": "Two-factor authentication could not be turned on. Try again.",
"disable_error": "Two-factor authentication could not be turned off. Try again.",
"setup_instructions": "Open your authenticator app (for example Google Authenticator or Authy) and add this link to it:",
"verification_code": "Code from the app",
"disable_confirm_prompt": "To turn off two-factor authentication, type your password.",
"password_required": "Type your password",
"code_required": "Type the code from the app",
"code_invalid": "This code is wrong or too old. Type the code the app shows right now."
},
"app_passwords": {
"description": "Extra passwords for mail apps that can't ask for a second code, like some phone mail apps",
"allowed_ips_label": "Only from these internet addresses (optional)",
"allowed_ips_hint": "Put a comma or a space between addresses. Leave it empty to allow every address.",
"copy_now_warning": "Copy this password now. You won't see it again.",
"add_error": "The app password could not be created. Try again.",
"remove_error": "The app password could not be deleted. Try again.",
"delete_message": "Apps that use {name} will not be able to sign in any more."
},
"api_keys": {
"description": "Keys for programs and scripts that work with your mailbox without you, for example a backup",
"copy_now_warning": "Copy this key now. You won't see it again.",
"add_error": "The key could not be created. Try again.",
"remove_error": "The key could not be deleted. Try again.",
"delete_message": "Programs and scripts that use {name} will stop working."
},
"encryption": {
"label": "Store mail encrypted",
"description": "The server stores your mail in a locked form. Only your key can unlock it.",
"error": "The encryption setting could not be changed. Try again.",
"algorithm_label": "Type of encryption",
"encrypt_on_append": "Also encrypt mail that apps add to your mailbox",
"allow_spam_training": "Let the spam filter learn from new mail before it is encrypted",
"disable_message": "From now on, the server stores new mail without encryption."
},
"public_keys": {
"key_label": "Public key (S/MIME or PGP, as text)",
"add_error": "The public key could not be added. Check that you pasted all of it.",
"remove_error": "The public key could not be deleted. Try again."
},
"email_client": {
"description": "Use these details to set up mail on your computer or phone, for example in Thunderbird, Apple Mail or Outlook",
"jmap_username_label": "User name for mail apps",
"password_instructions": "In your mail app, use the user name above and an app password. You can create an app password further down on this page."
},
"link_device": {
"description": "Sign in to the Bulwark Mail app on your phone without typing your password. Your phone gets its own sign-in. If you sign out here, your phone stays signed in.",
"error": "The code could not be created. Try again.",
"password_prompt": "Type your password to show it's you. Your password is not sent to the phone.",
"error_not_signed_in": "You were signed out. Sign in again, then link your phone.",
"error_impersonation": "You are looking at someone else's mailbox right now. You can't link a phone from here.",
"error_too_many_attempts": "You tried too often. Wait a moment, then try again.",
"error_session_secret": "Linking a phone is not set up on this server. Ask your administrator.",
"error_server_unreachable": "We can't reach your mail server. Try again.",
"error_pairing_unavailable": "Your mail server does not allow linking a phone this way. Ask your administrator.",
"error_insecure_server": "The phone app needs a secure connection to the mail server (https). This server does not have one."
},
"sections": {
"devices_description": "Read your mail in other apps and on other devices."
}
},
"identities": {
"description": "The email addresses you can send from",
"identities_count": {
"label": "Your sender addresses",
"description": "Addresses you can send mail from"
},
"sub_addressing": {
"label": "Addresses with a tag",
"description": "Add a tag to your address, like user+shop@domain.com. Mail to it still reaches you, and you can sort it by the tag."
}
},
"vacation": {
"description": "Answer new mail by yourself while you are away",
"not_supported": "Your mail server can't send automatic replies.",
"fetch_error": "Your automatic reply could not be loaded. Try again.",
"status": {
"description": "Everyone who writes to you gets an automatic reply"
},
"date_range": {
"start_description": "Leave it empty to start right away"
},
"message": {
"subject_description": "The subject of the automatic reply",
"html_description": "Add a version with links and formatting. People whose mail app can't show it get the plain text above."
},
"warnings": {
"end_before_start": "The end date must come after the start date",
"invalid_date": "Enter a start and end date with a time, or leave the field empty",
"empty_body": "The message is empty. People would get a reply with no text.",
"subject_too_long": "The subject is too long for the server. Make it shorter (at most {max} bytes).",
"body_too_long": "The message is too long for the server. Make it shorter (at most {max} bytes, with formatting).",
"forward_address": "Enter the email address to forward to",
"not_running": "Forwarding and the reply list are saved, but they are off right now. Click Save to turn them on again."
},
"forward": {
"description": "Send new mail on to another address during the time above, with or without the automatic reply. Without an end date, this goes on until you turn it off. Spam is not forwarded.",
"keep_description": "If this is off, the message is only forwarded. It is not kept in this mailbox, and your filter rules don't run on it."
}
},
"folders": {
"description": "Manage your folders and choose which folder is used for what",
"folder_list_description": "Click the icon of a folder to change it",
"standard_roles_description": "Choose which folder is your Inbox, Sent, Trash and so on.",
"reorder": "Drag up or down to change the order. Drag left or right to put the folder in another folder.",
"move_partial_error": "{count, plural, one {# folder} other {# folders}} could not be moved. Try again.",
"confirm_delete": "Delete \"{name}\"? The messages in this folder go to the Trash.",
"cannot_delete_role": "This folder has a special job, like Inbox or Trash. Remove the job first, then you can delete it.",
"error_delete_has_children": "The folder was not deleted because there are other folders inside it. Delete or move them first.",
"error_delete_has_email": "The folder was not deleted because there are still messages in it. Move or delete them first.",
"error_role": "The job of the folder could not be changed. Try again."
},
"advanced": {
"debug_mode": {
"description": "Record more details about what the app does. This helps to find problems."
},
"settings_sync": {
"label": "Same settings on all devices",
"description": "Save your settings on the server, so all your browsers and devices use them"
},
"sender_favicons": {
"description": "For companies, show the icon of their website as the sender's picture",
"refresh_description": "Load the website icons again, for example when a company has a new one"
},
"show_avatars_in_junk": {
"label": "Show sender pictures in the Spam folder",
"description": "Show the sender's picture or website icon in the Spam folder too. This is off at first, because fake messages can look more real with a picture."
},
"keyboard_shortcuts": {
"description": "See all keys you can use instead of the mouse"
},
"refresh_cache": {
"label": "Load data from the server again",
"description": "Load your contacts, calendars and folders from the server again. Use this when something looks old or wrong. You stay signed in.",
"confirm_title": "Load data from the server again?"
},
"reset_settings": {
"description": "Put all settings back to how they were at the start"
},
"export_settings": {
"description": "Save your settings as a file on your device"
},
"import_settings": {
"description": "Load your settings from a file you saved before"
},
"sections": {
"backup_description": "Use the same settings in other browsers and on other devices.",
"fix_description": "When something looks old or wrong, or you want to start again."
}
},
"sidebar_apps": {
"description": "Add your own apps and links to the left bar",
"keep_loaded_description": "Keep apps open in the background when you switch, so they don't have to load again",
"managed_description": "Your administrator set up these apps for everyone. You can't change them."
},
"contacts": {
"group_by_letter_description": "Put a letter above each group of names in the contact list",
"sort_by_last_name_description": "Sort contacts by family name, so families are next to each other",
"import_description": "Add contacts from a contacts file (.vcf)",
"export_description": "Save all contacts in a contacts file (.vcf)"
},
"downloads": {
"description": "Choose the file names for messages and attachments you save.",
"email_template": {
"description": "The file name when you save a message or drag it to a folder on your computer. The ending .eml is added for you."
},
"attachment_template": {
"description": "The file name when you save an attachment or drag it out. If you leave out '{filename}' and '{ext}', the file keeps its ending."
},
"bundle_template": {
"description": "The file name when you save several messages together in one .zip file. The ending .zip is added for you."
},
"spaces": {
"description": "Use another character instead of spaces in the file name."
},
"lowercase": {
"description": "Write the whole file name in small letters."
},
"strip_diacritics": {
"description": "Change letters with accents into plain letters (ä → a, é → e). Some programs can't handle letters with accents."
},
"collapse_separators": {
"description": "When there are several spaces, _ or - in a row, keep only one."
},
"after_export": {
"description": "You can move the message to another folder after you save it as a file."
}
},
"filters": {
"description": "Rules that sort new mail by themselves, for example into a folder",
"no_rules_description": "Make a rule, and new mail is sorted by itself",
"vacation_active_description": "New mail gets an automatic reply",
"delete_confirm": "Delete this rule?",
"raw_editor": "Edit rules as code (Sieve)",
"raw_editor_warning": "If you change the code, you may not be able to edit the rules here with the normal editor any more. Your changes in the code replace the normal rules.",
"not_supported": "Your mail server can't use filter rules.",
"match_all": "All conditions must be true",
"match_any": "One condition is enough",
"stop_processing": "Don't check the rules below this one",
"value_placeholder_multi": "Value (put commas between several)",
"condition_fields": {
"header": "Other header line",
"body": "Message text"
},
"comparators": {
"matches": "matches the pattern (* = any text)"
},
"forward_limit": "This server forwards a message to at most {count} addresses. Other forwards are left out.",
"forward_unavailable": "This server does not allow forwarding. Rules that forward mail do nothing.",
"header_name": "Name of the header line",
"opaque_warning": "These rules were changed in another app. You can only edit them here as code.",
"open_sieve_editor": "Edit rules as code (Sieve)",
"expanded_view_description": "Show each rule with all its conditions and actions",
"reset_warning": "This deletes all your current rules and starts again with none.",
"validation_empty_conditions": "Add at least one condition and fill in its value",
"validation_empty_actions": "Add at least one action",
"validation_header_name": "Use only the letters A to Z, numbers, - and _ in the name of the header line",
"sieve_editor": {
"title": "Filter rules as code (Sieve)",
"warning": "If you change the code, you may not be able to edit the rules here with the normal editor any more. Your changes in the code replace the normal rules.",
"script_content": "Rules as code",
"valid": "The code has no mistakes",
"invalid": "The code has mistakes",
"validating": "Checking…",
"validate": "Check",
"overwrite_message": "This code no longer contains the rules you made with the normal editor. If you save, it replaces them. You can't undo this.",
"validation_failed": "The code could not be checked. Try again."
},
"managed_by_tooltip": "{source} manages this. Change it in that app, or edit the rules as code.",
"apply_existing_unsupported": "Only some rules can also run on messages that are already here: rules that check the sender, recipients, subject or header lines, and that move, copy, mark as read, star or tag.",
"validation_period_order": "The end date must come after the start date",
"period_hint": "The rule works for messages that arrive in this time. Leave a field empty if there is no start or no end.",
"validation_period_invalid": "Enter a date with a time, or leave the field empty",
"apply_existing_period": "A rule with a time period only works for messages that arrive in that time. So it can't run on messages that are already here."
},
"templates": {
"description": "Save messages you write often and use them again. They can have gaps that you fill in each time.",
"export_import_description": "Save your templates as a file, or move them to another device",
"validation": {
"too_long": "The name can have at most 200 characters"
}
},
"files": {
"sort_direction": {
"description": "From A to Z or from Z to A"
},
"show_thumbnails": {
"description": "Show a small picture of each image instead of an icon"
},
"show_hidden": {
"description": "Show hidden files and folders (their names start with a dot)"
}
},
"reading_page": {
"sections": {
"images_description": "Pictures from the internet can tell the sender that you opened the message."
}
}
},
"errors": {
"page_error_description": "This page stopped working. Try again, or go back to your inbox.",
"sidebar_error": "Your folders could not be loaded",
"email_list_error": "Your messages could not be loaded",
"viewer_error_title": "This message can't be shown",
"viewer_error_description": "Something in this message stops it from being shown here. It may use a format we don't support.",
"composer_error": "The editor for new messages could not be opened",
"settings_error_title": "Settings could not be opened",
"settings_error_description": "Your settings could not be loaded. Changes you make now may not be saved."
},
"identities": {
"delete_confirm": "Delete this sender address? You can't undo this.",
"cannot_delete": "You can't delete this sender address",
"validation_errors": {
"invalid_emails": "These email addresses are not correct: {emails}"
},
"form": {
"email_invalid": "Enter a correct email address",
"email_immutable": "You can't change the email address after you add it",
"signature_byte_limit_reached": "The signature is too long for the server"
},
"sub_address": {
"invalid_tag": "Use only letters, numbers and dashes (-) in the tag"
}
},
"calendar": {
"import": {
"url_description": "Enter the web address of a calendar file (.ics) to add its events."
},
"management": {
"description": "Make new calendars, rename them and change their colour. You can also right-click a calendar in the side bar to change its colour.",
"description_touch": "Make new calendars, rename them and change their colour.",
"confirm_delete": "Delete \"{name}\"? All appointments in this calendar are deleted too.",
"confirm_clear": "Delete all appointments in \"{name}\"? You can't undo this.",
"kind_hint": "This decides which apps on your other devices show this calendar. You can't change it later.",
"default_alerts_hint": "New appointments in this calendar get these reminders, unless you change them in the appointment. A change here does not change appointments you already have. The mail server sends reminders by email."
},
"subscription": {
"description": "Follow a calendar from the internet, for example public holidays. Its appointments are updated by themselves and shown in their own calendar. The address starts with https:// or webcal://.",
"confirm_delete": "Stop following \"{name}\"? The calendar and all its appointments are removed."
}
},
"sharing": {
"description": "Let other people or groups on this server use this. The change works at once.",
"directory_restricted": "Your server does not show a list of all its users. So you only see your groups and people who share something with you. Your administrator can change this.",
"directory_not_browsable": "Your server does not show a list of all its users.",
"directory_failed": "The list of people and groups could not be loaded. Try again.",
"share_failed": "Sharing could not be changed. Try again."
},
"delegates": {
"description": "Let someone use your mail, calendars and contacts, all in one step. See who can use what.",
"read_failed": "Some of your sharing could not be loaded. You can change or remove access when it has loaded.",
"remove_message": "{name} can no longer use anything you shared from your account: mail, calendars, contacts and files.",
"not_found": "There is nobody with the address {address} on this server.",
"lookup_failed": "This address could not be found. Check it and try again.",
"known_only": "You only see your groups and people who shared something with you.",
"directory_off_note": "To add someone new, your administrator first has to allow searching for users on the server.",
"directory_failed": "The list of people and groups could not be loaded. Try again.",
"nothing_to_change": "Nothing has changed. Choose what the person should get, or click Remove access.",
"change_note": "Folders, calendars and address books that you don't choose are no longer shared. Shared files stay shared. Remove access takes them away too.",
"rights_keep_hint": "What they already have stays the same. Anything new gets the same rights as the rest of its kind, or read only.",
"send_note": "People you give access can read and sort your mail and calendar. They can't send mail as you or for you. If several people need one address, ask your administrator for a shared mailbox.",
"partial_title": "Some things could not be changed. Everything else is done."
},
"smime": {
"your_certificates_desc": "Add and manage your certificates. You need one to sign or encrypt mail with S/MIME.",
"recipient_certificates_desc": "Certificates of other people. With them, you can send encrypted mail to these people.",
"identity_bindings_desc": "Choose which certificate belongs to which of your sender addresses",
"defaults_desc": "Choose if new messages are signed or encrypted from the start",
"encrypt_by_default_desc": "Encrypt a message by itself when you have a certificate for every recipient",
"remember_unlocked_desc": "Keep your keys unlocked until you close this browser",
"p12_passphrase_desc": "Enter the password of this certificate file",
"storage_passphrase_desc": "Choose a password. It protects this key while it is saved in your browser.",
"unlock_key_desc": "Enter the password of this key to sign or open messages",
"cert_expired": "This certificate is no longer valid",
"status_encrypted_no_key": "This message is encrypted, but you don't have the key to open it",
"status_encrypted_failed": "This message is locked, and it could not be unlocked. You may not have the right key.",
"status_signed_invalid": "The signature is not valid. The message may have been changed.",
"status_signed_expired_cert": "Signed with a certificate that is no longer valid",
"status_signed_self_signed": "The signature is valid. But the sender made the certificate themselves, so nobody has checked who they are.",
"status_signed_mismatch": "The signature is valid, but it belongs to someone other than the sender",
"auto_import_signer_certs_desc": "When a signed message is checked, save the sender's certificate. Then you can send them encrypted mail later.",
"export_passphrase_desc": "Choose a password to protect the file you save",
"export_storage_desc": "Enter the password of this key to save it as a file"
},
"recover_deleted": {
"load_failed": "Deleted mail could not be loaded. Try again."
},
"global_search": {
"account_error": "{account} could not be searched"
}
}
+1750 -208
View File
File diff suppressed because it is too large Load Diff
+1738 -196
View File
File diff suppressed because it is too large Load Diff
+1739 -197
View File
File diff suppressed because it is too large Load Diff
+1745 -203
View File
File diff suppressed because it is too large Load Diff
+1743 -201
View File
File diff suppressed because it is too large Load Diff
+1741 -199
View File
File diff suppressed because it is too large Load Diff
+1745 -203
View File
File diff suppressed because it is too large Load Diff
+1734 -192
View File
File diff suppressed because it is too large Load Diff
+1739 -197
View File
File diff suppressed because it is too large Load Diff
+1757 -215
View File
File diff suppressed because it is too large Load Diff
+1744 -202
View File
File diff suppressed because it is too large Load Diff
+1737 -195
View File
File diff suppressed because it is too large Load Diff
+1744 -202
View File
File diff suppressed because it is too large Load Diff
+1746 -204
View File
File diff suppressed because it is too large Load Diff
+1748 -206
View File
File diff suppressed because it is too large Load Diff
+769 -35
View File
File diff suppressed because it is too large Load Diff
+774 -35
View File
File diff suppressed because it is too large Load Diff
+774 -35
View File
File diff suppressed because it is too large Load Diff
+771 -35
View File
File diff suppressed because it is too large Load Diff
+491 -45
View File
@@ -3,7 +3,10 @@
"after": "Nach",
"before": "Vor",
"no_attachment": "Ohne Anhang",
"not_starred": "Nicht markiert"
"not_starred": "Nicht markiert",
"advanced_filters_count": "{name}, {count} aktiv",
"larger_than_kb": "Größer als {size} KB",
"smaller_than_kb": "Kleiner als {size} KB"
},
"calendar": {
"alerts": {
@@ -26,7 +29,8 @@
"copy_link_short": "Link",
"export_ics_short": ".ics",
"nothing_planned": "Nichts geplant. Zum Erstellen tippen.",
"tap_to_create": "Tippen Sie auf +, um einen zu erstellen"
"tap_to_create": "Tippen Sie auf +, um einen zu erstellen",
"copy_title_short": "Titel"
},
"fab": {
"event": "Termin",
@@ -70,24 +74,40 @@
"trust_authentication_missing": "Der Absender konnte nicht verifiziert werden.",
"trust_sender_mismatch": "Der Absender weicht vom Organisator des Termins ab.",
"trust_sender_mismatch_unverified": "Der Absender stimmt nicht mit dem Organisator überein, und die Nachricht ist nicht authentifiziert.",
"trust_verified": "Absender verifiziert"
"trust_verified": "Absender verifiziert",
"cancel_outdated": "Ihr Kalender enthält eine neuere Version dieses Termins als diese Absage.",
"remove_error": "Der Termin konnte nicht entfernt werden",
"remove_from_calendar": "Aus Kalender entfernen",
"removed_from_calendar": "Aus Ihrem Kalender entfernt."
},
"management": {
"clear_confirm": "Alle entfernen",
"clear_description": "Alle Termine in diesem Kalender werden gelöscht. Termine, die auch zu einem anderen Kalender gehören, werden nur aus diesem entfernt.",
"clear_title": "Alle Termine entfernen?",
"color_invalid": "Geben Sie eine Farbe als sechs Hex-Ziffern ein, mit oder ohne #, etwa #3b82f6.",
"delete_description": "Der Kalender und alle seine Termine werden gelöscht. Dies kann nicht rückgängig gemacht werden.",
"delete_title": "Kalender löschen?",
"description_field": "Beschreibung",
"description_placeholder": "Optional",
"edit_calendar": "Kalender bearbeiten",
"new_calendar": "Neuer Kalender"
"new_calendar": "Neuer Kalender",
"custom_color": "Eigene Farbe (Hex)",
"fewer_colors": "Weniger Farben",
"more_colors": "Mehr Farben"
},
"notifications": {
"channel_name": "Kalendererinnerungen",
"participant_limit": "Der Server erlaubt höchstens {max, plural, one {# Teilnehmer} other {# Teilnehmer}} pro Termin, den Organisator mitgezählt, und hat die Änderung deshalb abgelehnt. Ihr Administrator kann das Limit erhöhen.",
"starts_in_days": "{count, plural, one {Beginnt in # Tag} other {Beginnt in # Tagen}}",
"starts_in_hours": "{count, plural, one {Beginnt in # Stunde} other {Beginnt in # Stunden}}",
"starts_in_minutes": "{count, plural, one {Beginnt in # Minute} other {Beginnt in # Minuten}}"
},
"occurrence_not_found_again": "Die Änderung wurde nicht gespeichert: Der Termin konnte auf dem Server nicht wiedergefunden werden. Versuchen Sie es erneut.",
"participants": {
"limit_warning": "Mehr als {max, plural, one {# Teilnehmer} other {# Teilnehmer}}, den Organisator mitgezählt: Der Server lehnt das Speichern dieses Termins möglicherweise ab. Ihr Administrator kann das Limit erhöhen.",
"remove_named": "{name} entfernen",
"group_members": "{count, plural, one {# Mitglied} other {# Mitglieder}}"
},
"recurrence": {
"editor_occurrences_count": "{count, plural, one {Termin} other {Terminen}}",
"every_count_days": "{count, plural, one {Jeden Tag} other {Alle # Tage}}",
@@ -104,7 +124,7 @@
"time_zone": "Zeitzone",
"time_zone_auto": "Gerätezeitzone",
"time_zone_auto_zone": "Gerätezeitzone ({zone})",
"time_zone_desc": "Zeitzone für neue Termine und für die Darstellung des Kalenders. \"Gerätezeitzone\" folgt dem Telefon."
"time_zone_desc": "Zeitzone für neue Termine und für die Darstellung des Kalenders. \"Gerätezeitzone\" folgt diesem Gerät."
},
"share": {
"add_people": "Personen hinzufügen",
@@ -122,7 +142,10 @@
"not_a_feed": "Diese URL hat keinen iCalendar-Feed geliefert",
"not_synced": "Noch nicht synchronisiert",
"url_scheme_invalid": "Die URL muss mit https://, http://, webcal:// oder webcals:// beginnen",
"your_subscriptions": "Ihre Abonnements"
"your_subscriptions": "Ihre Abonnements",
"account_changed": "Das Konto hat gewechselt, während das Abonnement synchronisiert wurde. Es wird erneut in seinem eigenen Konto synchronisiert.",
"feed_truncated": "Der Feed wurde vor seinem Ende abgeschnitten. Es wurde nichts geändert.",
"mass_removal": "Der Feed hat auf einmal {count} von {total} Terminen weggelassen. Sie wurden behalten, falls der Feed fehlerhaft ist."
},
"tasks": {
"add_placeholder": "Aufgabe hinzufügen…",
@@ -138,7 +161,16 @@
"n_days": "{count} Tage",
"schedule": "Terminübersicht",
"three_days": "3 Tage"
}
},
"detail": {
"delete_confirm": "Dieses Ereignis löschen?",
"location_hint": "Öffnet den Ort; lange drücken zum Kopieren"
},
"change_month_named": "{month}, Monat wechseln",
"a11y": {
"event_count": "{count, plural, one {# Termin} other {# Termine}}"
},
"account_changed": "Das Konto hat gewechselt, während der Kalender geladen oder gespeichert wurde."
},
"common": {
"add": "Hinzufügen",
@@ -164,7 +196,8 @@
"add_tag": "Kategorie hinzufügen",
"delete_confirm_mobile": "{count, plural, one {# Kontakt löschen? Dies kann nicht rückgängig gemacht werden.} other {# Kontakte löschen? Dies kann nicht rückgängig gemacht werden.}}",
"move_to_address_book": "In Adressbuch verschieben",
"tag_failed": "Kategorie konnte nicht zugewiesen werden"
"tag_failed": "Kategorie konnte nicht zugewiesen werden",
"delete_confirm_title": "Kontakte löschen"
},
"contact": "Kontakt",
"create_hint_mobile": "Lange drücken, um stattdessen eine Gruppe zu erstellen",
@@ -215,6 +248,7 @@
"new_organization": "Neue Organisation",
"org_name_required": "Geben Sie den Namen der Organisation an.",
"photo_hint_mobile": "Tippen Sie auf das Foto, um ein Bild auszuwählen",
"photo_media_unread": "Die Fotos des Kontakts konnten nicht vom Server gelesen werden, daher wurde nichts gespeichert. Versuchen Sie es erneut.",
"photo_permission_message": "Erlauben Sie den Zugriff auf die Fotomediathek, um ein Kontaktfoto auszuwählen.",
"photo_permission_title": "Fotozugriff erforderlich",
"pick_date": "Datum auswählen",
@@ -247,7 +281,11 @@
"into_address_book": "In Adressbuch importieren",
"into_named": "In {name} importieren",
"no_address_book": "Erstellen Sie ein Adressbuch, bevor Sie Kontakte importieren.",
"picker_failed": "Die Dateiauswahl konnte nicht geöffnet werden"
"picker_failed": "Die Dateiauswahl konnte nicht geöffnet werden",
"replaces": "Ersetzt {name}",
"update_existing": "Vorhandenen aktualisieren",
"unnamed": "Unbenannter Kontakt",
"updated_count": "{count, plural, one {1 Kontakt war bereits vorhanden und wurde aktualisiert.} other {# Kontakte waren bereits vorhanden und wurden aktualisiert.}}"
},
"no_tags_hint": "Noch keine Kategorien. Geben Sie oben eine ein.",
"picker": {
@@ -256,11 +294,23 @@
},
"rename_tag_hint_mobile": "Tippen Sie lange auf eine Kategorie, um sie umzubenennen",
"tag_name_placeholder": "Name der neuen Kategorie",
"unnamed": "Unbenannt"
"unnamed": "Unbenannt",
"delete_confirm_title": "Kontakt löschen",
"directory": {
"restricted_hint": "Ihr Mailserver listet nur die Konten auf, mit denen Sie bereits zu tun haben, etwa Ihre Gruppen und Personen, die etwas mit Ihnen teilen.",
"restricted_queries_off": "Ihr Mailserver listet nur die Konten auf, mit denen Sie bereits zu tun haben, etwa Ihre Gruppen und Personen, die etwas mit Ihnen teilen. Ein Administrator kann Verzeichnisabfragen aktivieren, damit alle angezeigt werden."
}
},
"context_menu": {
"color_tag": "Label",
"remove_color": "Label entfernen"
"emails_selected": "{count, plural, one {# E-Mail ausgewählt} other {# E-Mails ausgewählt}}",
"remove_color": "Label entfernen",
"rules": {
"login_changed": "Sie haben das Konto gewechselt, daher wurde die Regel nicht gespeichert"
}
},
"delegates": {
"remove_for": "Zugriff für {name} entfernen"
},
"device_sync": {
"auth_problem_text": "Der Server akzeptiert die Anmeldung von {account} nicht mehr, daher werden die Kontakte und Kalender dieses Kontos auf diesem Gerät nicht mehr synchronisiert. Melden Sie sich erneut an, um fortzufahren. Auf diesem Gerät vorgenommene Änderungen bleiben erhalten.",
@@ -280,6 +330,11 @@
"attachment_reminder_title": "Anhang vergessen?",
"attachments_too_large_total": "Die Anhänge würden die {max} überschreiten, die dieser Server pro Nachricht erlaubt.",
"editor_unavailable": "Der Nachrichteninhalt konnte nicht gelesen werden. Kopieren Sie Ihren Text, schließen Sie dann den Editor und öffnen Sie ihn erneut.",
"files_picker": {
"attach": "{count, plural, =0 {Anhängen} one {1 Datei anhängen} other {# Dateien anhängen}}",
"empty_folder": "Dieser Ordner ist leer",
"load_failed": "Ihre Dateien konnten nicht geladen werden"
},
"from_options": "Absenderoptionen",
"group_chip": "{name} ({count})",
"identity_unavailable": "Identität nicht verfügbar",
@@ -288,10 +343,38 @@
"no_sent_folder": "Kein Gesendet-Ordner gefunden",
"permission_camera": "Zum Aufnehmen eines Fotos ist die Kameraberechtigung erforderlich.",
"permission_photos": "Zum Anhängen von Bildern ist die Berechtigung für die Fotomediathek erforderlich.",
"placeholder_warning": {
"title": "Platzhalter prüfen?",
"message": "Die Nachricht enthält Platzhaltertext, den die Empfänger sehen. Vor dem Senden bearbeiten?",
"found": "Gefundene Platzhalter",
"more": "und {count} weitere",
"dont_ask_again": "Nicht mehr fragen",
"send_anyway": "Trotzdem senden",
"back": "Zurück zur Bearbeitung"
},
"placeholder_words": {
"before": [
"ihr",
"ihre",
"ihren",
"dein",
"deine",
"deinen"
],
"after": [
"einfügen",
"eintragen",
"ergänzen"
],
"unfinished": []
},
"recipient_add_contact": "Zu Kontakten hinzufügen",
"recipient_copy": "Adresse kopieren",
"recipient_move_to": "Verschieben nach {field}",
"recipient_remove": "Entfernen",
"recipients_partly_refused": "Der Server hat {recipients} abgelehnt. Die anderen Empfänger erhalten die Nachricht.",
"recipients_partly_refused_title": "Nicht an alle gesendet",
"recipients_refused": "Nicht gesendet: Der Server hat {recipients} abgelehnt.",
"remove_attachment": "Anhang entfernen",
"save_draft": "Entwurf speichern",
"save_failed_no_drafts": "Es gibt keinen Entwürfe-Ordner, in dem dieser Entwurf gespeichert werden kann.",
@@ -312,6 +395,8 @@
"send_now": "Jetzt senden",
"send_timeout_body": "Die Nachricht wurde möglicherweise bereits versendet. Prüfen Sie Ihren Gesendet-Ordner, bevor Sie sie erneut senden.",
"send_timeout_title": "Keine Antwort vom Server",
"submission_limit": "Der Server hat das Senden abgelehnt, weil ein Limit erreicht ist: {reason}",
"submission_quota": "Der Server hat für dieses Konto zu viele Einträge zu gesendeten Nachrichten. Bulwark hat entfernt, was möglich war; wenn das weiterhin vorkommt, bitten Sie Ihren Administrator, das Limit für E-Mail-Übermittlungen zu erhöhen.",
"toolbar": {
"insert_image": "Bild einfügen",
"remove_link": "Link entfernen"
@@ -322,7 +407,31 @@
"uploading_pct": "Hochladen {pct} %",
"validation": {
"subject_required": "Betreff hinzufügen"
}
},
"format_plain_text": "Zu Nur-Text wechseln",
"format_rich_text": "Zu formatiertem Text (HTML) wechseln",
"login_switched_attach_body": "Sie haben diese Nachricht in {account} begonnen. Wechseln Sie zurück zu diesem Konto, um Dateien anzuhängen.",
"login_switched_body": "Sie haben diese Nachricht in {account} begonnen. Wechseln Sie zurück zu diesem Konto, um sie zu senden.",
"login_switched_save_body": "Sie haben diese Nachricht in {account} begonnen. Wechseln Sie zurück zu diesem Konto, um sie zu speichern.",
"login_switched_title": "Konto gewechselt",
"option_encrypt": "Verschlüsseln",
"option_encrypt_hint": "Nur die Empfänger (und Sie) können sie lesen; jeder Empfänger braucht einen Schlüssel",
"option_security": "Signatur und Verschlüsselung",
"option_sign": "Signieren",
"option_sign_pgp_hint": "Eine OpenPGP-Signatur mit Ihrem Schlüssel hinzufügen",
"option_sign_smime_hint": "Eine S/MIME-Signatur mit Ihrem Zertifikat hinzufügen",
"read_receipt_off": "Lesebestätigung anfordern",
"secure_failed_title": "Signieren oder Verschlüsseln nicht möglich",
"secure_missing_keys": "Kein Schlüssel für {recipients}. Importieren Sie einen unter Einstellungen → Verschlüsselung.",
"secure_no_key": "Auf diesem Gerät gibt es keinen Schlüssel für {email}. Importieren Sie einen unter Einstellungen → Verschlüsselung.",
"secure_offline": "Signierte oder verschlüsselte Nachrichten können nicht im Postausgang warten. Senden Sie sie, wenn Sie online sind.",
"secure_unlock_reason": "Ihr Schlüssel signiert diese Nachricht.",
"send_unconfirmed_body": "Der Server hat das Senden nicht bestätigt. Prüfen Sie Ihren Gesendet-Ordner, bevor Sie erneut senden. Ihr Entwurf wurde behalten.",
"send_unconfirmed_title": "Senden nicht bestätigt",
"undo_send_other_login": "Diese Nachricht wurde von einem anderen Konto gesendet",
"undo_send_other_login_body": "Wechseln Sie zurück zu {account}, um das Senden rückgängig zu machen oder sofort zu senden. Sie geht raus, wenn die Verzögerung abläuft.",
"upload_login_switched": "Nicht hochgeladen: Das Konto wurde gewechselt",
"use_template": "Vorlage"
},
"email_list": {
"cross_account_move_offline": "Verschieben zwischen Konten erfordert eine Verbindung",
@@ -415,28 +524,46 @@
"pdf_external": "PDFs werden in Ihrem PDF-Viewer auf diesem Gerät geöffnet.",
"unsupported": "Keine Vorschau für diesen Dateityp.",
"media_unsupported": "Diese Datei kann hier nicht abgespielt werden.",
"pdf_protected": "Diese PDF ist passwortgeschützt. Öffne sie in einer anderen App, um das Passwort einzugeben.",
"pdf_protected": "Diese PDF ist passwortgeschützt. Öffnen Sie sie in einer anderen App, um das Passwort einzugeben.",
"pdf_unreadable": "Diese PDF kann hier nicht angezeigt werden.",
"archive_files": "{count, plural, one {# Datei} other {# Dateien}}",
"archive_first_files": "Die ersten {count} Dateien",
"archive_protected": "Dieses Archiv ist passwortgeschützt. Öffne es in einer anderen App, um das Passwort einzugeben.",
"archive_protected": "Dieses Archiv ist passwortgeschützt. Öffnen Sie es in einer anderen App, um das Passwort einzugeben.",
"archive_unreadable": "Dieses Archiv kann hier nicht geöffnet werden."
},
"received_via": "an {name}",
"remove_color": "Label entfernen",
"replied": "Beantwortet",
"reschedule_recipients_refused": "Nichts wurde geändert: Der Server hat {recipients} abgelehnt.",
"save_to_files": {
"title": "In Dateien speichern",
"save_here": "Hier speichern",
"saved": "In Dateien gespeichert",
"saved_to": "In {folder}",
"failed": "Konnte nicht in Dateien gespeichert werden",
"load_failed": "Ihre Ordner konnten nicht geladen werden. Versuchen Sie es erneut."
},
"sender_actions": "Absender…",
"set_color": "Label setzen",
"show_all_count": "Alle anzeigen ({count})",
"show_less": "Weniger anzeigen",
"smime_unsupported": "Mit S/MIME signiert/verschlüsselt - auf Mobilgeräten nicht unterstützt",
"source_too_large": "Die Nachricht ist zu groß, um vollständig angezeigt zu werden. Teilen Sie stattdessen den vollständigen Quelltext.",
"still_scheduled": "Die Nachricht ist weiterhin geplant.",
"still_scheduled_at": "Die Nachricht ist weiterhin für {date} geplant.",
"thread_loading": "Unterhaltung wird geladen...",
"undo_send_failed": "Senden konnte nicht rückgängig gemacht werden",
"unsubscribe_banner": {
"no_identity": "Keine Absenderidentität verfügbar"
},
"view_dark_mode": "Im dunklen Modus anzeigen",
"view_light_mode": "Im hellen Modus anzeigen"
"view_light_mode": "Im hellen Modus anzeigen",
"not_spam_short": "Kein Spam",
"previous": "Vorherige",
"next": "Nächste",
"quick_reply_login_switched": "Diese Nachricht gehört zu einem anderen Konto. Wechseln Sie zurück zu diesem Konto, um zu antworten.",
"quick_reply_unconfirmed": "Der Server hat das Senden nicht bestätigt. Prüfen Sie Ihren Gesendet-Ordner, bevor Sie erneut senden.",
"spam_short": "Spam"
},
"files": {
"delete_cascade_hint": "Ordner werden samt ihrem gesamten Inhalt gelöscht. Dies kann nicht rückgängig gemacht werden.",
@@ -444,10 +571,17 @@
"delete_items_title": "{count, plural, one {# Element löschen?} other {# Elemente löschen?}}",
"done": "Fertig",
"download_failed_status": "Download fehlgeschlagen (HTTP {status})",
"error_forbidden": "Dazu fehlt Ihnen hier die Berechtigung.",
"error_invalid_name": "Der Server akzeptiert diesen Namen nicht.",
"error_over_quota": "Die Höchstzahl an Dateien oder Ordnern ist erreicht. Löschen Sie einige und versuchen Sie es erneut.",
"error_too_deep": "Ordner lassen sich nicht so tief verschachteln.",
"error_too_large": "Diese Datei ist für den Server zu groß.",
"folder_empty": "Dieser Ordner ist leer",
"invalid_name": "Ungültiger Name",
"invalid_name_slash": "Namen dürfen kein \"/\" enthalten.",
"move_to": "Verschieben nach…",
"name_control_chars": "Namen dürfen keine Steuerzeichen enthalten.",
"name_too_long": "Dieser Name ist für den Server zu lang.",
"open_error": "Die Datei konnte nicht geöffnet werden",
"open_with": "Öffnen mit…",
"preview_too_large": "Diese Datei ist zu groß für eine Vorschau.",
@@ -460,13 +594,41 @@
"share_no_users": "Keine anderen Benutzer gefunden",
"share_remove": "Zugriff entfernen",
"share_remove_confirm": "Zugriff entfernen?",
"share_remove_for": "Zugriff für {name} entfernen",
"share_role_custom": "Benutzerdefiniert",
"share_search_placeholder": "Nach Name oder E-Mail suchen",
"share_unavailable": "Teilen ist auf diesem Gerät nicht verfügbar",
"shared_with": "Geteilt mit",
"sharing_access": "Freigabe & Zugriff",
"symlink": "Symbolischer Link",
"upload_failed": "{count, plural, one {1 Datei konnte nicht hochgeladen werden} other {# Dateien konnten nicht hochgeladen werden}}",
"upload_unavailable": "Die Dateiauswahl ist in diesem Build nicht installiert. Erstellen Sie die App neu, um Uploads zu aktivieren.",
"uploading_progress": "Hochladen {current}/{total} · {name}"
"uploading_progress": "Hochladen {current}/{total} · {name}",
"batch_delete_confirm_message": "Möchten Sie wirklich {count, plural, one {1 Element} other {# Elemente}} löschen? Dies kann nicht rückgängig gemacht werden.",
"batch_delete_success": "{count, plural, one {1 Element gelöscht} other {# Elemente gelöscht}}",
"delete_confirm_message": "Möchten Sie \"{name}\" wirklich löschen? Dies kann nicht rückgängig gemacht werden.",
"delete_confirm_title": "Ressource löschen",
"delete_success": "Erfolgreich gelöscht",
"name_trash": "„.Trash“ ist der Name des Papierkorb-Ordners auf der obersten Ebene Ihrer Dateien. Wählen Sie einen anderen Namen.",
"settings_behavior": "Verhalten",
"settings_colored_icons": "Farbige Symbole",
"settings_colored_icons_desc": "Farbige statt einfarbige Symbole verwenden",
"settings_default_sort": "Standardsortierung",
"settings_default_sort_desc": "Wählen Sie die Standardsortierung für Dateien",
"settings_default_view": "Standardansicht",
"settings_default_view_desc": "Wählen Sie zwischen Raster- und Listenansicht",
"settings_display": "Anzeige",
"settings_icons": "Symbole",
"settings_show_hidden": "Versteckte Dateien anzeigen",
"settings_show_hidden_desc": "Dateien und Ordner anzeigen, die mit einem Punkt beginnen",
"settings_show_icons": "Dateisymbole anzeigen",
"settings_show_icons_desc": "Symbole neben Dateien und Ordnern anzeigen",
"settings_show_thumbnails": "Vorschaubilder anzeigen",
"settings_show_thumbnails_desc": "Bildvorschauen statt Symbole für Bilddateien anzeigen",
"settings_sort_direction": "Sortierrichtung",
"settings_sort_direction_desc": "Wählen Sie auf- oder absteigende Reihenfolge",
"trash_unavailable_message": "{count, plural, one {Dieses Element kann} other {Diese # Elemente können}} nicht in den Papierkorb verschoben werden. Stattdessen endgültig löschen? Das kann nicht rückgängig gemacht werden.",
"upload": "Hochladen"
},
"identities": {
"badge": {
@@ -520,7 +682,7 @@
"err_pair_connect_detail": "Zeigen Sie auf Ihrem Computer einen neuen Code an und scannen Sie erneut.",
"err_pair_connect_title": "Angemeldet, aber keine Verbindung zu {host}",
"err_pair_insecure_detail": "{host} möchte eine unverschlüsselte Verbindung verwenden.",
"err_pair_network_detail": "Prüfen Sie Ihre Verbindung und ob Ihr Telefon das Webmail erreichen kann, und versuchen Sie es dann erneut.",
"err_pair_network_detail": "Prüfen Sie Ihre Verbindung und ob dieses Gerät das Webmail erreichen kann, und versuchen Sie es dann erneut.",
"err_pair_rate_detail": "Warten Sie eine Minute und versuchen Sie es dann erneut.",
"err_pair_server_detail": "Versuchen Sie es mit einem neuen Code erneut. Wenn es weiterhin fehlschlägt, wenden Sie sich an Ihren Administrator.",
"err_pair_server_title": "Das Webmail konnte die Anmeldung nicht abschließen",
@@ -589,11 +751,34 @@
"unencrypted": "Unverschlüsselte Verbindung",
"use_password": "Stattdessen mit Passwort anmelden",
"username_label": "E-Mail oder Benutzername",
"your_server": "Ihrem Server"
"your_server": "Ihrem Server",
"cancel_sign_in": "Anmeldung abbrechen",
"err_client_rejected_detail": "Der Server hat Bulwark als Anmelde-Client abgelehnt. Bitten Sie Ihren Administrator, ihn zuzulassen, oder melden Sie sich mit einem App-Passwort an.",
"err_client_rejected_title": "{host} hat die App nicht akzeptiert",
"err_handoff_expired_detail": "Der Browser hat die Anmeldung erst nach ihrem Ablauf zurückgegeben. Melden Sie sich erneut an.",
"err_handoff_expired_title": "Die Anmeldung hat zu lange gedauert",
"err_handoff_network_detail": "Das Webmail war nach Ihrer Anmeldung nicht erreichbar. Prüfen Sie Ihre Verbindung und melden Sie sich erneut an.",
"err_handoff_network_title": "Anmeldung konnte nicht abgeschlossen werden",
"err_handoff_rate_detail": "Warten Sie ein paar Minuten und melden Sie sich dann erneut an.",
"err_handoff_refused_detail": "Der Anmeldelink wurde bereits verwendet oder stammt nicht aus dieser Anmeldung. Melden Sie sich erneut an.",
"err_handoff_refused_title": "Das Webmail hat diese Anmeldung nicht akzeptiert",
"err_handoff_server_detail": "Melden Sie sich erneut an. Wenn es weiterhin fehlschlägt, wenden Sie sich an Ihren Administrator.",
"err_handoff_server_title": "Das Webmail konnte die Anmeldung nicht abschließen",
"err_token_exchange_title": "{host} hat keine Sitzung gestartet",
"reauth_detail": "Der Server akzeptiert die gespeicherte Anmeldung für {account} nicht mehr. Melden Sie sich erneut an, um dieses Konto weiter zu nutzen.",
"reauth_title": "Erneut anmelden",
"server_cert_active": "Anmeldung mit dem Client-Zertifikat „{alias}“. Tippen, um ein anderes zu wählen.",
"server_cert_clear": "Ohne Zertifikat anmelden",
"server_cert_failed": "Zertifikat konnte nicht ausgewählt werden",
"server_cert_hint": "Verlangt Ihr Server ein Client-Zertifikat? Wählen Sie eines, das auf diesem Gerät installiert ist."
}
},
"notifications": {
"vacation_filters_off": "Abwesenheits-Einstellungen gespeichert, aber Ihre Filter sind ausgeschaltet. Speichern Sie noch einmal, um sie wieder einzuschalten."
},
"offline": {
"banner": "Sie sind offline",
"discarded_changes": "{count, plural, one {# Offline-Änderung wurde} other {# Offline-Änderungen wurden}} verworfen, weil sich der Mailserver geändert hat",
"queued_changes": "{count, plural, one {# Änderung wird} other {# Änderungen werden}} synchronisiert, sobald Sie wieder verbunden sind",
"sync": {
"cached_count": "{count, plural, one {# neue Nachricht zwischengespeichert} other {# neue Nachrichten zwischengespeichert}} • {size}",
@@ -607,6 +792,10 @@
"up_to_date": "Bereits aktuell"
}
},
"org_templates": {
"personal_in_account": "Ihre Vorlagen werden in Ihrem Konto gespeichert, Sie haben sie also auf jedem Gerät, auf dem Sie sich anmelden. Alle, für die Sie Ihren Posteingang freigeben, können sie lesen.",
"section_personal": "Meine Vorlagen"
},
"push_prompt": {
"body": "Aktivieren Sie Hintergrundbenachrichtigungen, damit neue Nachrichten Sie auch bei geschlossener App erreichen.",
"dismiss": "Nicht jetzt",
@@ -614,12 +803,22 @@
"title": "Bei neuen E-Mails benachrichtigt werden"
},
"scheduled": {
"already_sent": "Diese Nachricht wurde bereits gesendet.",
"cancel_body": "Die Nachricht wird nicht zugestellt. Eine Kopie bleibt in Ihrem Gesendet-Ordner.",
"cancel_failed": "Abbrechen fehlgeschlagen",
"edit_failed": "Die Nachricht konnte nicht zum Bearbeiten geöffnet werden",
"keep_scheduled": "Geplant lassen",
"no_longer_scheduled": "Diese Nachricht ist nicht mehr geplant.",
"reschedule_failed": "Neu planen fehlgeschlagen",
"send_now_failed": "Sofortiges Senden fehlgeschlagen"
"send_now_failed": "Sofortiges Senden fehlgeschlagen",
"actions": "Aktionen",
"cancel_to_drafts_body": "Die Nachricht wird nicht gesendet. Sie kommt zurück in Ihre Entwürfe.",
"cancelled_not_moved": "Senden abgebrochen, aber nicht in Entwürfe verschoben",
"cancelled_not_moved_body": "Die Nachricht wird nicht gesendet. Sie liegt noch in „Gesendet“.",
"login_switched": "Diese geplanten Nachrichten gehören zu einem anderen Konto. Wechseln Sie zurück zu diesem Konto, um sie zu ändern.",
"reschedule_lost": "Die Nachricht ist nicht mehr geplant. Prüfen Sie Entwürfe und Gesendet, bevor Sie sie erneut senden.",
"reschedule_lost_drafts": "Die Nachricht ist nicht mehr geplant. Sie liegt wieder in den Entwürfen, wo Sie sie erneut senden können.",
"send_cancelled": "Senden abgebrochen"
},
"settings": {
"account": {
@@ -648,6 +847,15 @@
}
},
"appearance": {
"cross_all": {
"label_mobile": "Alle Mails"
},
"cross_starred": {
"label_mobile": "Alle markierten"
},
"cross_unread": {
"label_mobile": "Alle ungelesenen"
},
"language": {
"restart_for_direction": "Starten Sie die App neu, um die neue Schreibrichtung anzuwenden.",
"system_default": "Systemstandard"
@@ -655,7 +863,10 @@
"toolbar_labels": {
"description_mobile": "Textbeschriftungen neben den Symbolleistensymbolen anzeigen."
},
"description_mobile": "Passe an, wie die App aussieht"
"description_mobile": "Passen Sie an, wie die App aussieht",
"accessibility_link": {
"description_mobile": "Schriftgröße, reduzierte Bewegung und die Voreinstellungen zur Barrierefreiheit finden Sie unter Barrierefreiheit."
}
},
"badges": {
"experimental": "Experimentell",
@@ -802,6 +1013,10 @@
"ask_mobile": "Fragen",
"block_mobile": "Blockieren"
},
"placeholder_warning": {
"label": "Warnung bei Platzhaltern",
"description": "Vor dem Senden einer Nachricht nachfragen, die noch Platzhalter wie [TIME] oder [Ihr Name], leere Vorlagenfelder oder unfertigen Text wie TODO oder TBD enthält."
},
"plain_text_mode": {
"description_mobile": "Nur im Klartext verfassen und lesen."
},
@@ -826,6 +1041,13 @@
"sync_add_failed": "Synchronisierung mit dem Adressbuch fehlgeschlagen",
"sync_remove_failed": "Entfernen aus dem Adressbuch fehlgeschlagen",
"synced_badge": "Über Adressbuch synchronisiert"
},
"disable_threading": {
"description": "E-Mails als einzelne Nachrichten statt nach Konversation gruppiert anzeigen",
"label": "Konversationsgruppierung deaktivieren"
},
"recipient_mentions": {
"description_mobile": "Wenn Sie in der Nachricht @ tippen, werden deren Empfänger angeboten und der Vorname des gewählten wird eingefügt."
}
},
"experimental": {
@@ -835,8 +1057,11 @@
"remove_action": "Aktion entfernen",
"remove_condition": "Bedingung entfernen",
"sieve_editor": {
"line_count": "{count, plural, one {# Zeile} other {# Zeilen}}"
}
"line_count": "{count, plural, one {# Zeile} other {# Zeilen}}",
"confirm_save": "Speichern bestätigen",
"save_warning": "Das Speichern überschreibt alle visuellen Regeln. Dies kann nicht rückgängig gemacht werden. Klicken Sie erneut auf Speichern zur Bestätigung."
},
"validation_header_name": "Geben Sie einen Header-Namen ein, der nur die Buchstaben A–Z, Ziffern, Bindestriche und Unterstriche enthält, zum Beispiel X-Spam-Flag."
},
"folders": {
"account": "Konto",
@@ -851,6 +1076,7 @@
"not_empty_message": "\"{name}\" enthält {count} E-Mails. Trotzdem löschen?",
"not_empty_title": "Ordner nicht leer",
"own_account": "Meine Ordner",
"parent_folder": "Übergeordneter Ordner",
"role": "Ordnerrolle",
"role_hint": "Weisen Sie diesem Ordner eine Sonderrolle zu (Archiv, Spam, …). Jede Rolle kann nur einem Ordner zugewiesen werden.",
"save_failed": "Speichern fehlgeschlagen",
@@ -878,7 +1104,8 @@
"no_name": "(kein Name)",
"primary": "primär",
"save_failed": "Speichern fehlgeschlagen",
"text_signature": "Klartext-Signatur"
"text_signature": "Klartext-Signatur",
"title": "Sendeidentitäten"
},
"keywords": {
"delete_confirm_title": "Tag „{label}“ löschen?",
@@ -936,7 +1163,9 @@
"email": {
"app_icon_badge": "Ungelesen-Zähler auf dem App-Symbol",
"app_icon_badge_desc": "Die Anzahl ungelesener Nachrichten im Posteingang als Badge auf dem App-Symbol anzeigen.",
"sound_channel_hint": "Ton und Vibration für E-Mail-Benachrichtigungen werden über den Android-Benachrichtigungskanal gesteuert: Tippen Sie lange auf eine Benachrichtigung oder öffnen Sie die App-Einstellungen des Systems."
"sound_channel_hint": "Ton und Vibration für E-Mail-Benachrichtigungen werden über den Android-Benachrichtigungskanal gesteuert: Tippen Sie lange auf eine Benachrichtigung oder öffnen Sie die App-Einstellungen des Systems.",
"mute_account": "Dieses Konto stummschalten",
"mute_account_desc": "Keine Benachrichtigungen über neue E-Mails in diesem Konto. Tippen Sie in der Seitenleiste lange auf einen Ordner, um nur ihn stummzuschalten oder wieder einzuschalten."
},
"push": {
"confirm_disable_message_mobile": "Dieses Gerät erhält für dieses Konto keine Benachrichtigungen über neue E-Mails mehr.",
@@ -945,8 +1174,11 @@
"err_fcm_empty": "Firebase hat ein leeres Geräte-Token zurückgegeben.",
"err_fcm_token": "Firebase konnte kein Geräte-Token ausstellen ({detail}). Push über FCM erfordert Google Play-Dienste auf diesem Gerät.",
"err_fcm_token_up": "Firebase konnte kein Geräte-Token ausstellen ({detail}). Push über FCM erfordert Google Play-Dienste auf diesem Gerät. Auf diesem Gerät ist ein UnifiedPush-Distributor installiert - stellen Sie die Zustellmethode stattdessen auf UnifiedPush um.",
"err_forbidden": "Der Mailserver erlaubt für dieses Konto keine Hintergrundbenachrichtigungen.",
"err_jmap_list": "Die Push-Registrierungen konnten nicht vom Mailserver gelesen werden: {detail}",
"err_jmap_refused": "Der Mailserver hat das Push-Abonnement abgelehnt: {detail}",
"err_no_account": "Kein Konto geladen - Push kann nicht eingerichtet werden.",
"err_over_quota": "Für dieses Konto gibt es auf dem Mailserver zu viele Push-Registrierungen (meist sind 15 erlaubt). Widerrufen Sie Geräte, die Sie nicht mehr nutzen, in den Benachrichtigungseinstellungen und versuchen Sie es erneut.",
"err_permission": "Die Benachrichtigungsberechtigung wurde nicht erteilt.",
"err_relay_https": "Die Relay-URL muss https:// verwenden.",
"err_relay_rejected": "Das Push-Relay hat die Registrierung abgelehnt: {detail}",
@@ -966,6 +1198,7 @@
"relay_desc_mobile": "Der Server, der Ihre Benachrichtigungen zustellt. Standardmäßig das gehostete Bulwark-Relay; ändern Sie dies nur, wenn Sie selbst hosten. Muss https verwenden.",
"relay_invalid": "Geben Sie eine gültige https://-URL ein",
"setup_failed": "Einrichtung fehlgeschlagen",
"shared_mailboxes_no_push": "Postfächer, die andere mit Ihnen teilen, senden keine Hintergrundbenachrichtigungen.",
"status_android_only": "Hintergrundbenachrichtigungen sind nur unter Android verfügbar.",
"transport_desc": "Wie Benachrichtigungen dieses Gerät erreichen: Googles Firebase Cloud Messaging oder eine UnifiedPush-Distributor-App (z. B. ntfy) für die Zustellung ohne Google.",
"transport_fcm": "Google (FCM)",
@@ -973,7 +1206,17 @@
"transport_unifiedpush": "UnifiedPush",
"up_distributor_desc": "Mehrere UnifiedPush-Distributor-Apps sind installiert - wählen Sie die aus, die Benachrichtigungen zustellen soll.",
"up_distributor_label": "Distributor",
"up_no_distributor": "Auf diesem Gerät ist keine UnifiedPush-Distributor-App installiert. Installieren Sie eine (z. B. ntfy) und versuchen Sie es erneut."
"up_no_distributor": "Auf diesem Gerät ist keine UnifiedPush-Distributor-App installiert. Installieren Sie eine (z. B. ntfy) und versuchen Sie es erneut.",
"accounts_desc_mobile": "Hintergrundbenachrichtigungen für jedes angemeldete Konto auf diesem Gerät. Wechseln Sie zu einem Konto, um sie dafür ein- oder auszuschalten.",
"err_account_changed": "Das Konto hat gewechselt, während Push eingerichtet wurde. Versuchen Sie es erneut.",
"err_apns_empty": "iOS hat ein leeres Push-Token zurückgegeben.",
"err_apns_token": "iOS konnte kein Push-Token ausstellen ({detail}).",
"err_relay_no_apns": "Das Push-Relay unter {url} stellt noch nicht an iPhones und iPads zu.",
"err_url_refused": "Der Mailserver weigert sich, Benachrichtigungen an das Relay unter {url} zu senden. Er akzeptiert nur https-Adressen mit einem öffentlichen Domainnamen, keine private IP-Adresse und keinen lokalen Namen wie localhost, .local oder .lan. Verwenden Sie das gehostete Relay oder geben Sie Ihrem Relay einen öffentlichen Domainnamen."
},
"calendar": {
"invitation_parsing": "E-Mail-Einladungen erkennen",
"invitation_parsing_desc": "Kalendereinladungen in E-Mail-Anhängen erkennen und Kalenderaktionen anzeigen"
}
},
"offline": {
@@ -1005,7 +1248,7 @@
"window_24h": "Letzte 24 Stunden",
"window_days": "Letzte {count} Tage",
"window_desc": "Wie weit zurück zwischengespeichert wird, gemessen am Empfangsdatum der Nachricht.",
"description_short": "Neue Nachrichten auf dem Telefon behalten, damit sie auch ohne Verbindung aufgehen.",
"description_short": "Neue Nachrichten auf diesem Gerät behalten, damit sie auch ohne Verbindung aufgehen.",
"keep_24h": "24 Stunden",
"keep_days": "{count} Tage",
"keep_from": "Mails der letzten",
@@ -1030,7 +1273,10 @@
"not_set": "Nicht festgelegt",
"pick": "Zertifikat auswählen",
"pick_failed": "Auswahl fehlgeschlagen",
"title": "TLS-Client-Zertifikat"
"title": "TLS-Client-Zertifikat",
"clear_message_ios": "Neue Anfragen legen kein Zertifikat mehr vor. Das Zertifikat wird aus der App entfernt; um es wieder zu verwenden, wählen Sie die Datei erneut aus.",
"description_ios": "Importiert eine Zertifikatsdatei, mit der sich die App beim TLS-Handshake am Server authentifiziert. Nützlich, wenn der Reverse-Proxy mTLS erzwingt.",
"hint_ios": "Wählen Sie die Zertifikatsdatei (.p12 oder .pfx) in Dateien aus und geben Sie ihr Passwort ein."
},
"credentials": {
"allowed_ips": "Erlaubte IPs (optional)",
@@ -1058,12 +1304,15 @@
"password": {
"change": "Passwort ändern",
"changed": "Ihr Kontopasswort wurde aktualisiert.",
"changed_sign_in_again": "Ihr Kontopasswort wurde aktualisiert. Melden Sie sich mit dem neuen Passwort erneut an.",
"changed_title": "Passwort geändert",
"description": "Das Passwort für dieses Konto ändern.",
"error": "Passwort konnte nicht geändert werden.",
"mismatch": "Die Passwörter stimmen nicht überein.",
"toggle_visibility": "Passwort ein- oder ausblenden",
"too_short": "Das neue Passwort muss mindestens 8 Zeichen lang sein."
"too_short": "Das neue Passwort muss mindestens 8 Zeichen lang sein.",
"session_ended": "Durch die Passwortänderung wurde diese Sitzung beendet. Melden Sie sich mit Ihrem neuen Passwort erneut an.",
"session_ended_title": "Erneut anmelden"
},
"public_keys": {
"add_error": "Öffentlicher Schlüssel konnte nicht hinzugefügt werden",
@@ -1082,6 +1331,7 @@
"disable": "Deaktivieren",
"disable_error": "Zwei-Faktor-Authentifizierung konnte nicht deaktiviert werden.",
"disable_hint": "Geben Sie Ihr Passwort ein, um die Zwei-Faktor-Authentifizierung zu deaktivieren.",
"disable_hint_code": "Geben Sie Ihr Passwort und einen Code aus Ihrer Authentifizierungs-App ein, um die Zwei-Faktor-Authentifizierung zu deaktivieren.",
"enable_error": "Zwei-Faktor-Authentifizierung konnte nicht aktiviert werden.",
"enabled_message": "Bei der nächsten Anmeldung werden Sie nach einem Code gefragt.",
"enabled_title": "Zwei-Faktor-Authentifizierung aktiviert",
@@ -1094,6 +1344,12 @@
"open_app": "Zur Authenticator-App hinzufügen",
"password_required": "Geben Sie Ihr aktuelles Passwort ein.",
"section_title": "Zwei-Faktor-Authentifizierung"
},
"restricted": "Ihr Server erlaubt nur Apps, die sein Administrator freigegeben hat, das Passwort, die Zwei-Faktor-Anmeldung, App-Passwörter und API-Schlüssel zu ändern. Bulwark hat sich ohne diese Freigabe angemeldet: E-Mail, Kontakte, Kalender und Dateien funktionieren, diese Einstellungen aber nicht. Ändern Sie sie in der Weboberfläche Ihres Servers oder im Bulwark Webmail.",
"screen_privacy": {
"block_screenshots": "Bildschirmfotos blockieren",
"block_screenshots_description": "Verhindert Bildschirmfotos und Bildschirmaufnahmen der App und verbirgt ihren Inhalt in der Ansicht der zuletzt verwendeten Apps.",
"title": "Bildschirmschutz"
}
},
"sidebar_apps": {
@@ -1122,7 +1378,16 @@
"updates": "Updates",
"storage": "Speicher & Sicherung",
"about": "Über",
"troubleshooting": "Fehlerbehebung"
"troubleshooting": "Fehlerbehebung",
"content_senders": "Inhalte & Absender",
"identities": "Identitäten",
"keywords": "Labels",
"language": "Sprache, Region & Zeit",
"sidebar_apps": "Sidebar-Apps",
"templates": "Vorlagen",
"themes": "Designs",
"downloads": "Downloads",
"layout": "Layout"
},
"templates": {
"delete_confirm": "\"{name}\" endgültig löschen?",
@@ -1134,15 +1399,28 @@
"imported_count": "{count, plural, one {# Vorlage} other {# Vorlagen}} hinzugefügt.",
"is_html": "Text ist HTML",
"placeholders_hint": "Platzhalter: {{recipient_name}}, {{company}}, {{date}}, {{day_of_week}}, {{sender_name}}",
"share_unavailable": "Teilen nicht möglich"
"share_unavailable": "Teilen nicht möglich",
"import_json_label": "Vorlagen als JSON"
},
"themes": {
"default_description": "Die Standardpaletten für Hell und Dunkel.",
"mobile_description": "Wählen Sie ein Farbdesign. Designs folgen der Hell/Dunkel-Einstellung unter Darstellung; eigene Designpakete werden im Webmail verwaltet."
},
"vacation": {
"status": {
"ended": "Beendet",
"scheduled": "Geplant"
},
"warnings": {
"date_format": "Datumsangaben müssen das Format \"YYYY-MM-DD\" oder \"YYYY-MM-DD HH:MM\" haben"
},
"date_range": {
"description": "Die automatische Antwort optional auf einen bestimmten Zeitraum beschränken",
"title": "Zeitraum"
},
"message": {
"description": "Die Nachricht, die als Antwort gesendet wird",
"title": "Text der automatischen Antwort"
}
},
"about": {
@@ -1152,19 +1430,19 @@
"licences_desc": "AGPL-3.0 für die App und die Bibliotheken, die sie nutzt",
"never_checked": "Noch nicht geprüft",
"privacy": "Datenschutz",
"privacy_desc": "Was auf diesem Telefon bleibt und was an deinen Server geht",
"privacy_desc": "Was auf diesem Gerät bleibt und was an Ihren Server geht",
"report_problem": "Problem melden",
"report_problem_desc": "Öffnet ein Issue auf GitHub mit eingetragener Version",
"server": "Server",
"server_value": "{host} · angemeldet als {user}",
"source_code": "Quellcode",
"this_device": "Dieses Gerät",
"this_device_desc": "Womit die App verbunden ist und wo du Hilfe bekommst.",
"this_device_desc": "Womit die App verbunden ist und wo Sie Hilfe bekommen.",
"up_to_date": "Aktuell",
"updates": "Updates"
},
"backup": {
"description": "Übertrage deine Einstellungen auf ein anderes Telefon oder ins Webmail.",
"description": "Übertragen Sie Ihre Einstellungen auf ein anderes Gerät oder ins Webmail.",
"export_import": "Exportieren oder importieren",
"export_import_desc": "Eine JSON-Datei im selben Format wie im Webmail",
"reset": "Zurücksetzen",
@@ -1172,26 +1450,26 @@
"title": "Einstellungen sichern"
},
"licences": {
"app_desc": "Die App ist freie Software unter der {licence}. Du darfst sie nutzen, untersuchen, weitergeben und ändern.",
"app_desc": "Die App ist freie Software unter der {licence}. Sie dürfen sie nutzen, untersuchen, weitergeben und ändern.",
"libraries": "Bibliotheken",
"libraries_desc": "Open-Source-Code, mit dem die App gebaut ist.",
"read_licence": "Den vollständigen Lizenztext lesen"
},
"privacy": {
"favicons": "Absendersymbole für geschäftliche Mails kommen vom Symboldienst von DuckDuckGo, der dabei die Domain des Absenders sieht. Ausschalten kannst du sie unter Darstellung → Absender-Favicons.",
"favicons": "Absendersymbole für geschäftliche Mails kommen vom Symboldienst von DuckDuckGo, der dabei die Domain des Absenders sieht. Ausschalten können Sie sie unter Darstellung → Absender-Favicons.",
"no_tracking": "Die App hat keine Analyse und keine Werbung.",
"other_title": "Weitere Verbindungen",
"phone_backup": "Nichts davon landet in der Cloud-Sicherung von Android.",
"phone_secure": "Deine Anmeldedaten liegen im sicheren Speicher des Systems. Einstellungen, der Offline-Cache und noch nicht synchronisierte Änderungen liegen im Speicher der App.",
"phone_title": "Auf diesem Telefon",
"push": "Wenn Benachrichtigungen an sind, meldet dein Server dem Push-Relay ({relay}), dass sich etwas geändert hat, und das Relay weckt die App über {service}. Das Relay sieht ein Gerätetoken und eine Änderungsmarke, nie deine Mails. Unter Benachrichtigungen kannst du ein eigenes Relay eintragen.",
"phone_secure": "Ihre Anmeldedaten liegen im sicheren Speicher des Systems. Einstellungen, der Offline-Cache und noch nicht synchronisierte Änderungen liegen im Speicher der App.",
"phone_title": "Auf diesem Gerät",
"push": "Wenn Benachrichtigungen an sind, meldet Ihr Server dem Push-Relay ({relay}), dass sich etwas geändert hat, und das Relay weckt die App über {service}. Das Relay sieht ein Gerätetoken und eine Änderungsmarke, nie Ihre Mails. Unter Benachrichtigungen können Sie ein eigenes Relay eintragen.",
"push_title": "Push-Benachrichtigungen",
"server": "Mails, Kalender, Kontakte und Dateien werden von deinem Server geladen und dort gespeichert. Außer den Verbindungen unten spricht die App nur mit deinem Server.",
"server_title": "Dein Mailserver",
"server": "Mails, Kalender, Kontakte und Dateien werden von Ihrem Server geladen und dort gespeichert. Außer den Verbindungen unten spricht die App nur mit Ihrem Server.",
"server_title": "Ihr Mailserver",
"service_apple": "den Push-Dienst von Apple",
"service_google": "den Push-Dienst von Google",
"updates": "Die App fragt bei GitHub nach der neuesten Version, um dir Updates anzuzeigen.",
"updates_android": "Die App fragt bei GitHub nach der neuesten Version, um dir Updates anzuzeigen. Ausschalten kannst du das unter Updates."
"updates": "Die App fragt bei GitHub nach der neuesten Version, um Ihnen Updates anzuzeigen.",
"updates_android": "Die App fragt bei GitHub nach der neuesten Version, um Ihnen Updates anzuzeigen. Ausschalten können Sie das unter Updates."
},
"summary": {
"events_off": "Termine aus",
@@ -1205,7 +1483,7 @@
"layout": "Schnellaktionen, Wischen, Reihenfolge",
"reading": "Gelesen-Status, Löschen, Anhänge",
"composing": "Signatur, Sendeverzögerung, Entwürfe",
"identities": "Adressen, von denen du sendest",
"identities": "Adressen, von denen Sie senden",
"vacation": "Automatische Antwort bei Abwesenheit",
"filters": "Regeln für eingehende Mails",
"templates": "Wiederverwendbare Nachrichten",
@@ -1217,15 +1495,77 @@
"contacts": "Adressbücher, Sortierung",
"files": "Ansicht, Sortierung, Vorschaubilder",
"sidebar_apps": "Web-Apps im Seitenmenü",
"updates": "Neue Versionen installieren"
"updates": "Neue Versionen installieren",
"accessibility": "Schriftgröße, Bewegung, Bestätigungen"
},
"troubleshooting": {
"cache_desc": "Wenn eine Liste veraltet oder falsch aussieht.",
"cache_title": "Zwischengespeicherte Daten",
"row_desc": "Daten neu laden, Debug-Protokoll",
"title": "Fehlerbehebung"
},
"composer": {
"autosave": {
"1m": "Jede Minute",
"2m": "Alle 2 Minuten",
"30s": "Alle 30 Sekunden",
"5m": "Alle 5 Minuten",
"description": "Wie oft Entwürfe automatisch gespeichert werden",
"label": "Automatisches Speichern"
},
"description": "Konfigurieren Sie die E-Mail-Erstellungseinstellungen",
"title": "Editor"
},
"tab_groups": {
"advanced": "Erweitert",
"appearance": "Darstellung",
"general": "Allgemein",
"privacy": "Datenschutz & Sicherheit"
},
"accessibility": {
"presets": {
"dyslexia": {
"description_mobile": "Atkinson Hyperlegible als Schrift, auch für Nachrichten als reiner Text."
},
"applied_mobile": "{name} hat {count, plural, one {# Einstellung} other {# Einstellungen}} geändert",
"apply": "Anwenden",
"apply_named": "{name} anwenden",
"low-vision": {
"description_mobile": "Text auf 150 % und Beschriftungen unter den Symbolleistensymbolen."
},
"motion": {
"description_mobile": "Keine Animationen und keine gleitenden Übergänge."
},
"screen-reader": {
"description_mobile": "Keine Gruppierung nach Konversation, jede Nachricht hat also eine eigene Zeile."
}
},
"font": {
"description_ios": "Atkinson Hyperlegible und Lexend sind so gestaltet, dass sich die Buchstaben gut unterscheiden; OpenDyslexic hilft manchen Menschen mit Legasthenie. Auf diesem Gerät gilt die Schrift für Nachrichten als reiner Text."
},
"cvd_palette": {
"description_mobile": "Verwendet Farben für Tags und Kalender, die bei Rot-Grün- oder Blau-Gelb-Schwäche unterscheidbar bleiben."
},
"plain_language": {
"description_mobile": "Einfachere Formulierungen in Einstellungen und Fehlermeldungen. Auf Deutsch und Englisch verfügbar."
},
"reduce_motion": {
"description_mobile": "Schaltet gleitende Bereiche und Übergänge aus. Die App tut das auch, wenn in den Einstellungen Ihres Geräts „Bewegung reduzieren“ aktiviert ist."
},
"text_size": {
"description_mobile": "Skaliert den Text der App zusätzlich zur Schriftgröße Ihres Geräts. 100 % entspricht der Größe Ihres Geräts."
},
"toast_duration": {
"automatic": "Automatisch (länger mit Screenreader)"
}
}
},
"sharing": {
"directory_failed": "Die Liste der Benutzer und Gruppen konnte nicht geladen werden.",
"directory_not_browsable": "Ihr Server erlaubt Ihnen nicht, sein Benutzerverzeichnis zu durchsuchen.",
"directory_restricted": "Ihr Server erlaubt kein Durchsuchen seines Benutzerverzeichnisses, daher werden nur Ihre Gruppen und Personen aufgeführt, die etwas mit Ihnen geteilt haben. Ein Administrator kann Verzeichnisabfragen in den Freigabeeinstellungen von Stalwart aktivieren.",
"unknown_user": "Unbekannter Benutzer"
},
"sidebar": {
"account_error": "Aktion erforderlich",
"remove_account_hint": "Lange drücken, um dieses Konto zu entfernen",
@@ -1237,7 +1577,8 @@
"sign_out_confirm_message": "Das Konto wird von diesem Gerät entfernt. Sie können sich jederzeit wieder anmelden.",
"sign_out_confirm_title": "Abmelden?",
"sign_out_confirm_title_account": "Von {account} abmelden?",
"tab_unavailable": "{name} (nicht verfügbar)"
"tab_unavailable": "{name} (nicht verfügbar)",
"account_sign_in_again": "Erneut anmelden"
},
"unified_mailbox": {
"accounts_failed": "{count, plural, one {# Konto konnte nicht geladen werden} other {# Konten konnten nicht geladen werden}}"
@@ -1479,5 +1820,110 @@
"unsupported": "Abwesenheitsnotizen sind nicht verfügbar",
"until": "Bis {date}"
}
},
"deep_link": {
"open_failed": "Konnte nicht geöffnet werden"
},
"encryption": {
"armored_key": "Schlüssel im ASCII-Format",
"banner": {
"checking": "Verschlüsselung und Signatur werden geprüft…",
"decrypted": "Entschlüsselt ({kind})",
"failed": "Entschlüsseln fehlgeschlagen: {reason}",
"invalid": "Ungültige Signatur ({kind})",
"invalid_reason": "Ungültige Signatur ({kind}): {reason}",
"locked": "Verschlüsselt ({kind}). Entsperren Sie Ihren Schlüssel, um die Nachricht zu lesen.",
"no_key": "Verschlüsselt ({kind}) für einen Schlüssel, der nicht auf diesem Gerät ist.",
"self_signed": "Das Zertifikat ist selbstsigniert und nicht von einer bekannten Stelle ausgestellt.",
"signed": "Gültige Signatur ({kind})",
"signed_by": "Gültige Signatur ({kind}) von {signer}",
"signer_mismatch": "Der Unterzeichner ist nicht der unter „Von“ angezeigte Absender.",
"unknown_key": "Signiert ({kind}) mit einem Schlüssel, den Sie nicht importiert haben.",
"unsupported": "Verschlüsselt mit {algorithm}, das die App nicht entschlüsseln kann.",
"key_offered": "Die Nachricht enthält einen öffentlichen Schlüssel für {email}. Fingerabdruck: {fingerprint}",
"save_key": "Schlüssel für {email} speichern",
"save_key_failed": "Der Schlüssel konnte nicht gespeichert werden"
},
"check_description": "Führt jeden Algorithmus, den PGP und S/MIME verwenden, auf diesem Gerät aus.",
"check_run": "Prüfung starten",
"check_title": "Verschlüsselungsunterstützung prüfen",
"delete_private_message": "{key} und der zugehörige private Schlüssel werden von diesem Gerät entfernt. Damit verschlüsselte E-Mails können hier nicht mehr gelesen werden, es sei denn, Sie importieren ihn erneut.",
"delete_public_message": "{key} wird von diesem Gerät entfernt.",
"delete_title": "Schlüssel löschen?",
"expired": "Abgelaufen am {date}",
"expires": "Läuft am {date} ab",
"find_key": "Schlüssel auf keys.openpgp.org suchen",
"import": "Importieren",
"import_cert": "Zertifikat importieren",
"import_p12": "S/MIME-Zertifikat importieren (.p12)",
"import_pgp_file": "PGP-Schlüsseldatei importieren",
"import_public_file": "Datei mit öffentlichem Schlüssel importieren",
"imported": "{key} importiert",
"lock": "Sperren",
"lock_all": "Alle Schlüssel sperren",
"lock_all_description": "Die entsperrten Schlüssel vergessen, bis Sie ihre Passphrase erneut eingeben.",
"locked": "Gesperrt",
"locked_all": "Schlüssel gesperrt",
"lookup_confirm": "Vergleichen Sie diesen Fingerabdruck mit dem, den Ihnen der Inhaber gegeben hat (persönlich, per Telefon), bevor Sie ihn importieren.",
"lookup_none": "Kein Schlüssel gefunden.",
"lookup_placeholder": "E-Mail-Adresse oder Fingerabdruck",
"lookup_search": "Suchen",
"lookup_verified": "Der Schlüssel hat den Fingerabdruck, nach dem Sie gesucht haben.",
"no_other_keys": "Noch keine.",
"no_own_keys": "Noch keine Schlüssel.",
"not_a_key": "Dies ist kein Schlüssel und kein Zertifikat, das die App lesen kann.",
"others_description": "Öffentliche Schlüssel und Zertifikate, um damit zu verschlüsseln und Signaturen zu prüfen. Schlüssel aus signierten E-Mails und aus Ihren Kontakten werden ebenfalls verwendet.",
"others_title": "Schlüssel anderer Personen",
"own_description": "E-Mails mit OpenPGP oder S/MIME signieren und entschlüsseln. Private Schlüssel bleiben im sicheren Speicher dieses Geräts und werden einmal pro App-Sitzung mit ihrer Passphrase entsperrt.",
"own_title": "Ihre Schlüssel",
"p12_password_message": "Geben Sie das Passwort ein, mit dem die .p12-Datei exportiert wurde. Sie brauchen es erneut, um den Schlüssel nach einem Neustart der App zu entsperren.",
"p12_password_title": "Zertifikatspasswort",
"passphrase": "Passphrase",
"passphrase_message": "Geben Sie die Passphrase dieses Schlüssels ein. Sie wird jetzt geprüft und einmal pro App-Sitzung erneut abgefragt.",
"passphrase_title": "Schlüssel-Passphrase",
"password": "Passwort",
"paste_pgp": "PGP-Schlüssel einfügen",
"paste_public": "Öffentlichen Schlüssel einfügen",
"share_cert": "Zertifikat teilen",
"share_key": "Öffentlichen Schlüssel teilen",
"tab": "Verschlüsselung",
"title": "Verschlüsselung",
"unlock": "Entsperren",
"unlock_pgp_message": "Geben Sie die Passphrase Ihres PGP-Schlüssels {key} ein.",
"unlock_read_reason": "Die Nachricht ist für diesen Schlüssel verschlüsselt.",
"unlock_smime_message": "Geben Sie das Passwort Ihres S/MIME-Zertifikats {key} ein.",
"unlock_title": "Schlüssel entsperren",
"unlocked": "Entsperrt",
"unlocking": "Wird entsperrt…",
"wrong_passphrase": "Diese Passphrase ist nicht richtig."
},
"outbox": {
"discard": "Verwerfen",
"discard_body": "Sie wird nicht gesendet, und ihr Text geht verloren.",
"discard_title": "Diese Nachricht verwerfen?",
"failed_count": "{count, plural, one {# Nachricht} other {# Nachrichten}} nicht gesendet",
"move_failed_body": "Die Nachricht bleibt im Postausgang. Versuchen Sie es erneut, wenn die Verbindung wieder besteht.",
"move_failed_title": "Verschieben in Entwürfe fehlgeschlagen",
"move_to_drafts": "In Entwürfe verschieben",
"open_hint": "Öffnet den Postausgang",
"queued_body": "Sie wird gesendet, sobald Sie wieder online sind.",
"queued_title": "Im Postausgang gespeichert",
"retry": "Erneut versuchen",
"send_failed": "Eine Nachricht im Postausgang konnte nicht gesendet werden",
"sent": "Nachricht aus dem Postausgang gesendet",
"status_failed": "Nicht gesendet",
"status_failed_reason": "Nicht gesendet: {reason}",
"status_offline": "Wartet auf eine Verbindung",
"status_retrying": "Wartet auf das Senden",
"status_sending": "Wird gesendet…",
"title": "Postausgang",
"waiting": "{count, plural, one {# Nachricht} other {# Nachrichten}} im Postausgang",
"already_sent": "Diese Nachricht wurde bereits gesendet"
},
"push": {
"ios": {
"more_messages": "und {count} weitere",
"no_subject": "(kein Betreff)"
}
}
}
+596 -14
View File
File diff suppressed because it is too large Load Diff
+780 -35
View File
File diff suppressed because it is too large Load Diff
+1844
View File
File diff suppressed because it is too large Load Diff
+768 -35
View File
File diff suppressed because it is too large Load Diff
+778 -35
View File
File diff suppressed because it is too large Load Diff
+772 -35
View File
File diff suppressed because it is too large Load Diff
+770 -35
View File
File diff suppressed because it is too large Load Diff
+776 -35
View File
File diff suppressed because it is too large Load Diff
+774 -35
View File
File diff suppressed because it is too large Load Diff
+772 -35
View File
File diff suppressed because it is too large Load Diff
+773 -35
View File
File diff suppressed because it is too large Load Diff
+769 -35
View File
File diff suppressed because it is too large Load Diff
+771 -35
View File
File diff suppressed because it is too large Load Diff
+774 -35
View File
File diff suppressed because it is too large Load Diff
+774 -35
View File
File diff suppressed because it is too large Load Diff
+777 -35
View File
File diff suppressed because it is too large Load Diff
+774 -35
View File
File diff suppressed because it is too large Load Diff
+777 -35
View File
File diff suppressed because it is too large Load Diff
+773 -35
View File
File diff suppressed because it is too large Load Diff
+1844
View File
File diff suppressed because it is too large Load Diff
+775 -35
View File
File diff suppressed because it is too large Load Diff
+776 -35
View File
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More