No description
  • JavaScript 96.3%
  • Python 3%
  • Shell 0.3%
  • TypeScript 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-11-09 22:35:28 +01:00
.github/workflows ci: install a plugin's own dependencies before its tests when publishing 2026-10-20 13:54:22 +02:00
ai-assistant chore: release ai-assistant 2.2.1 2026-11-03 20:12:18 +01:00
auto-tag chore: release auto-tag 1.3.2 2026-10-31 20:51:27 +01:00
birthdays chore: release birthdays 1.1.2 2026-10-31 20:51:30 +01:00
calendar-agenda chore: leave the calendar agenda version to the release and rebuild its bundle 2026-11-09 22:33:57 +01:00
category-tabs chore: release category-tabs 1.1.1 2026-11-03 20:13:22 +01:00
docs/screenshots docs: retake the surface screenshots with the showcase drawn in the UI kit 2026-10-14 16:46:18 +02:00
e-invoice fix: keep the e-invoice and s/mime bundles in step with their manifests 2026-10-31 20:53:34 +01:00
e2e test: run the assistant's end-to-end tests against any account and server 2026-11-02 21:10:59 +01:00
email-stats chore: release email-stats 1.3.2 2026-10-31 20:51:33 +01:00
email-to-task chore: release email-to-task 1.2.1 2026-10-31 20:51:36 +01:00
external-link-warning chore: rebuild external-link-warning 2.0.0 with the review fixes 2026-11-01 09:38:21 +01:00
external-mail-warning chore: release external-mail-warning 1.6.2 2026-11-03 20:12:18 +01:00
gravatar chore: release gravatar 1.4.1 2026-10-31 20:51:41 +01:00
hello-world chore: keep the plugin lockfile versions in step with their packages 2026-10-18 17:07:42 +02:00
impersonation-notice chore: release impersonation-notice 1.5.0 2026-11-08 01:30:44 +01:00
jitsi-meet chore: release jitsi-meet 1.3.1 2026-10-31 20:51:45 +01:00
languagetool chore: release languagetool 1.0.0 2026-11-02 22:52:15 +01:00
libravatar chore: keep the plugin lockfile versions in step with their packages 2026-10-18 17:07:42 +02:00
nextcloud chore: release nextcloud 1.2.1 2026-10-31 20:51:49 +01:00
outlook-style-headers chore: release outlook-style-headers 1.2.3 2026-10-31 20:51:51 +01:00
paperless chore: release paperless 1.2.2 2026-11-03 20:12:18 +01:00
parcels chore: release parcels 1.2.2 2026-10-31 20:51:53 +01:00
plugin-template feat: draft plugin changelogs from commits and name them with the version on release 2026-10-30 02:01:46 +01:00
quick-notes chore: release quick-notes 1.3.3 2026-10-31 20:51:54 +01:00
reply-all-guardrail chore: release reply-all-guardrail 1.3.2 2026-10-31 20:51:57 +01:00
scripts Merge the AI assistant 2.2 2026-11-03 02:01:20 +01:00
send-checks fix: refuse java and android apps whatever they are named 2026-11-01 10:10:22 +01:00
slot-showcase fix: require webmail 1.15.0 for plugins that use the ui kit or the composer api 2026-10-18 17:07:11 +02:00
smime test: run the detached signature check on Windows and note the fix in the S/MIME changelog 2026-11-09 22:35:28 +01:00
spam-score chore: release spam-score 1.1.2 2026-10-31 20:52:01 +01:00
team-inbox chore: release team-inbox 1.0.1 2026-10-31 20:52:05 +01:00
test-env test: let the mock script lookups and actions for both AI shapes 2026-11-02 21:06:08 +01:00
test-utils test: add the kit's table to the ui kit stand-in 2026-10-29 23:48:21 +01:00
timezones chore: release timezones 1.1.2 2026-10-31 20:52:10 +01:00
translate feat: draft plugin changelogs from commits and name them with the version on release 2026-10-30 02:01:46 +01:00
.gitignore chore: ignore Python caches from the test scripts 2026-10-14 10:19:49 +02:00
LICENSE Switch to canonical AGPL-3.0 license text 2026-04-20 14:52:19 +02:00
package-lock.json chore: declare the react and jsdom test libraries the unit tests import 2026-10-18 17:26:38 +02:00
package.json feat: draft plugin changelogs from commits and name them with the version on release 2026-10-30 02:01:46 +01:00
publish.json chore: list the inbox category tabs plugin for publishing 2026-11-03 20:13:22 +01:00
README.md Merge pull request #36 from tobiasge/feat/calendar-agenda-side-panel 2026-11-09 22:32:42 +01:00
vitest.config.mjs feat: draft plugin changelogs from commits and name them with the version on release 2026-10-30 02:01:46 +01:00
WRITING_PLUGINS.md docs: name the webmail versions that can return from an impersonation 2026-11-08 01:30:04 +01:00

