Commit Graph
33 Commits
Author SHA1 Message Date
Linus Rath 4b60fcc1e6 fix: make the offline outbox never send twice, never lose mail silently and never skip a plugin #523
The review of offline mode found ways to send a message twice, to lose
unsent mail without a word, and to send mail without its signing,
encryption or check plugin. This fixes them and the smaller findings:

- each outbox message is taken for sending in one database transaction
  under a Web Lock per account; only a failure before the submission
  request (SendNotStartedError from the client) puts it back, any other
  one marks it "may have gone out"; read-backs after the submission no
  longer fail a send; a send of a closed tab turns uncertain after a minute
- every sign-out the user didn't confirm names the deleted messages on
  the sign-in page, or in a toast when another account stays signed in
- nothing is queued while an enabled plugin isn't running (after a start
  without the network none can); the composer says why
- the outbox waits for a fresh policy and holds messages the rules have
  since made stricter (a required label, a required send check)
- another tab switching offline mode off sticks: only switching it on
  may create the database
- only attachments of kept messages are kept, filed with their message,
  deleted with it and under the cap between syncs; file names and types
  are sealed; folders that went away leave the scope, and a folder that
  couldn't be asked keeps its mail without holding the others back
- a group scope that forbids offline mode waits until the account's
  groups are known; one server down no longer holds another's mail
- an offline start only when the server is really unreachable, and the
  real sign-in follows by itself once it answers
- the service worker files pages and chunks under the build that served
  them, and the shell list of another build isn't kept under this one
- a raw send counts as the composer's only from the plugin running its
  takeover, for that identity
- errors and refusals are in all languages; the banner tells "offline"
  from "server can't be reached"; outbox buttons name their message;
  changes and messages go in the order they were made
2026-11-01 14:19:54 +01:00
Linus Rath 52188f54d6 Merge branch 'feat/enterprise-pack' into feat/enterprise-pack-f 2026-11-01 13:50:22 +01:00
Linus Rath 91be465cbc feat: version the service worker by build and offer a reload after a deploy 2026-11-01 11:13:31 +01:00
Linus Rath aa88e1b279 Merge branch 'main' into feat/observability 2026-11-01 04:21:12 +01:00
Linus Rath be5a5e88ec ci: upload browser source maps to Bugsink and keep every map out of what ships
Every image, the standalone tarballs and the Lite zips are built with
source maps (`BULWARK_SOURCE_MAPS=1`). Turbopack builds give each chunk
a debug ID themselves; the webpack build in the Docker image gets them
from `sentry-cli sourcemaps inject`. `scripts/sourcemaps.mjs collect`
then moves every map out of the build before anything is packed, and
`scripts/sourcemaps.mjs check` fails the build when a `.map` file, an
inline map or a `sourceMappingURL` to a missing file would ship.

- Docker images: a `sourcemaps` stage holds only the maps; the amd64
  job exports it (a cache hit) and uploads it, since both platforms
  build identical chunks
- standalone tarballs, the Lite zips and the Lite image upload their
  own maps and check what they ship
- `scripts/upload-sourcemaps.sh` uploads to `bulwark-instances` and
  `bulwark-telemetry` (Bugsink keeps maps per project) and skips with a
  notice when the `BUGSINK_AUTH_TOKEN` secret is missing
2026-11-01 03:31:48 +01:00
Linus Rath 7cb0c5f972 chore: use @prometheus-io/client, the maintained successor of the deprecated prom-client 2026-11-01 03:20:52 +01:00
Linus Rath 481c4b6030 feat: serve Prometheus metrics and split health into live and ready
- `/api/metrics` in Prometheus text format, off until `METRICS_TOKEN`
  or `METRICS_TOKEN_FILE` is set (404 before, 401 without the bearer
  token): requests per route group with status class and latency,
  sign-in outcomes, calls to Stalwart, WOPI, translation, the plugin
  proxy and OAuth, browser errors, errors today, process memory,
  event-loop lag, uptime and build info. Labels come from fixed lists.
  Each replica serves its own.
- `/api/health/live` answers while the process runs;
  `/api/health/ready` checks the config, the shared store, writable
  data dirs and Stalwart's session resource. `/api/health` and
  `/api/ready` are unchanged.
