NotesNext webmail for HCL Domino
My actual side project : redesign of the HCL Domino webmail.
From an original idea of Karsten Lehmann (Mindoo)
NotesNext Architecture
The core idea
I’m working with HCL Verse On-Premises, which is a webmail application where the frontend and backend are completely separate. The backend is a set of Java servlets inside OSGi JARs running in Domino, and the frontend is a webpack bundle served from those same JARs. What I do is replace only the frontend—the backend stays 100% untouched.
Three layers
Layer 1 — The browser
I build a React single-page application. When a user opens https://demo.data101.es/verse, they get my custom UI. It calls Domino APIs using the same session cookies that Domino already set during login. From the browser’s point of view, it’s just a normal HTTPS web app.
Layer 2 — OpenResty (the traffic cop)
I use OpenResty on port 443 to decide what happens with each request:
/verse(exact) → serves myindex.htmlfromdist//assets/*→ serves my JS/CSS fromdist/assets//mail/*→ proxies to Domino on 8443/pob/api/*→ proxies to Domino on 8443/verse/*→ proxies to Domino on 8443 (userinfo, photo, checksession, etc.)/*→ proxies to Domino on 8443 (iNotes, Traveler, everything else)
I’m basically doing a surgical intercept—only the paths that serve static UI files are intercepted. Everything else, including API calls and Domino services, passes through transparently.
Layer 3 — Domino (unchanged)
I move Domino from port 443 to 8443. It still handles authentication (LtpaToken cookies), data (mail NSF files), API endpoints, iNotes, Traveler, KEEP—everything. Domino has no idea I introduced a new frontend. It just responds to API calls like it always has.
Authentication flow
When the browser requests /verse, OpenResty serves my index.html.
Then my app runs bootstrap.ts and makes a request to:/verse/userinfo?sq=1&xhr=1
OpenResty proxies that to Domino on port 8443.
Domino checks the LtpaToken cookie:
- If it’s valid, it returns JSON with things like
dbPath,locale, andgkFeatures. - If it’s missing or invalid, it returns a 401.
In that case, the browser redirects to /verse/checksession (the Domino login page). After login, Domino sets the LtpaToken cookie and redirects back to /verse. Then bootstrap runs again and succeeds.
I never touch authentication in my UI—Domino handles all of it.
Data flow (mail inbox example)
When the user clicks the Mail icon:
- My React app (via React Query) calls
useInbox() - That triggers a fetch like:
/mail/demouser.nsf/pob/api/search/inbox?rows=30... - OpenResty proxies it to Domino on 8443
- Domino reads the NSF mail file and returns JSON
- React renders the mail items
When the user clicks a message:
- I fetch:
/mail/demouser.nsf/0/{UNID}/?OpenDocument&Form=l_inlineJSON... - Domino returns JSON with a
BodyHtmlfield - React renders it using
dangerouslySetInnerHTML - The browser then loads embedded images from paths like:
/mail/demouser.nsf/0/{unid}/Body/M2...
Why this architecture works
I don’t modify Domino internals at all—no JARs, NSF databases, AdminP, certstore, or LDAP. That’s critical in a production environment.
I also don’t introduce a new authentication system. LtpaToken cookies continue to work across all paths because OpenResty just passes them through. The user logs in once and everything works—mail, calendar, contacts, even old iNotes if needed.
I can deploy incrementally. This setup can run alongside the original Verse. Switching is just a matter of changing the OpenResty location = /verse block. If needed, I can roll back in about 30 seconds with systemctl reload openresty.
It’s also safe for internet exposure. Port 41735 (used in the old Vite dev setup) is never exposed, and Domino on 8443 isn’t public either—only OpenResty on 443 is. So it behaves like a standard reverse proxy setup.
Development vs production
In development, I can still run npm run dev on port 41735 with VOP_WebpackDevServerEnabled=1 for hot reload.
In production, OpenResty serves the compiled dist/ folder. The only real differences are a single notes.ini setting and whether Vite is running.

