Closes#75 as far as it can be closed without implementing OAuth, and adds the real fix to the roadmap.
It's ours, not Stalwart's
The reporter guessed Stalwart; it isn't. Stalwart accepts a TOTP code only through an OAuth flow — its own web interface is an OAuth client, which is exactly why signing in there works — and it offers only the authorization-code and device flows. There is no password grant, so a client holding a username and password has nowhere to exchange them plus a code for a token.
The concatenated password$code form ihasmail sends is not a route the server has, and appears never to have been. What the README recorded as "confirmed live" was enabling and disabling 2FA, never signing in with a code — so this path was never tested.
The codebase already knew. Turning 2FA on in ihasmail mints an app password and reseals the session onto it, precisely because a plain password stops working from that moment. The sign-in page was the one place still assuming otherwise.
The reporter's own evidence fits exactly: app password works ✅ (Stalwart's documented route for non-OAuth clients, and it bypasses TOTP), Stalwart's own login works ✅ (OAuth), ihasmail 401s ❌.
What changed — no new capability, just honesty
A 401 on a sign-in carrying a code now explains itself, points at app passwords, and says the password is probably fine. A sign-in without a code is untouched, so an ordinary typo still reads as an ordinary typo — there's a test for each.
The field stays and is honest. Removing it would leave someone with 2FA finding nothing at all, which is worse than a field that explains the situation and sends them somewhere that works. It also stays forward-compatible with servers that do accept it.
The README's false claim is corrected, not quietly dropped — it said Stalwart "accepts password$code", and it doesn't.
Backlog
Real 2FA is now in the roadmap as what it actually is: an OAuth implementation. Stalwart offers authorization-code and device flows, so ihasmail would hand sign-in to Stalwart's login and come back with a token. That's arguably a better posture than today's sealed password — a refresh token rather than a credential — but it replaces ihasmail's own sign-in page for those users and may need an OAuth client registered.
265 web + 77 server tests, typecheck and build clean.
Closes #75 as far as it can be closed without implementing OAuth, and adds the real fix to the roadmap.
## It's ours, not Stalwart's
The reporter guessed Stalwart; it isn't. Stalwart accepts a TOTP code **only through an OAuth flow** — its own web interface is an OAuth client, which is exactly why signing in *there* works — and it offers only the authorization-code and device flows. **There is no password grant**, so a client holding a username and password has nowhere to exchange them plus a code for a token.
The concatenated `password$code` form ihasmail sends is not a route the server has, and appears never to have been. What the README recorded as "confirmed live" was **enabling and disabling** 2FA, never *signing in* with a code — so this path was never tested.
**The codebase already knew.** Turning 2FA *on* in ihasmail mints an app password and reseals the session onto it, precisely because a plain password stops working from that moment. The sign-in page was the one place still assuming otherwise.
The reporter's own evidence fits exactly: app password works ✅ (Stalwart's documented route for non-OAuth clients, and it bypasses TOTP), Stalwart's own login works ✅ (OAuth), ihasmail 401s ❌.
## What changed — no new capability, just honesty
**A 401 on a sign-in carrying a code now explains itself**, points at app passwords, and says the password is probably fine. A sign-in *without* a code is untouched, so an ordinary typo still reads as an ordinary typo — there's a test for each.
**The field stays and is honest.** Removing it would leave someone with 2FA finding nothing at all, which is worse than a field that explains the situation and sends them somewhere that works. It also stays forward-compatible with servers that do accept it.
**The README's false claim is corrected**, not quietly dropped — it said Stalwart "accepts `password$code`", and it doesn't.
## Backlog
Real 2FA is now in the roadmap as what it actually is: an OAuth implementation. Stalwart offers authorization-code and device flows, so ihasmail would hand sign-in to Stalwart's login and come back with a token. That's arguably a **better** posture than today's sealed password — a refresh token rather than a credential — but it replaces ihasmail's own sign-in page for those users and may need an OAuth client registered.
265 web + 77 server tests, typecheck and build clean.
**Merged** 2026-08-26 as coffey-labs/ihasmail@450a38f7cd86
<sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #75 as far as it can be closed without implementing OAuth, and adds the real fix to the roadmap.
It's ours, not Stalwart's
The reporter guessed Stalwart; it isn't. Stalwart accepts a TOTP code only through an OAuth flow — its own web interface is an OAuth client, which is exactly why signing in there works — and it offers only the authorization-code and device flows. There is no password grant, so a client holding a username and password has nowhere to exchange them plus a code for a token.
The concatenated
password$codeform ihasmail sends is not a route the server has, and appears never to have been. What the README recorded as "confirmed live" was enabling and disabling 2FA, never signing in with a code — so this path was never tested.The codebase already knew. Turning 2FA on in ihasmail mints an app password and reseals the session onto it, precisely because a plain password stops working from that moment. The sign-in page was the one place still assuming otherwise.
The reporter's own evidence fits exactly: app password works ✅ (Stalwart's documented route for non-OAuth clients, and it bypasses TOTP), Stalwart's own login works ✅ (OAuth), ihasmail 401s ❌.
What changed — no new capability, just honesty
A 401 on a sign-in carrying a code now explains itself, points at app passwords, and says the password is probably fine. A sign-in without a code is untouched, so an ordinary typo still reads as an ordinary typo — there's a test for each.
The field stays and is honest. Removing it would leave someone with 2FA finding nothing at all, which is worse than a field that explains the situation and sends them somewhere that works. It also stays forward-compatible with servers that do accept it.
The README's false claim is corrected, not quietly dropped — it said Stalwart "accepts
password$code", and it doesn't.Backlog
Real 2FA is now in the roadmap as what it actually is: an OAuth implementation. Stalwart offers authorization-code and device flows, so ihasmail would hand sign-in to Stalwart's login and come back with a token. That's arguably a better posture than today's sealed password — a refresh token rather than a credential — but it replaces ihasmail's own sign-in page for those users and may need an OAuth client registered.
265 web + 77 server tests, typecheck and build clean.
Merged 2026-08-26 as coffey-labs/ihasmail@450a38f7cd
Rebuilt from: git history, session transcript.