Webmail for the Stalwart server in your home lab.
Run Bulwark as one container next to Stalwart on a NAS, a VM or a 64-bit ARM board, or put Bulwark Lite on a web server you already run and skip the Node process. It works for one person, a family or a club.


Bulwark is the client for the Stalwart server you already run.
Stalwart handles SMTP, storage, spam filtering and the accounts. Bulwark talks JMAP to it from the browser and puts mail, calendar, contacts and files in one window.
Stalwart and Bulwark in one Compose file.
The images are built for amd64 and arm64; a Raspberry Pi needs a 64-bit OS. On the project's amd64 test VM the container used about 70 MiB of memory at idle, signed out. Put Caddy, nginx or Traefik in front for HTTPS, and drop the ports the two containers publish once it's there; Run Bulwark at home shows that layout.
Compose with Stalwartservices:
stalwart:
image: stalwartlabs/stalwart:v1.0
ports: ["443:443", "25:25", "465:465", "993:993", "8080:8080"]
volumes:
- stalwart-etc:/etc/stalwart
- stalwart-data:/var/lib/stalwart
restart: unless-stopped
bulwark:
image: ghcr.io/bulwarkmail/webmail:latest
ports: ["3000:3000"]
environment:
JMAP_SERVER_URL: https://mail.example.com
volumes:
- bulwark-data:/app/data
depends_on: [stalwart]
restart: unless-stopped
volumes:
stalwart-etc:
stalwart-data:
bulwark-data:Bulwark Lite is a folder of static files.
Serve it from any web server, run the 23 MB nginx image, or let Stalwart serve it as an application. Lite leaves out the admin console, plugins and web push. Its nginx image used about 8 MiB at idle on the same VM.
Static hosting

Every account in one window.
Sign in to your own mailbox and the family's at once and switch between them. An admin can allow one merged inbox across all of them.
Multiple accounts

Push to the phone without opening a port for it.
Stalwart sends push to the Bulwark relay over HTTPS, and the relay wakes the phone, so push needs no inbound connection. Reading mail away from home still needs Stalwart and Bulwark reachable, over HTTPS or a VPN. On Android without Google services, the app can push through UnifiedPush with a distributor such as ntfy, through the hosted relay or one you run. A relay of your own is pre-1.0, built from source, and needs a public HTTPS address. The phone app is a beta.
Mobile apps and push

Plugins and themes, and your own.
The admin console installs plugins and themes from the extension directory, such as Paperless-ngx, Nextcloud or a Thunderbird-like theme. A plugin of your own is a manifest and some JavaScript, a theme a manifest and CSS, and the guides cover both.
Plugins

What Bulwark contacts, and the switch that stops it.
Usage stats are off until you turn them on. Out of the box, Bulwark does check for new releases once an hour, loads sender icons, and uses the project's push relay and extension directory. Set NO_THIRD_PARTY_TRAFFIC=true, or turn on the same switch in the admin console, and all of that stops.
Third-party trafficPackages other people maintain.
These are not run by the Bulwark project, and they may install an older release. Check the version before you use one.
More from the Bulwark project.
What each part needs.
| Feature | Needs |
|---|---|
| Lite on a separate host | The Permissive CORS policy in Stalwart, unless Stalwart serves Lite |
| More than five accounts in one browser | HTTPS (HTTP/2) between the browser and Bulwark |
| Installing as an app | HTTPS |
| Web push in the browser | HTTPS and the full edition |
| Push to the phone | The beta app, and Stalwart reaching the relay over public HTTPS |
| UnifiedPush | Android and a distributor app such as ntfy |
| Plugins | The full edition; an admin turns them on |