- the Docker image gets a HEALTHCHECK on the live endpoint
- adds prom-client, and the OpenTelemetry packages the tracing step
  loads on demand
2026-11-01 02:59:40 +01:00
Linus Rath d2a83a4ee9 fix: stop proxying locale rewrites back to the server when it listens on 127.0.0.1 #1487 2026-11-01 02:41:42 +01:00
Linus Rath d781e8762e Merge branch 'feat/multi-instance' 2026-11-01 01:43:15 +01:00
Linus Rath a03f76ec85 feat: send X-Powered-By: bulwarkmail.org on every response 2026-11-01 00:21:32 +01:00
Linus Rath df90492258 feat: add a shared store that keeps today's files by default and can use a redis-protocol server #1066 2026-10-31 22:40:44 +01:00
Linus Rath be28fb8ed7 fix: keep the production build's type check within 2 GB of memory #1431 2026-10-29 20:11:08 +01:00
Linus Rath 121d2d5e31 fix: stop buffering up to 100 MB of every unauthenticated request body before routing 2026-10-26 14:52:04 +01:00
Linus Rath e38e326efb perf: load plugin frames as a plain page instead of a Next app
Every plugin iframe loaded /plugin-sandbox, a force-dynamic Next page.
Each frame then fetched, parsed and hydrated Next's client router on top
of React and the runtime, and the server rendered the page once per
frame, one after another when a dozen plugins started together.
Sandboxed frames have opaque origins, so the browser's code cache never
helps, and they run on the host page's main thread: with 12 plugins
the frames took 2.5-3 s to come up after the app had loaded.

The sandbox routes now return a small HTML page with the UI kit's
stylesheet and the runtime as an ES module, both built ahead of time:

- `scripts/build-plugin-sandbox.mjs` bundles the runtime with esbuild
  and compiles `plugin-kit.css` with the app's Tailwind setup into
  `public/plugin-sandbox-assets/`, with a manifest. Named Tabler imports
  are pointed at the icons' own files, so the full icon set stays in a
  chunk that only loads for an icon name the kit doesn't ship.
- `npm run build`, `npm run dev`, the Dockerfile and the perf-guard
  workflow run it before `next build`.
- `lib/plugin-sandbox/sandbox-document.ts` writes the page; proxy.ts
  still sets the sandbox CSP and the script carries its nonce.
- The assets are served from public/ with CORS and immutable caching.

A frame on an idle page is ready in 255 ms instead of 362 ms, eleven at
once in 1.0 s instead of 2.1 s.
2026-10-25 02:01:47 +01:00
Linus Rath 42cb8a8bbe chore: let a dev server announce another app version for plugin tests 2026-10-20 18:17:00 +02:00
Linus Rath 10b4d2a9db feat: rework the admin panel around a settings-style sidebar and one URL per page
The old panel kept its active tab in a store, so every page sat at
/admin, the sidebar duplicated the webmail rail by hand, and each page
was a stack of boxed cards with their own save buttons. Plugin settings
were only reachable through a gear icon in a row of five.

- one catch-all route for the whole panel; pages switch through
  history.pushState, so `/admin/plugins/<id>`, the back button, bookmarks
  and connector links all work without a route transition
- sidebar laid out like the user settings: back to mail, search over
  pages and their sections, two group headings, change password and
  sign out at the bottom; the phone shows a list, then the page
- pages use SettingsSection rows instead of nested cards, with one page
  header, one flash message and one save bar at the bottom (Discard,
  Save, Ctrl+S); the unsaved-changes prompt still guards leaving
- plugins and themes are lists of rows with a toggle each; a row opens
  the extension's own page with its settings, force-on, permissions,
  kept versions and removal
- overview shows stat tiles, server facts and recent activity
- old `/admin?tab=x` links redirect; the tab store is gone and the
  connector registry resolves admin links to the page URL
- `NEXT_TURBOPACK_ROOT` overrides the Turbopack root so `next dev` runs
  from a worktree with a junctioned node_modules
