Commit Graph
1413 Commits
Author SHA1 Message Date
Linus Rath 23c4ab87da feat: confirm sending without a subject instead of blocking it #684 2026-08-06 17:31:50 +02:00
Linus Rath e2da05c130 feat: deep links for mail, calendar, contacts, files, settings and improve screen reader support
#733 #722 #721  #720  #719
2026-08-06 16:47:20 +02:00
Linus Rath b3f5f51efa fix: stop minting duplicate "Trusted Senders" address books #730 2026-08-06 15:07:54 +02:00
Linus Rath 6f293bbf04 Merge pull request #729 from MrDeik/agent/push-notification-onboarding
feat: add background notification onboarding
2026-08-06 14:47:31 +02:00
Linus Rath e9c4929913 fix: address review feedback on push onboarding
- track session-only dismissal per account instead of a single account id
- label the close button with a dedicated dismiss aria string in all locales
- read PWA install prompt visibility from a shared flag and subscribe in a
  layout effect so an early visibility event cannot be missed
- restore real timers after the push onboarding tests
2026-08-06 14:46:32 +02:00
Linus Rath d26101417c Merge pull request #751 from romanvru/feat/plugin-attachment-offload
Plugin API: attachment offload from onBeforeBlobUpload, binary http.post, blob-read for upfiles.get
2026-08-06 14:09:49 +02:00
Linus Rath f056db5a05 fix(plugins): make attachment offload workable and keep the write gate closed
Review follow-up to #751.

The offload path could not actually run: onBeforeBlobUpload goes through
HookBus.transform, which capped every handler at the 5s default. A handler
that pushes the file to an external store blows past that, and on timeout
the error is swallowed, the original file id is kept, and the composer
attaches the big file anyway - silently - while three such timeouts in a
minute auto-disable the plugin. Buses can now carry their own per-handler
budget and this one gets 120s. The same ceiling existed inside the sandbox,
where http.post/http.fetch were bounded by the 30s default API timeout;
those two are transfers rather than round-trips and now get 120s as well.

PRIVILEGED_ONLY_METHODS listed 'upfiles.set', which matches no dispatched
method - the dispatcher calls it 'upfiles.save'. So the write was never
tier-gated despite the comment saying it was, and an untrusted plugin with
email:blob-write could replace the bytes of a file the user was about to
send. Fixed the name. Relaxing the read while that stayed open was the
wrong order.

Moving upfiles.get to email:blob-read is kept, but it is a wider grant than
the PR described: onBeforeBlobUpload hands the file id to every registered
handler, so the id is not a secret and any untrusted plugin holding
email:blob-read can read every file the user attaches. The consent copy now
says that; email:blob-write's copy was describing the read and now describes
the replace it actually gates.

Also in the composer offload path:
- sanitize the handler's html before it reaches the document and the sent
  message, and insert it through the editor at the caret rather than
  concatenating it after the signature and quoted block
- honour an abort that lands while the handler is offloading
- delete the right staged file when the handler re-saved it under a new id
  via upfiles.save, which otherwise stranded a copy in IndexedDB forever
2026-08-06 14:08:07 +02:00
Roman Varlakov 94c7a454cc feat(plugins): move upfiles.get behind email:blob-read, off the privileged tier
upfiles.get is a read, but sat behind the email:blob-write permission and
the privileged tier, which requires crypto:full plus a signed managed
bundle. That put a simple attachment-offload plugin in the same trust
bucket as crypto plugins that read stored message bytes.

The method only returns a file the user just attached in this session,
looked up by the unguessable UUID the composer itself passes to the
onBeforeBlobUpload hook. It cannot reach server-side message blobs; those
stay behind jmap.fetchBlob on the privileged tier. So gate it behind
email:blob-read (consent copy already covers attachment bytes) and drop
the tier requirement.

Note: the privileged set also lists 'upfiles.set', but the dispatched
method is named 'upfiles.save', so that entry never matched; left as-is
here, flagged in the PR.
2026-08-06 17:25:52 +06:00
Roman Varlakov 600ab02034 feat(plugins): binary Blob/File bodies for api.http.post
api.http.post(path, body, options) now accepts a Blob/File body and sends
it as-is to the same-origin route instead of JSON.stringify-ing it into
'{}'. The plugin may only supply Content-Type and X-Plugin-* metadata
headers; anything else is refused, and the host attaches its credential
headers after the plugin's, so they can never be overridden. Non-Blob
bodies keep the stock JSON behaviour. The sandbox facade previously
dropped the third argument, so the options never reached the host.
2026-08-06 17:25:42 +06:00
Roman Varlakov 656a9c9c3b feat(plugins): let onBeforeBlobUpload offload an attachment
A handler can now return an ExternalAttachmentResult ({externalAttachment:
true, html, text}) instead of a file id. The composer then deletes the
staged file, removes the attachment and appends the replacement content
to the body, so a plugin can push a large file to an external store and
leave a download link behind. Returning a string (or nothing) keeps the
stock upload path byte-for-byte.