Bulwark plugins

Plugins for the Bulwark webmail, with the tools to build and test them:

  • one folder per plugin, each with its manifest, source, tests and a built ZIP;
  • scripts/build.mjs, which bundles and packages a plugin;
  • scripts/gen-docs.mjs, which refreshes the reference tables in the guide;
  • test-utils/ and e2e/, helpers for unit and end-to-end tests;
  • test-env/, Docker services to test against (Stalwart, Nextcloud, Paperless-ngx, Vikunja, mock APIs, Ollama);
  • WRITING_PLUGINS.md, the guide for plugin authors, with screenshots of every place a plugin can show something in docs/screenshots/.

Plugins

Plugin What it does Permissions
ai-assistant Writes, rewrites, translates and proofreads mail and summarises messages with an OpenAI-compatible service or Anthropic; text is only sent when you click email:read http:proxy admin:config ui:email-banner ui:composer-toolbar ui:settings-section
auto-tag Tags incoming mail by rules (newsletters, invoices, GitHub notifications, your own) email:read email:write
birthdays Upcoming birthdays from your contacts in the sidebar, with reminders, a quick greeting and optional calendar events contacts:read ui:sidebar-widget ui:notifications compose:open calendar:read calendar:write
calendar-agenda An agenda of your next events in the sidebar calendar:read contacts:read ui:sidebar-widget ui:email-details ui:app-top-banner
category-tabs Gmail-style inbox tabs (Primary, Social, Promotions, Updates) with your own tabs, rules and per-sender overrides email:read email:write ui:message-list-tabs ui:settings-section filters:read filters:write
email-stats Counts the mail you open and send during a session, in the sidebar email:read ui:sidebar-widget
email-to-task Turns a message into a task in Bulwark, Vikunja, Todoist or a CalDAV list, with a link back and a due date from the text email:read email:write email:render-takeover tasks:read tasks:write http:proxy ui:message-list ui:toolbar ui:observe ui:keyboard admin:config
external-link-warning Asks before opening links to domains that aren't on a trusted list admin:config
external-mail-warning Retired: Bulwark 1.19 does this itself (Organisation > Security). On older versions, asks before sending outside your safe domains and marks mail from outside with a banner email:read email:send identity:read ui:email-banner admin:config
gravatar Sender pictures from Gravatar email:read http:fetch
hello-world The smallest example: logs app and mail events to the console email:read
impersonation-notice A banner while an admin is signed in as another user (Stalwart master user), with a way back account:read auth:emit ui:app-top-banner admin:config
jitsi-meet An "Add Jitsi Meeting" button in the event form calendar:read calendar:write ui:calendar-action http:post
languagetool Underlines spelling, grammar and style mistakes while you write, checked by your own LanguageTool server (or the public API, if the admin allows it); never for signed or encrypted mail compose:check http:proxy ui:settings-section
libravatar Sender pictures from Libravatar email:read http:fetch
nextcloud Attach files from Nextcloud, send large files as share links, save attachments and messages there, add Talk meetings to events http:proxy ui:settings-section ui:composer-toolbar email:read email:send email:blob-write email:blob-read ui:toolbar ui:app-page compose:open ui:download-file calendar:read ui:calendar-action
outlook-style-headers Outlook-style From / Sent / To / Subject block above quoted replies and forwards email:send
paperless Send attachments and messages to Paperless-ngx, find documents there and attach them to new mail http:proxy admin:config email:read email:blob-read email:blob-write compose:open ui:message-list ui:toolbar ui:app-page ui:settings-section ui:notifications ui:download-file
plugin-template A starting point for a new plugin email:read ui:sidebar-widget
quick-notes Notes per message, shown again when you open it email:read ui:email-banner ui:email-details
reply-all-guardrail Asks before a reply-all (or any send) to more recipients than a limit email:read email:send admin:config
slot-showcase For plugin authors: a labelled box in every UI slot and a button on every action surface every ui:* permission, plus account:read email:read email:send compose:open calendar:read contacts:read tasks:read files:read
smime S/MIME signing and encryption, verifying and decrypting, with keys kept in the browser (privileged tier) crypto:full email:blob-read email:raw-send email:render-takeover email:read email:send smime:read ui:composer-toolbar ui:email-banner ui:settings-section app:lifecycle
spam-score A spam analysis section in the message details, from the spam filter's headers email:read ui:email-details
team-inbox Assign messages in team mailboxes to a member and mark them done, with "Assigned to me" and "Open" filters email:read email:write account:read directory:read ui:message-list ui:toolbar
translate Translates received mail with MyMemory or LibreTranslate email:read ui:email-banner http:post

