Skip to content

FM Consulting

Customer Portal Improvement Program

A self-service account portal for team, plan, and billing management — paired with an internal account-health view that scores churn risk from real usage data, not guesswork.

Gallery

Case study

The problem

Two related problems, one portal. Customers routed basic account changes — adding a teammate, upgrading a plan, understanding what a plan change would actually cost mid-cycle — through support tickets, because the portal could display account state but not change it. On the other side, the first real signal of churn risk was almost always the cancellation itself: an account with a shrinking, disengaged team could be paying for far more seats than it used for months, with nothing in any internal view surfacing that until the account left and someone asked why.

The solution

The portal became genuinely self-service: a team page with seat-limit-aware invites and role changes, and a billing page where changing plans shows the actual prorated credit and charge for the rest of the current cycle before the customer confirms anything — not a vague "new price starting next month." Canceling requires picking a reason from a fixed list rather than a free-text box nobody reads later. On the internal side, every active account gets a single health score built from two real signals — what share of the team logged in recently, and how much of the plan's seat capacity is actually provisioned — so a support or success lead can see which accounts are quietly drifting before renewal, not after.

How it was built

Proration is a real, deterministic calculation: the unused days left in the current billing cycle are credited at the old plan's per-seat rate, the same days are charged at the new plan's rate, both scaled by actual seat count — checked by hand against known-good numbers (an upgrade nets positive, a downgrade nets an exact negative, and changing to the same plan nets exactly zero) before any UI was built on top of it. The health score deliberately combines two signals that aren't the same thing wearing two hats: engagement (is the team logging in) and plan fit (is the account sized to what it's actually using) — an account with eight people on a two-hundred-fifty-seat plan is a downgrade risk independent of how often those eight people log in, and that distinction is what makes the score a real diagnostic instead of a single reused number. There's no real login system — a lightweight account switcher stands in for auth so every seeded account's state is explorable directly — but every customer-facing page reads the active account from a cookie, which has a useful side effect: pages that read a cookie are rendered per-request automatically, so they can't silently go stale the way a statically-prerendered admin page can. That exact staleness bug was caught in a sibling project on this site; the admin pages here carry an explicit override for the same reason, applied from the start rather than rediscovered. The seed data surfaced a smaller, honest bug during development, too: an early version of the random name generator could assign the same person's name to two different seats on one account, caught by actually reading the rendered team page rather than trusting the row counts.

More projects