Also adds plainTextMode to the upload callback deps, which the new branch
reads to pick between the html and text replacement.
2026-08-06 17:25:27 +06:00
Linus Rath 5ed25dda98 Merge pull request #743 from paulhenry46/plugin-api-crypto
feat: new crypto methods for plugin API
2026-08-06 12:25:01 +02:00
Paulhenry Saux 1979c27d9e fix: use correct method 'crypto.getPublicKeys' in crypto plugin API 2026-08-06 07:36:18 +02:00
Linus Rath d68ebb55c0 Merge pull request #746 from bulwarkmail/copilot/fix-comments-in-review-thread
Restrict shared-account capability fallback to explicit non-personal accounts
2026-08-06 00:54:23 +02:00
Linus Rath 4b2970ba80 Merge pull request #706 from polastre/check-account-capabilities
fix(jmap): hide Contacts and Calendars when account lacks capability
2026-08-06 00:54:13 +02:00
copilot-swe-agent[bot]andrathlinus 6f9fc53502 fix: require explicit shared account fallback for contacts/calendars
Co-authored-by: rathlinus <139418639+rathlinus@users.noreply.github.com>
2026-08-05 22:41:26 +00:00
copilot-swe-agent[bot] aa2bc9985c Initial plan 2026-08-05 22:38:39 +00:00
Linus Rath ab1f89d633 Merge pull request #740 from koolninad/feat/mail-list-refresh-button
feat: add a refresh button to the mail list toolbar
2026-08-06 00:33:40 +02:00
Linus Rath 57d802db85 Merge pull request #704 from guisea/fix/sent-attachment-list-hidden-when-unwrapped
fix: keep sent message/rfc822 attachment visible after inline unwrapping
2026-08-06 00:30:23 +02:00
Linus Rath 87a65e7dd9 Merge pull request #742 from paulhenry46/stalwart-public-keys-encryption-at-rest
feat: manage public keys and at rest encryption
2026-08-06 00:29:02 +02:00
Paulhenry Saux d573cda920 feat: add new crypto API plugin methods 2026-08-05 21:55:50 +02:00
Paulhenry Saux 3b519c3b3b feat: manage public keys and at rest encryption 2026-08-05 21:46:42 +02:00
Ninad Chandorkar b797ea88b8 feat: add a refresh button to the mail list toolbar
The only ways to re-fetch mail are the browser refresh gestures (F5,
Ctrl/Cmd+R, pull-to-refresh) that useRefreshGesture intercepts. On a
desktop that is discoverable enough, but there is no visible control,
and users who reach for one find nothing.

Extract the gesture's inline onRefresh into a shared handleManualRefresh
with a spinner state, then reuse it for a refresh button next to the
filter control in the mail-list toolbar. The icon spins while the fetch
is in flight and the button is disabled until it settles, so repeated
clicks can't stack requests.

Uses the existing common.refresh translation key, so no new strings.

Also guard the login submit with rememberMeEnabled: the checkbox is only
rendered when the server has a SESSION_SECRET, but the value was passed
through unconditionally, asking for a credential cookie the server will
not write.
2026-08-05 23:37:36 +05:30
Linus Rath e27f17b796 Merge pull request #736 from dealerweb/fix/calendar-parse-debug-log
fix: route the calendar parse dump through the debug logger
2026-08-05 17:20:32 +02:00
Linus Rath da17464e05 Merge pull request #737 from dealerweb/fix/internal-mailto-links
fix: open address links in the built-in composer
2026-08-05 17:20:18 +02:00
Linus Rath 151af5ced0 Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-08-05 17:19:40 +02:00
Linus Rath 0fe484efda fix: time out stalled JMAP requests so sends can't hang forever #702 2026-08-05 17:19:10 +02:00
dealerweb 04996a3af1 fix: open address links in the built-in composer
Clicking an address in the app's own chrome - the recipient card, the
contact sidebar, the contact details - handed a mailto: URL to the OS
mail handler. In a webmail client that goes nowhere: the browser asks
which application should open the link, and the message is composed
somewhere else, if at all. Only users who registered Bulwark itself as
their system mailto handler got a usable result, and even then via a
detour through the OS into a new tab.

A new MailtoLink component sends the click to the built-in composer
instead. It stays a real anchor with an href, so "Copy email address",
middle click and assistive technology keep working; only the plain
left click is redirected, and only when a listener takes it - otherwise
the browser's own behaviour still applies. Modified clicks are left
alone.

The request reaches the mail page through a window event, the way
`bulwark:rate-limit-blocked` already does, rather than threading a
callback through three component levels and five call sites. It goes to
openMailtoDraft rather than the protocol path: an in-app click carries
its own context - the account being read - so the account picker that an
external mailto: needs would only get in the way here.

