Sign in before you verify — and a verify link that always works

The first five minutes with a new tool decide whether you ever reach the tenth. For Batchrouter, those minutes start with email verification — and until this week that step had more friction than it should. You couldn't sign in until you'd verified, and the verification link itself sometimes greeted you with "invalid or expired" on your very first click. We fixed both, and tightened one thing on purpose. These changes shipped to production on June 9, 2026 and are live now. Here's what's different. Sign in before you verify Signing up no longer locks you out of your own workspace. Previously, logging in before confirming your email returned a hard 403 email_unverified — you had to go find the verification message before you could see anything. Now POST /v1/auth/login issues a normal session for unverified accounts, so you land in the app right away and can look around while you get to verification in your own time. A quiet in-app banner reminds you that your address isn't confirmed yet, with a one-click Resend verification email action so you never have to dig through your inbox for the original. One door stays locked on purpose: admin sign-in still requires a verified email. The operator entry point keeps its stricter check as defense in depth. A verify link that always works The more interesting fix is the one you'll never see again. Some people clicked a fresh verification link and got "invalid or expired" on the first click — a confusing dead end for a link that had only just been generated. The cause was a subtle collision between one-time tokens and modern email security. Verification tokens were single-use: the first request to load the confirmation page consumed the token. But mail providers like Gmail and Microsoft Safe Links prefetch links — they quietly open and render a page to scan it for malware before you ever click. That automated render spent the one-time token first, so by the time you clicked, it was already used and the page reported it as expired. We fixed it from both sides. The verification endpoint (POST /v1/auth/verify-email/confirm) is now idempotent — a token stays valid until it genuinely expires, so a scanner opening it ahead of you is harmless and your click always succeeds. And the verify page no longer auto-submits on load: it waits for an explicit Verify my email click, so a bare prefetch never touches the token in the first place. The idempotent endpoint is the authoritative fix; the explicit-click page is defense in depth. Either alone would solve it — together, a fresh link works on the first click, every time. Buying credits stays behind a verified email One thing got stricter, deliberately. Buying credits now requires a verified email. The check is enforced server-side at POST /v1/billing/checkout-sessions — the authoritative gate — and mirrored in the app, where the buy-credits action is clearly disabled, with an explanation and a resend link, until you've confirmed your address. It's a one-time step, and it keeps billing tied to an address we know reaches you. Everything else — exploring your workspace, wiring up the API, reading your usage — is open the moment you sign up. (API keys are issued credentials in their own right, so they're treated as verified and programmatic access is unaffected.) When both reminders could apply, verification comes first: the app shows a single nudge at a time, and an unverified email always takes precedence over the low-balance "top up credits" prompt — so the next step is never ambiguous. If your email is already confirmed, you won't see any of this — sign-in, your workspace, and billing all work exactly as before. These changes only smooth the path for brand-new accounts. It's a small surface — sign-in, one banner, one link — but it's the part of the product everyone touches first, so it should just work. A plain-language summary is on our changelog.