The bug
A B2B web app logs users in with one-time links sent by email. Company mailboxes often sit behind an email security scanner that opens the links in an incoming message to check them before a person does. The scan is a real visit. It uses up the one-time token, so by the time the user clicks, the link is spent.
How it shows up
Every tester on Gmail logs in without trouble. Then the first companies sign up, and their users click the link and get “Email link is invalid or has expired”. They ask for a new link, and that one arrives used too. Only business customers behind a mail scanner are affected, which usually means the paying ones, and they blame the product.
How a test finds it
Do what the scanner does. Request the login link once with a plain HTTP client, the way a security scanner fetches it, then open the same link in a browser as the user. If the second visit fails, every customer behind such a scanner is locked out. The test takes minutes to write and needs no Microsoft account.
The fix
Send a one-time code the user types in, instead of a link. Or make the link open a page with a “Confirm sign-in” button, so the token is spent only when a person clicks it. A scanner loads the page and leaves the token alone.
The test that catches it
A test fetches the login link once with a plain HTTP client, as a scanner would, then opens it in a browser and asserts that the user ends up signed in. It runs in CI on every change to the login flow.
import { test, expect, request } from "@playwright/test";
import { requestLoginLink, signInFrom } from "./auth";
test("a mail scanner doesn't use up the login link", async ({ page }) => {
const link = await requestLoginLink("buyer@company.example");
// What an email security scanner does: fetch the link before anyone clicks.
const scanner = await request.newContext();
await scanner.get(link);
await signInFrom(page, link);
const expired = page.getByText("Email link is invalid or has expired");
await expect(expired).toHaveCount(0);
await expect(page.getByRole("button", { name: "Sign out" })).toBeVisible();
});
What it puts at risk
The company accounts the product was built to win, at the moment they first try it. A team that can’t log in on day one may not try again.