Message content is deliberately untouched. Plain text bodies render into
the main document rather than the sandboxed frame and their sanitizer
permits mailto:, so a document-wide interceptor would have swept up
untrusted links too; a component cannot be reached from message HTML at
all.
2026-08-05 14:42:24 +02:00
dealerweb 264a8493fb fix: route the calendar parse dump through the debug logger
Opening a message with a calendar invitation printed the whole parsed
event to the console on every render - title, description, attendees and
location included - through a leftover `[PARSE DEBUG]` console.log.

The surrounding file already logs via debug.log('calendar', ...) in 22
places, which honours the debugMode setting and its category filters, so
this one line was the outlier. It also ran JSON.stringify with
indentation unconditionally, even though nothing was listening.

Passing the object to debug.log instead leaves the formatting to the
logger, so nothing is serialised unless the calendar debug category is
actually switched on.
2026-08-05 14:40:21 +02:00
Eduard Polonskyi e036e10004 fix: sequence PWA and push onboarding 2026-08-03 15:31:17 +02:00
Eduard Polonskyi 573fab8b83 feat: add background notification onboarding 2026-08-03 03:23:08 +02:00
Linus Rath e94a1429d5 Merge pull request #724 from paulhenry46/userlogout-api-hook
feat: implement existing hooks onBeforeLogout and onAfterLogout + new plugin API method
2026-08-01 23:09:39 +02:00
Linus Rath 15982c2468 Merge pull request #708 from paulhenry46/user-api-plugin
feat(plugins): add 2 new methods : getAccounts and getIdentities
2026-08-01 23:06:53 +02:00
Paulhenry Saux d15c58f8da feat: implement logout hook and add a new plugin api method to perform logout 2026-08-01 21:46:33 +02:00
Paulhenry Saux 6698ec8456 Merge branch 'bulwarkmail:main' into user-api-plugin 2026-08-01 20:24:08 +02:00
Linus Rath e738941950 Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-08-01 12:28:21 +02:00
Linus Rath 1890cade08 fix: surface underlying network error cause in JMAP passthrough failures 2026-08-01 11:47:16 +02:00
Linus Rath fc7fec44a8 Merge pull request #714 from MathyV/respect-max-calls
fix: make bulwark respect server limits
2026-07-31 05:43:26 +02:00
Mathy Vanvoorden 1652a0ec62 fix: make bulwark respect server limits
If you have a large number of tags, getTagCounts would not be able to get the
unread counts because it did not respect maxCallsInRequest, even though the
value was actually read out, it was just ignored. There are also other places
where the limits were not respected.

Batching is now generalized in a helper that also takes maxObjectsInSet, which
also was ignored, into account and is applied to all functions.

In addition, the dev mock now also advertises and enforces the limits, so these
issues can get picked up during development.

Possible closes #699
Possibly closes #399
2026-07-30 22:11:40 +02:00
Linus Rath e659fe3d38 feat: allow contact cards for organizations #701 2026-07-30 19:44:36 +02:00
Linus Rath 348e032dce fix: files show creation date instead of modification date #700 2026-07-30 19:41:02 +02:00
Linus Rath a6c15b8ad6 fix: empty folder stopped after 500 emails #711 2026-07-30 19:40:25 +02:00
Linus Rath 5a2bc6b671 Merge branch 'main' of https://github.com/bulwarkmail/webmail
# Conflicts:
#	app/api/dev-jmap/[...path]/route.ts
2026-07-30 19:17:43 +02:00
Linus Rath 59bc7fd64c fix: reply to own thread message addresses original recipients #703 2026-07-30 19:02:39 +02:00
Linus Rath aa7a814b86 test: rewrite mock email content 2026-07-30 18:44:53 +02:00
Linus Rath c5c6509867 Merge pull request #710 from MathyV/nested-tags
Nested tags
2026-07-30 18:41:22 +02:00
Linus Rath 1cdbf75270 fix: use full tag path in drag-drop toasts, fresh email in context menu markAsRead
Nested tag toasts from drag-and-drop only showed the leaf name for
non-root tags, contradicting the comment above it and making two
same-named leaves under different parents (e.g. Personal/Receipts vs
Work/Receipts) indistinguishable in the toast.

The context menu's markAsRead handler was the one action left reading
the stale contextMenu.data instead of the live-refreshed
contextMenuEmail introduced alongside it, so it could act on outdated
email state while every sibling handler was already updated.
2026-07-30 18:39:49 +02:00
Mathy Vanvoorden ea7892b497 feat: Change the dev mode defaults to include nested tags 2026-07-29 19:52:31 +02:00
Mathy Vanvoorden d52dfebad4 Fix Catalan translation warnings 2026-07-29 19:16:31 +02:00
Mathy Vanvoorden 7bb58f4f9f Add translations for new tag functionality 2026-07-29 19:14:28 +02:00
Mathy Vanvoorden f1e1ed1df7 fix: make the tint of selected rows work the same way in dark and light mode 2026-07-29 17:18:42 +02:00