DEV Community

Ju
Ju

Posted on

Shipping a Team Plan: Pricing, Growth, Pain Relief, and How-To

Adding a Team plan (we call it “Crew”) is one of the highest‑leverage levers for a product graduating from solo users to real collaboration. It changes your pricing, accelerates growth loops, removes a latent blocker for buyers, and—done right—doesn’t require a rewrite.

Here’s how we approached it in Indie10k, what we learned, and how you can ship it without derailing your roadmap.

Why a Team Plan Matters

  • Momentum spreads in groups. When collaborators see each other’s progress, they keep pace.
  • Expansion revenue beats pure acquisition. Teams raise ARPU and unlock account‑level upgrades.
  • The “but my co‑founder needs access” objection disappears once you support basic roles.

Pricing Model: From Individual to Account Seats

We priced Crew around an account‑level seat pool, not per‑project invites.

  • Account seats represent unique collaborators across all owned projects.
  • Pending invites consume seats; revoking frees them immediately.
  • The owner doesn’t count against the pool, and the same collaborator on multiple projects counts once.

This strikes a balance between predictable revenue and flexibility for small teams or collectives. For Indie10k:

  • Free/Builder/Hacker: solo or limited collaboration (0 seat pool by default)
  • Crew: includes a pooled seat allowance (e.g., 3 seats)

Env example:

ENABLE_TEAM=true
TEAM_DEFAULT_SEAT_POOL=0
TEAM_CREW_SEAT_POOL=3
TEAM_INVITE_EXPIRES_DAYS=7
Enter fullscreen mode Exit fullscreen mode

Tip: Map seats to paid tiers via your billing mapping rather than baking prices into code.

Growth Effects: ARPU, Viral Loops, Retention

  • Higher ARPU: Accounts upgrade to unlock more collaborators; seat caps create natural upsell moments.
  • Product‑led invites: Each invite is a low‑friction acquisition touchpoint; acceptance funnels new users into your product.
  • Retention via accountability: Teams return to close loops together (status checks, evidence sharing, and reviews).

Instrumentation ideas:

  • Track seat‑cap‑reached events as an upsell signal.
  • Measure invite acceptance rate and time‑to‑first‑action post‑accept.
  • Segment retention by “solo vs team” to quantify the impact.

Pain It Solves (Real Quotes We Heard)

  • “I can’t add my co‑founder without sharing my login.”
  • “We split projects between personal accounts—now everything’s scattered.”
  • “We need read‑only for the advisor and write for the dev.”

Answer: roles (reader, writer, admin, owner) with predictable behavior and a simple members UI.

How To Ship It (Fast, Safely)

The path we used—minimal, reversible, and feature‑flagged.

1) Start With a Tight PRD

Keep scope lean: invites by email, roles, a members panel, seat enforcement, and secure tokens. Our full PRD is in docs/team-prd.md and covers:

  • Roles matrix and non‑goals for v1.
  • Account‑level seat pool rules.
  • Security (hashed, expiring, single‑use tokens; no email enumeration).

2) Schema + Services (Server‑First)

Add two tables:

  • project_members(project_id, user_id, role, status, …)
  • project_invites(project_id, email, role, token_hash, expires_at, status, …)

Service helpers (server‑only):

  • requireRole(projectId, userId, minRole)
  • createInvite(projectId, email, role, invitedBy)
  • acceptInvite(token, userId)
  • changeMemberRole(...), removeMember(...), revokeInvite(inviteId)
  • Seat math: getSeatPoolSize(ownerId) + getSeatsUsed(ownerId) (distinct users + pending emails across all owner projects)

3) APIs That Mirror the Mental Model

  • POST /api/projects/:id/invites → create invite (admin+)
  • POST /api/team/invites/accept → accept invite (auth)
  • PATCH /api/projects/:id/members/:userId → change role (admin+)
  • DELETE /api/projects/:id/members/:userId → remove (admin+)
  • DELETE /api/projects/:id/invites/:inviteId → revoke (admin+)
  • POST /api/projects/:id/invites/:inviteId/resend → rotate token + resend (admin+, rate‑limited)
  • GET /api/projects/:id/members → list members + pending invites + seat usage

Guard every route with server‑side role checks. Keep responses predictable: NextResponse.json({ ... }, { status }).

4) Email + Accept Flow

  • Invite email includes an accept URL: /invite/accept?token=....
  • The page calls POST /api/team/invites/accept and redirects to the project.
  • In dev: RESEND_API_KEY=STDOUT to safely preview emails in logs.

5) Members UI That Doesn’t Overreach

  • A members list with roles and a simple invite form.
  • A seat meter that reads “X of Y seats used (account‑wide)”.
  • Pending invites with “Resend” and “Revoke”.
  • Show names/emails instead of raw IDs for clarity.

6) Ratchets, Not Roadblocks

Use rate limits where abuse is likely (invite create/resend). Fail‑open on missing infra in dev.

// Example keys
invite:create:user:{userId}
invite:resend:user:{userId}
invite:resend:project:{projectId}
Enter fullscreen mode Exit fullscreen mode

7) Secure by Default

  • Hash tokens (sha256 with a salt) and store only the hash.
  • Enforce expiry on accept; treat missing keys as 500 in production.
  • Avoid email enumeration; always respond with a generic “Invite sent”.

Example PRD (Summary)

Roles: reader, writer, admin, owner. Readers view; writers edit and run actions; admins manage members; owner is immutable and can delete the project.

Seat Pool: unique collaborators across owner’s projects; pending invites count; revokes free seats immediately.

UI: Members list, invite form, seat meter, and clear error states.

Reference: see docs/team-prd.md for the complete matrix and constraints.

Implementation Guide (Summary)

We documented the exact stack‑aligned steps (Drizzle schema, App Router routes, Zod validators, Resend emails, Stack Auth checks) in docs/team-impl.md.

Highlights:

  • Feature flag: ENABLE_TEAM hides everything until you’re ready.
  • Role checks: centralized with requireRole() and reused across routes.
  • Access scope: extend project listings and fetches to include active members (not just owners), while locking down mutations to writer/admin.

Final Notes

If you’ve got even a handful of users asking “Can I invite my co‑founder?”, a Team plan unlocks not just collaboration—but an entire business model path: expansion revenue, stickier usage, and more reliable growth.

Ship it small. Lock it down. Iterate from there.

Top comments (0)