# ModuleDesk — SaaS Pricing & Tier Specification

Status: **LOCKED** (agreed with Pol, 2026-06-11; prices and credit pools re-confirmed
2026-08-11). Source of truth for all monetization work.

> **Prices were €19/€49 in the original draft and are now €59/€99.** The code
> (`plan_service.PLAN_PRICE_CENTS`) and the public pricing page have said €59/€99
> for some time; this document was the stale one. Confirmed by Pol on 2026-08-11.

## Tiers

| | Free | Premium **€59/mo** | MAX **€99/mo** |
|---|---|---|---|
| Modules connected | 2 | 6 (+€5/mo per 5 extra, recurring add-on) | Unlimited |
| Team members | 1 | 1 (solo) | Unlimited + roles |
| AI credits/month | **25** | **250** | **1000** + **BYOK** |

Credit pools live in `plan_service.PLAN_LIMITS` — that dict is the single source of
truth. `public.plans` is vestigial (the `Plan` model is never queried) and had drifted;
it was re-synced by hand on 2026-08-11.

- **No Enterprise tier** (discarded — MAX unlimited + BYOK already serves large sellers).
- **Team (multi-user)**: moved from Premium to MAX only as of 2026-06-12. Premium = 1 user (solo). MAX = unlimited team members + roles.
- **Onboarding**: 14-day full MAX trial on signup → auto-downgrade to Free.
- **Payments**: Stripe only (~3.1% all-in). Collect EU VAT ID at signup, validate via VIES, issue 0% reverse-charge invoices. Stripe Tax deferred until meaningful B2C volume.
- **Credit top-ups**: one-time Stripe Checkout packs, consumable, non-recurring, never expire. Premium and MAX only. Repriced 2026-08-12 into three sizes with a real volume discount:

  | Pack | Price | Per credit |
  |---|---:|---:|
  | 100 credits | €4 | €0.040 |
  | 200 credits | €7 | €0.035 |
  | 500 credits | €15 | €0.030 |

  Sized to converge on MAX rather than undercut it: Premium (€59) + three 500-packs = **€104**, just past MAX's €99, so a genuinely heavy seller upgrades instead of living on top-ups. Cost basis ≈ $0.0024/credit, so a €15 pack costs ~$1.20 to serve.

  Implementation: three separate Stripe prices (not one price × quantity — that cannot express a volume discount). Defined in `billing_service.TOPUP_PACKS`, created by `scripts/setup_stripe_products.py`, env vars `STRIPE_PRICE_TOPUP_100/200/500`. `POST /billing/topup` takes `{"pack": "100"|"200"|"500"}`; the granted amount rides in Checkout metadata as `topup_credits`. The legacy `{"units": n}` body and the old `topup_units` metadata key are still honoured so in-flight sessions and older clients keep working.
- **Module add-on**: recurring subscription item, Premium only, shown ONLY in-app on the "module limit reached" screen — never on the public pricing table.

## AI credits model

Pooled per **organization** (not per module). Action-based, replacing the current raw-token counter (`organizations.ai_credits_used`):

Costs are calibrated to **measured average tokens per call** (2026-08-12), not guessed:

| Action | Avg tokens | Credits |
|---|---:|---|
| Guide generation | — | **5** — but the first **3 guides per org are free** (`FREE_GUIDE_ALLOWANCE`) |
| Write Reply (classify + draft) | 1,488 | **3** |
| Improve draft | 850 | **2** |
| Enrich draft | 545 | **2** |
| AI auto-response, thread summary | — | **2** |
| Proofread | 397 | **1** |
| Shorten | 107 | **1** |
| Check Reply | 1,592 | **1** — priced lowest *deliberately*: it is the safety net before Send, and a seller who rations it ships worse replies |
| Translate | — | **1** — deliberately cheap; it's what makes a non-English seller switch tools |
| Historical ingestion (embedding + summarising OLD threads) | — | **10 per 100 threads** |
| Language detect, topic labels, embeddings, TL;DR | — | 0 (free) |
| Live embedding of a **newly arrived** ticket | — | 0 (free — the inbox must never be metered) |

Costs live in `plan_service.CREDIT_COSTS`. Historical ingestion is charged via
`credits_override` in `_track_ai_usage` because its price is per-100-threads, not per
API call. The free-guide allowance counts `ai_usage_log` rows with `call_type='guide'`,
not saved guides — otherwise a user could draft unlimited free guides by never keeping one.