What each permission allows is listed in WRITING_PLUGINS.md.

Build

Install the development tools once with npm ci (esbuild, JSZip, Vitest, React Testing Library, jsdom, Playwright). Plugins are written as ES modules in <plugin>/src/ and bundled into one CommonJS file with React and the host API left external. From the repository root:

node scripts/build.mjs                  # every plugin
node scripts/build.mjs nextcloud smime  # some plugins
node scripts/build.mjs --check          # fail if a committed ZIP is out of date

Each build writes <plugin>/<plugin>.zip with manifest.json and index.js at its root. That ZIP is what the admin upload and the user upload take.

The extension directory doesn't take the ZIP. It builds each plugin from its folder alone with npm ci --ignore-scripts and npm run build, so a plugin's own package.json needs a build script that works without the rest of the repository and writes dist/index.js. CI checks both:

npm run check                           # manifests, versions, lockfiles and publish.json
npm run build:isolated                  # each plugin on its own, as the directory builds it

Test

npm test                                # unit tests (Vitest): <plugin>/test/*.test.mjs
npm run test:e2e                        # Playwright: <plugin>/e2e/*.spec.mjs and e2e/*.spec.mjs
./test-env/up.sh                        # or .\test-env\up.ps1: the Docker test services

Unit tests import a plugin's logic directly and use test-utils/fake-api.mjs for the api object. End-to-end tests need a running webmail (default http://localhost:3100, set BULWARK_URL) signed in to the Stalwart from test-env, and the admin password in BULWARK_ADMIN_PASSWORD; they install the plugin's ZIP through the admin API. See test-env/README.md for the services, users and the PLUGIN_PROXY_PRIVATE_ORIGINS value the webmail needs to reach them.

Install

  • Admin: turn on plugins in the webmail's admin dashboard and upload the ZIP under Plugins. Choose whether it is on for everyone or each user turns it on in Settings > Plugins.
  • User: upload the ZIP in Settings > Plugins, if the admin allows it. The webmail asks to confirm the plugin's permissions, and may wait for the admin to approve it.
  • Development: start the webmail with PLUGIN_DEV_DIR set to this repository. It loads every plugin folder from source and rebuilds it on each page load.

Releasing

Plugins reach the extension directory through a tag <plugin>@<version>. The directory builds the plugin from the tagged commit, and a reviewer approves each version before it is published.

  1. Merge the changes into main.

  2. Draft the notes and edit them in <plugin>/CHANGELOG.md:

    npm run changelog -- smime                    # or several: smime gravatar
    

    This adds the plugin's commits since its last release to the Unreleased section, sorted under Breaking, Security, Added, Changed and Fixed, and leaves out chore, test, ci, docs, style and build commits. With the GitHub CLI signed in, entries get their pull request number, and those from outside contributors thank the author. The admins who install the plugin read these notes before they update, so say what changes for them. There's no need to commit the edits.

  3. On main, raise the version, commit and tag:

    npm run release -- smime 1.4.0                # or smime minor, smime patch
    npm run release -- smime minor gravatar patch # several at once
    

    This sets the version in manifest.json, package.json and package-lock.json, rebuilds the ZIP, runs the checks, renames the Unreleased section to ## 1.4.0 (<date>), commits chore: release smime 1.4.0 and tags it smime@1.4.0. It pushes nothing. With an empty Unreleased section it still releases, with a warning, and the version has no notes in the directory.

  4. Push main, then each tag it printed on its own. GitHub starts no workflow when a push brings more than three tags.

    git push origin main
    git push origin smime@1.4.0
    

The Publish workflow then checks that the plugin is in publish.json, that the manifest has the tag's version and that the commit is on main, runs the plugin's tests and build, and submits it. The job fails when the directory's build or checks fail. When a submission fails, fix it and release the next patch version rather than moving the tag. The directory takes the version's section of CHANGELOG.md as its changelog; a tag without notes gets a warning on the run but is still submitted.

publish.json lists the folders that are published and the ones that are not, with the reason. A new plugin goes into one of the two, or CI fails. Before its first release, a directory admin adds a trusted publisher for it: repository bulwarkmail/plugins, workflow .github/workflows/publish.yml, environment extensions, the folder name as subpath, tags only.

A plugin that uses the UI kit (@plugin-host/ui), the composer API or the other plugin features of webmail 1.15.0 needs "minAppVersion": "1.15.0" or later; the check fails otherwise.

Writing a plugin

Start from plugin-template/ or hello-world/ and read WRITING_PLUGINS.md. After the webmail's hook or slot catalogue changed, refresh the tables in the guide:

node scripts/gen-docs.mjs               # set BULWARK_WEBMAIL if the webmail isn't in ../jmap-webmail-plugins

License

All plugins in this repository are released under the GNU Affero General Public License v3.