Bulwark Mobile · sign-in redesign
The current sign-in screen opens with a Server URL field. Most people don't know their JMAP endpoint, and the two paths that don't require knowing it — the webmail handoff and the QR code — sit below a divider, greyed out until the URL is filled in anyway. This proposes an entry screen that leads with the easy paths and asks for a server only when we genuinely can't work it out.
Everything at once, in the least helpful order. The webmail handoff is the safer route — it never puts a password into the app — but it's disabled until you've supplied the server, so it can't rescue anyone who's stuck.
Secure. Private. Yours.
The two disabled controls at the bottom are the ones that would have helped. Nothing tells you where to find a server URL, and nothing happens if you don't have one.
One question per screen, cheapest question first. The screens below are the happy path, left to right.
Secure. Private. Yours.
Two live options instead of six mixed ones. Each tile says what it costs you before you tap it. Manual setup stays reachable in one tap — it just isn't the greeting.
That's usually all we need — we'll look up the mail server for you.
Works with Bulwark, Stalwart, and any JMAP mail server.
Autofocus with the email keyboard, autocorrect off. The button stays enabled and validates on tap, so nobody is left staring at a dead grey button wondering which field is wrong.
Next you'll sign in on your provider's own page.
A browser jump with no warning feels like a bug. Naming the server first — and saying why the password isn't asked for here — turns the detour into a security feature.
This is the existing runWebmailHandoff flow, unchanged. It already supports whatever
the server offers — password, OAuth, passkeys, SSO — which the in-app form never will.
ada@example.com
The first sync is the slowest moment in the app. Naming what's arriving makes the wait legible, and the inbox opens as soon as mailboxes land — the rest fills in behind it.
Self-hosted servers, typo'd passwords, and second accounts are the normal case for this audience, not the exception. These four screens carry most of the redesign's weight.
Open Bulwark on your computer
Settings → Devices → Add phone shows a code. Point the camera at it.
Type the code instead
A scanner with no instructions is a dead end for anyone who hasn't seen the webmail's Devices page.
Redeeming a paired code skips the browser entirely — see redeemPairingCode.
We couldn't find a server for example.org. If you self-host, the address is the one you use for webmail.
Same address you type in the browser. We'll add https://.
Not the JMAP session URL — the webmail address they type every day. The scheme is added for them, and
a trailing slash or full /jmap path is accepted and trimmed.
This server doesn't support the browser sign-in, so we'll ask here.
The store's raw error string is replaced with mapped copy per failure: wrong credentials, unreachable host, bad certificate, 2FA-needs-an-app-password. Each says what to do next.
You're signed in to these already.
isAddMode currently shows the same blank three-field form. Most second accounts live on
the same server as the first, so offer that first and skip discovery entirely.
From ada@example.com: try https://example.com/.well-known/jmap, then
https://mail.example.com/.well-known/jmap, then the same host the last account used.
First 200 wins. Budget the whole thing at two seconds and fall through to Path B on timeout — a
slow lookup must never be worse than the manual form.
Every screen drives an existing store action — loginViaWebmail,
loginViaPairing, login. The redesign is routing and copy; the auth
surface, the state machine, and parseQrLoginPayload stay as they are.
A multi-step flow earns a hardware-back handler on Android. Every step goes back one, and step 1 leaves the flow — never a trapped screen with a filled-in field you can't clear.
autoComplete="email" and textContentType="username" on the address field,
"password" on the fallback field, so the platform password manager offers to fill and
save. The current screen sets neither.
Mockup · repos/react-native/mockups/login.html · static HTML, no interaction wired up