**Measured cost basis** (Pol's 99 days, 766 calls): ≈ **$2.50** of total OpenAI spend,
i.e. **~$0.0024 per credit**. A 250-credit Premium pool costs ~$0.60/month to serve.
Credits are therefore a *segmentation* device, not cost recovery — price them to be
used, and put the real walls on modules, seats and sync frequency.

**Calibration reference:** one active daily seller burns ≈ **172 credits/month**.
- Free (25) — a taste: 8 drafts or 25 proofreads.
- Premium (250) — fits one daily user with ~1.5× headroom; a user at 2× that volume (344) exceeds it and has a reason to upgrade.
- MAX (1000) — pooled per **org**, and MAX is the multi-seat plan, so ~5 seats brush the cap and 6 exceed it. This is intended: exhausting it routes them to top-ups or BYOK. BYOK costs us nothing (they pay OpenAI, we keep €99). Do **not** raise MAX on the theory that 1000 is unreachable — that reasoning comes from solo usage and does not apply to a plan that sells seats.
BYOK (MAX only): org's own OpenAI key bypasses credit deduction entirely (already implemented).

## Feature matrix

| Area | Free | Premium | MAX |
|---|---|---|---|
| Inbox & tickets (filters, tags, priority, drafts, next-ticket) | full | full | full |
| Smart Priority scoring | basic (date/unread) | full 10-signal | full |
| Auto-sync | every 24h + manual | every 30 min | every 30 min |
| Search | keyword only | + semantic | + semantic |
| Quick replies (templates) | 5, local only | unlimited + Addons sync + categories | + auto-attach module ZIP |
| AI composer | **all actions** (metered by credits) | all actions | all + BYOK |
| Translation (in/outbound) | — | ✔ | ✔ |
| Topic segmentation + TL;DR | — | ✔ | ✔ |
| RAG drafts + doc sources/scraping | — | ✔ | ✔ |
| **Guide generation (BETA)** | — | ✔ (beta) | ✔ (beta) |
| Products / module catalog | view only | + ZIP uploads, slugs | + possible-guides badges |
| Orders | last 3 months | 24 months + CSV export | full 10-yr backfill + CSV import + margin/refund filters |
| Customers | basic profile | + filters, order history, revenue | + VIP segmentation, installed-modules detection |
| Website health checks | — | — | ✔ |
| URL safety (VirusTotal) | — | — | ✔ |
| Credential vault | — | — | ✔ |
| Ratings | list | + product references | + CSV export |
| Dashboard | KPI tiles | + revenue/orders charts | + geography map, value segments, CSV export |
| Audit log | — | — | ✔ |
| Settings | theme, signature | + auto-close, language prefs | all |

## Upsell UX (agreed pattern)

- Locked features are **visible**, marked with a crown (Premium) or MAX badge icon — NOT grayed/hidden.
- Click → modal: "This feature is available on the {Premium|MAX} plan. Want to upgrade?" with upgrade CTA → billing page.
- Beta features additionally carry a "Beta" pill next to the crown.
- Free→Premium triggers: AI composer limits, 24h sync delay, template cap.
- Premium→MAX triggers: vault tab, health-check placeholders, ratings/dashboard export buttons, 6-module cap.

## Market context (research 2026-06)

- TAM: ~800–1,500 Addons sellers, ~300–600 active. Year-1 realistic: 50–150 customers.
- No competing seller-side helpdesk exists. Anchors: Freshdesk €15–19/seat, Crisp €45, Zendesk €55/seat.
- Sellers are cost-squeezed (platform -13% YoY, hated €99/module Addons fee) → entry stays at €19.
- Avg module nets seller ~€3,500/yr → one module sale covers ~3 months of Premium.

## Implementation roadmap

1. **Plan enforcement** (CRITICAL GAP): wire `check_feature()` / `check_ai_credits()` (plan_service.py) into routes — currently defined but never called; everything is unlimited today.
2. **Credits conversion**: token counter → action credits (see table above); rename/migrate `ai_credits_used`; deduction in ai_service per call_type; top-up Stripe products + Checkout + webhook crediting.
3. **Tier rework in Stripe**: replace per-module €6/€12 quantity model with flat Free/Premium/MAX prices + module add-on item.
4. **Trial-then-free**: signup grants 14-day MAX trial, Celery job downgrades to Free on expiry.
5. **Upsell UI**: crown/MAX badges + upgrade modal component, applied across templates per matrix.
6. **Public landing + pricing page** (none exists today — /register is the only public route).

## TODOs / deferred

- **Guide detection rework**: current possible-guide clustering produces low-quality proposals — review heuristics before removing the Beta label.
- Stripe Tax: only if B2C volume appears.
- GoCardless SEPA "pay by bank": only if customers ask.
- Per-extra-module add-on UI: build after launch if demand shows.