2026-10-20 15:16:05 +02:00
Linus Rath 1fd5d0558c fix: send HSTS on TLS requests and drop the X-Powered-By header 2026-10-19 01:32:27 +02:00
Linus Rath 31205e44d8 fix: stop cutting off uploads over 10 MB that pass through the server, such as office documents saved from the editor 2026-10-14 04:17:51 +02:00
Linus Rath 5c3c3bea99 fix: keep next's metadata modules in the standalone build so the server starts 2026-09-26 22:15:53 +02:00
Linus Rath 4f386f7a78 fix: keep runtime state and secrets out of the standalone build and image
The standalone trace excluded only `repos/`. It follows the runtime's
file reads, so building from a checkout that had been run copied
`data/` (admin password hash, plugin signing key, audit log, synced
settings), `local-data/` and `.env` files into `.next/standalone`, and
the Docker build context did not exclude `data/` or `local-data/`
either. Both now leave them out.
2026-09-26 16:19:26 +02:00
Linus Rath a4bc303f45 fix: stop office attachment launch hanging while checking the blob #1047 2026-09-23 19:03:37 +02:00
Linus Rath 5de98bc04b feat: add Bulwark Lite static export build 2026-09-17 23:54:33 +02:00
mulatta 1c116fde2d fix: allow sandbox chunk loading with CORS #922 2026-09-09 01:29:25 +09:00
Mathy Vanvoorden ea7892b497 feat: Change the dev mode defaults to include nested tags 2026-07-29 19:52:31 +02:00
Linus Rath 285b4e349c fix: add outputFileTracingExcludes to optimize Turbopack memory tracing 2026-05-16 16:35:06 +02:00
Linus Rath 1b0ca8967e feat: bundle plugin src/ on demand via esbuild 2026-05-05 18:35:22 +02:00
Linus Rath 599fa66822 fix: show git commit in About instead of "unknown" 2026-05-02 00:28:08 +02:00
Linus Rath 841513e510 feat: support subpath deployment with NEXT_PUBLIC_BASE_PATH environment variable 2026-05-01 14:57:49 +02:00
Linus Rath 1bdc51dcc7 feat: implement version badge and easter egg in settings 2026-04-02 14:31:25 +02:00
Linus Rath a9a8710909 feat: mobile long-press context menu, settings logout button
Mobile:
- Add useLongPress hook (500ms, movement cancellation, press feedback)
- Disable drag-and-drop on mobile in useEmailDrag
- Wire long-press context menu to all email list item components
- Add select-none and visual press feedback (scale + ring)

Settings:
- Add logout button to settings page sidebar
2026-03-14 19:16:49 +01:00
Matthieu MALVACHE a3fe9deaa6 feat: add server-side version update check
Logs current version on startup and checks GitHub for newer releases.
Only runs in production, output stays in server logs (no client exposure).
2026-02-28 14:59:11 +01:00
Matthieu MALVACHE fb414bf2c9 feat: add contacts phase 2, advanced search, vacation responder, Docker & TOTP 2FA
- Contact groups/lists, vCard import/export (RFC 6350), bulk operations
- Advanced search with JMAP filter panel, search chips, cross-mailbox queries
- Vacation responder with JMAP VacationResponse, settings tab, sidebar indicator
- TOTP two-factor authentication support
- Docker multi-stage build with standalone output and docker-compose
- CSP Report-Only headers and security headers via proxy middleware
- Virtual scrolling for large email lists
- Structured server-side logger (text/JSON, configurable level)
- 450+ tests (contacts, vCard, threads, headers, identity, components)
- Playwright E2E framework setup
- Updated README and ROADMAP with all new features
2026-02-16 18:51:30 +01:00
Matthieu MALVACHE cf21a84263 Initial release: JMAP Webmail Client
A modern, privacy-focused webmail client built with Next.js and the JMAP protocol.
Designed for Stalwart Mail Server.

Features:
- Full email operations (compose, reply, forward, threading)
- Real-time push notifications
- Dark/light theme support
- Mobile responsive design
- Keyboard shortcuts
- Drag-and-drop organization
- i18n (English/French)
- Security-first (external content blocked, HTML sanitization)
2025-12-10 17:54:22 +01:00