Passcode — product requirements
Passcode is a passwordless sign-in service. A person enters an email address, receives a 6-digit one-time code in a built-in demo mailbox, types it into six one-digit boxes and lands on an account page. It also offers sign-up, resend, expiry, attempt limits and a small HTTP API.
Problem. Passwords are forgotten and reused. People want to sign in with only an email address, and the service must make sure that only the person who controls the mailbox, holding the newest unexpired code, can get in.
Users
- Returning member — Sign in with just an email address and a short code, quickly and without a password.
- New visitor — Create an account with a name and email and be signed in with the first code.
- Account holder under attack — Be protected from guessed, replayed or stale codes.
- Integrator — Do the same flow over HTTP with a bearer token as the result.
Goals
- Only the newest, unexpired code for an address can sign a person in, and only within three tries.
- Every failure explains itself in one clear sentence and tells the person what to do next.
- The code shown in the mailbox is exactly the code the service accepts.
- The flow works with keyboard, paste and reload without losing the person in the middle.
Out of scope
- Real email delivery
- SMS codes
- Password sign-in
Contents
Sign-up
Self-service account creation with a name and an inbox address.
- "Create account" moves to a "Check your inbox" screen saying a 6-digit code was sent to a masked address such as a•••@inbox.test
- After the sixth digit is typed the code verifies automatically and the page shows "Welcome back, <name>" with Plan "Free"
- An empty form shows "Enter your name" and "Enter your email"
- An address outside the demo domain shows "Use an @inbox.test address so you can read the code"
- An address that already has an account shows "An account with this email already exists — sign in instead"
Requesting a code
The sign-in screen asks for an email address and sends a code to existing accounts only.
- The screen says "We sent a 6-digit code to d•••@inbox.test" for dev.patel@inbox.test
- After the code the page shows "Welcome back, Dev Patel", Plan "Free", Member since 2026-01-09 and a first recent sign-in marked "— this session"
- If a code was requested for the same address in the last 30 seconds the message is "Please wait N s" instead
- The message is "No account found for this email · Create an account" and the person stays on the sign-in screen
- An empty field shows "Enter your email"
- "mira.okafor@inbox" shows "Enter a valid email address"
- The message is "This account is suspended. Contact support."
- The mailbox for that address holds only "Your account has been suspended" and no sign-in code
Demo mailbox
A built-in mailbox page, opened in a second tab, stands in for real email.
- The list shows a masked subject "•••••• is your Passcode sign-in code"; the full code is visible only inside the message
- The message body shows a 6-digit code and its expiry time
- Entering the code exactly as displayed signs the person in; it is never rejected as incorrect
- mira.okafor@inbox.test shows at least "New sign-in from Firefox on Linux" and "Welcome to Passcode", newer above older
- The welcome message body starts "Hi Mira Okafor,"
Verifying a code
Entering the code, with a limited number of tries and a limited lifetime.
- First wrong code: "Incorrect code. 2 attempts left."
- Second wrong code: "Incorrect code. 1 attempt left."
- The third wrong code shows "Too many attempts. Request a new code." and the boxes are disabled
- After a resend the message is "A new code is on its way. Earlier codes no longer work.", the boxes are enabled and the newest code signs the person in
- No number of further tries on the locked code can succeed
- The default lifetime is 2 minutes; the verify screen shows a live "Code expires in m:ss" and then "Code expired"
- A shorter lifetime can be set with an optional ttl URL parameter (minimum 5 seconds); with 10 seconds the mail says "It expires in 10 seconds"
- An expired code shows "This code has expired. Request a new code."
Resend
Asking for another code, rate-limited and replacing earlier codes.
- The button reads "Resend code in 30 s", is disabled and counts down every second
- When the countdown ends it reads "Resend code" and is enabled
- After a resend the mailbox holds two code mails, newest on top
- Entering the older code shows "This code is no longer valid. Request a new code."
- Entering the newest code signs the person in
Code boxes
Six one-digit boxes that behave like a single input.
- All six boxes fill and the account page opens without further clicks
- Typing "12" fills box 1 and box 2 and focuses box 3
- Typing a letter enters nothing
- Backspace clears the previous box and puts focus there
Session and routing
A signed-in session persists across reloads and ends on sign-out.
- Reloading the account page still shows "Welcome back, <name>"
- "Sign out" returns to the sign-in screen
- Opening the account page directly afterwards redirects to the sign-in screen
- The address becomes the sign-in page and the "Sign in" heading is shown
HTTP API
The same flow over HTTP, ending in a bearer token.
- Sign-up answers 201; a first code request answers 200 with sentTo, ttl 120 and resendIn 30
- A second request straight away answers 429 "Please wait N s…" with retryAfter
- Verify with the mailbox code answers 200 with a token and user
- The account endpoint answers 200 with a bearer token and 401 "Sign in first" without it
Non-functional requirements
Glossary and rules
| Code | A 6-digit one-time number emailed to the address; valid for 2 minutes by default. |
|---|---|
| Superseded code | A code replaced by a newer one after a resend. |