In June 2026 we retired SnappyMail and shipped DarDev Roundcube at the same public URL—https://webmail.dardev.net. Mail never moved: every mailbox stayed on Stalwart with the same credentials. What changed was the browser UI and where user preferences live. Staff re-created signatures under Settings → Identities; inbound and outbound delivery, SPF, DKIM, and DMARC were untouched.
The trigger was reliability, not aesthetics. SnappyMail user settings—signatures, identities, UI preferences—did not persist reliably across logout and container restarts, a known upstream issue. Roundcube stores metadata in PostgreSQL and is the de-facto pairing with Stalwart in self-hosted stacks. For how webmail fits the wider compose topology, see inside-unified-mailer-stack; for how we talk about shipped versus planned work, see deployed-realities-explained.
What we deliberately did not migrate
- MX, SPF, DKIM, or DMARC records—Stalwart and GoDaddy DNS unchanged
- Zoho @dardev.net staff mail—still on Zoho webmail, not Roundcube
- SnappyMail volume data—we deleted snappymail-data without backup; it held disposable UI config only
- Listmonk or Stalwart admin URLs—mail.dardev.net and mta.dardev.net unchanged
- The public hostname—webmail.dardev.net kept the same A record and nginx TLS cert
That scope discipline kept cutover risk low. The worst realistic failure was a few minutes of webmail downtime while we swapped the container on host port 8888. Actual mail delivery and campaign sends on send.dardev.net continued through Stalwart regardless of whether staff could open a browser inbox.
Target architecture
Browser traffic hits host nginx on 443, which proxies to Roundcube bound at 127.0.0.1:8888 inside the dardev-mailer compose stack. Roundcube reads and writes user session and preference data to a dedicated PostgreSQL database named roundcube on the existing postgres service. IMAP and SMTP connections go to stalwart:993 and stalwart:465 over the internal Docker network with TLS verify relaxed for the internal hostname—same pattern SnappyMail used via domain JSON.
We pin the image tag dardev/roundcube:1.6.11 in production rather than :latest. Configuration lives in git-tracked roundcube/config/custom.inc.php plus environment variables for database credentials and ROUNDCUBEMAIL_DES_KEY—a 24-character secret required for session encryption that must stay stable across restarts or everyone gets logged out.
Milestone timeline
- W0 — Roadmap, custom.inc.php skeleton, postgres init for roundcube DB, .env.example vars
- W1 — Compose service, init-roundcube-db.sh for existing VPS Postgres, validate-webmail.sh smoke
- W2 — VPS cutover: stop SnappyMail, remove volume, start Roundcube, reload nginx upstream name
- W3 — Docs rewrite, ops health collectors, backup runbook adds pg_dump roundcube
- W4 — Delete snappymail/ directory and legacy fix scripts from the repo
- W5 — Seven-day staff soak: signature persist, compose and send, campaign reply read in webmail
We chose direct replace on port 8888 instead of running two webmail UIs in parallel. Parallel testing on 8889 was available for local dev only; production cutover accepted roughly two minutes of internal downtime. Rollback before volume delete meant reverting compose and redeploying SnappyMail; after volume delete, only mail on Stalwart remained authoritative.
Repo and ops cleanup
SnappyMail left fingerprints across docker-compose.yml, docker-compose.dardev-vps.yml, docker-compose.swarm.yml, nginx upstream names, deploy-webmail.sh, and a snappymail/domains/send.dardev.net.json file. We renamed upstream dardev_snappymail to dardev_roundcube while keeping the same host port. Scripts fix-snappymail-stalwart-domain.sh was deleted; probe-stalwart-from-snappymail.sh became probe-stalwart-from-webmail.sh. Ops metadata—company.json, mail-stack manifest, DNS profiles—updated service id labels from snappymail to roundcube without changing the public URL staff bookmark.
Staff communication
Internal message was short: same URL, same Stalwart address and password, mail unchanged, re-create signature in Settings → Identities. @dardev.net accounts stay in Zoho. Sales mailboxes on sales.dardev.net gained webmail access when that Stalwart domain went live—login filter in custom.inc.php lists both send and sales domains.
Post-cutover validation included login for contact@send.dardev.net, signature save and reload, compose to an external address, and reading a campaign reply. Backup runbooks now include PostgreSQL roundcube alongside twenty and listmonk dumps. Stalwart volumes stalwart-data and stalwart-etc remain the mail storage source of truth.
Risks we planned for
- Roundcube DB missing on existing VPS — idempotent init-roundcube-db.sh
- DES key rotation — document once-per-environment generation; rotation logs everyone out
- Stalwart TLS hostname mismatch on Docker network — ssl verify_peer off in custom.inc.php
- ManageSieve plugin errors — enable only after Stalwart :4190 STARTTLS listener confirmed
- Swarm worker volume drift — same PostgreSQL roundcube DB on manager postgres service
Total engineering effort was about one focused day plus a calendar soak week. The migration is a case study in swapping a UI layer without touching deliverability infrastructure—aligned with multi-product-saas-one-core thinking where shared platform services stay stable while product-facing surfaces evolve.
What staff should expect day one
Roundcube UI differs from SnappyMail—folder layout, settings menus, and identity editor paths changed. Training was a five-minute Loom, not a workshop. Campaign operators who only use Listmonk admin were unaffected. Anyone reading replies in webmail tested one inbound message after cutover. Mobile access remains browser-based; we did not ship a native app.
Did we lose mail during the SnappyMail to Roundcube cutover?
No. Mail lives on Stalwart volumes. Only the browser UI and disposable SnappyMail preference volume changed.
Can @dardev.net staff use webmail.dardev.net?
No. @dardev.net inbound stays on Zoho. Webmail is for Stalwart mailboxes on send.dardev.net and sales.dardev.net only.
Why not backup SnappyMail data?
The volume held UI preferences that failed to persist reliably anyway. Signatures were re-created once in Roundcube where PostgreSQL persistence works.
How long was webmail down?
Roughly two minutes during W2 cutover while we swapped the container on 127.0.0.1:8888 and reloaded nginx.
Where is the technical runbook now?
docs/WEBMAIL.md and docs/roadmaps/WEBMAIL-ROUNDCUBE-MIGRATION.md. Deploy with bash scripts/deploy-webmail.sh.



