# MNW Business Assumptions — Machine-Readable Source of Truth # # Status: draft / not yet consumed by tooling. Target consumer is a future # docengine feature that substitutes `{{ key }}` placeholders in markdown # at build time. See `MNW/server/docs/todo.md` § "Docengine — assumption # substitution" for the proposed implementation. # # Until that feature ships, this file is informational. The canonical # values still live in the markdown docs listed in each section header. # # Conventions: # - All currency in USD unless suffixed `_eur`. # - All time in months unless suffixed `_days` / `_years`. # - Percentages as decimals (0.029 not 2.9). # - Each section names the canonical markdown source. # - Open decisions tagged `# OPEN:` — these are placeholder values # pending founder approval per `pricing.md` §7 and `reserve_policy.md` §8. # # As of: 2026-05-16 # Last manual-verified: 2026-05-16 # ─── Expenses (canonical: expenses.md) ──────────────────────────────────── [expenses] F_monthly = 580 # Total fixed monthly burn (rounded from $579.08) F_monthly_exact = 579.08 as_of = "2026-05-16" [expenses.lines] hetzner = 31.00 coworking_industrious = 332.00 claude_code = 200.00 fastmail = 5.00 domains = 2.00 cloudflare = 0.00 postmark = 0.00 apple_developer_amortized = 8.25 # $99/yr ÷ 12 co_llc_periodic_amortized = 0.83 # $10/yr ÷ 12 # ─── Stripe fees (canonical: stripe_fees.md) ────────────────────────────── [stripe] percent = 0.029 # US standard online card fixed = 0.30 # Per-transaction flat fee, USD dispute_fee = 15.00 # Charged once at dispute creation instant_payout_us_pct = 0.015 # US/AU/NZ/AE region (was incorrectly cited as flat 1%) instant_payout_intl_pct = 0.010 # CA/EU/UK/SG/NO/HK/MY region instant_payout_min = 0.50 chargeback_protection_pct = 0.004 # Stripe Radar add-on (fraud-only) tax_pct = 0.005 # Stripe Tax per transaction [stripe.connect_standard] # What MNW currently uses for creator subs. per_account_fee = 0 per_payout_fee = 0 payout_volume_pct = 0 [stripe.connect_express] # Reference only — not used by MNW for creators. per_active_account = 2.00 per_payout = 0.25 payout_volume_pct = 0.0025 [stripe.instant_payout] # Nested form of the flat instant_payout_* keys above; substituted into # legal/payments.md and guide/stripe.md as {{ stripe.instant_payout.* }}. # Kept alongside the flat form for backward compatibility. us_pct = 0.015 intl_pct = 0.010 min_usd = 0.50 # ─── Policy commitments (canonical: guarantees.md) ─────────────────────── # Time-based commitments that appear verbatim across guarantees.md, pricing.md, # tiers.md, faq.md, fan-guide.md, account-lifecycle.md, generative-ai.md, and # how-we-work.md. Semantically distinct even where the number matches today. [policy] buyer_access_days = 90 # guarantees §Buyer Access — post-close fan download window shutdown_notice_days = 90 # guarantees §Shutdown, §Continuity price_change_notice_days = 90 # guarantees §Price Stability, pricing.md grandfather_months = 12 # guarantees §Price Stability cancellation_grace_days = 30 # tiers.md §Cancellation, getting-started.md content_archive_months = 12 # guarantees §Content Archive (planned) generative_ai_notice_days = 30 # generative-ai.md §Governance # ─── Uptime (canonical: guarantees.md §Availability) ───────────────────── [uptime] target_pct = 0.995 target_downtime_hours_year = 44 # (1 - 0.995) × 8760 ≈ 43.8, rounded up # ─── Broadcasts (canonical: tiers.md, mailing-lists.md) ────────────────── [broadcasts] per_day = 1 recipients_per_send = 10000 # Display string with thousands separator; docengine has no thousands filter # so the display form lives here (same pattern as `cohort.cap_display`). recipients_per_send_display = "10,000" # ─── Fan+ (canonical: fan-plus.md) ─────────────────────────────────────── [fan_plus] monthly_price_usd = 8 # Per-fan monthly credit allowance; funded by Fan+ subscription revenue, # not per-account overhead. Fan-only — creators don't receive it. monthly_credit_usd = 5 # ─── Hetzner prices (canonical: hetzner_prices.md) ──────────────────────── [hetzner] fx_eur_to_usd = 1.085 # Verify when FX moves >5% # Compute SKUs (EUR/mo) ccx13_eur = 13.10 # 2 dedicated vCPU / 8 GB / 80 GB — production ccx23_eur = 30.00 # 4 dedicated vCPU / 16 GB / 160 GB cpx11_eur = 3.85 # 2 shared vCPU / 2 GB / 40 GB cpx31_eur = 13.10 # 4 shared vCPU / 8 GB / 160 GB cx22_eur = 4.59 # 2 shared vCPU / 4 GB / 40 GB # Storage and bandwidth object_storage_eur_per_tb = 5.99 object_storage_included_tb = 1 egress_eur_per_tb_overage = 1.00 egress_included_per_server_tb = 1 volume_eur_per_gb = 0.044 ipv4_eur = 0.60 backup_pct_of_server = 0.20 # ─── Tier prices (canonical: pricing.md) ────────────────────────────────── [tiers.founding] # Founder pricing — exactly 50% of standard sticker, locked for life. Active # until the founder window closes (1,000 creators OR exit-beta, whichever # first). Decision 2026-05-18; raised to 8/12/18/30 on 2026-05-31 to track # the standard-tier raise; reset to Refined-A (4/7/9/12) on 2026-07-08 per # pricing_interrogation_2026_07.md §6. basic = 4 small_files = 7 big_files = 9 everything = 12 [tiers.standard] # Refined-A sticker — set 2026-07-08 per pricing_interrogation_2026_07.md §6. # Priced against residency-scale labor ($41.5/hr Y1 support, ~$50/hr blended # engineering) rather than the earlier $80/hr market rate, with right-sized # Basic (10GB) and Small Files (100GB) envelopes. Founder rate is exactly # 50% of these. History: 16/24/36/60 (2026-05-31), 12/18/24/40 (earlier). basic = 8 small_files = 14 big_files = 18 everything = 24 # ─── Tier envelope limits (storage caps + file size caps) ──────────────── # Per-tier file-size envelope. Public-facing copy substitutes these via # {{ tier_limits.* }} so the toml is the single source of truth and a future # limit change is a one-line edit. Stored as display strings (no NBSP, no # trailing periods) because that's the form the docs always need. # # `[tier_bytes]` below carries the same envelopes as machine-readable integer # byte counts — that's the form Rust needs for upload gating, storage-cap # accounting, and the metrics fill-ratio SQL. Docengine validates that each # `tier_bytes.` parses back to the matching `tier_limits.` string, so # a drift between the two forms fails at boot instead of shipping to prod. [tier_limits] # Refined-A envelopes (2026-07-08): Basic shrunk to 5MB/10GB and Small Files # total shrunk to 100GB to make the tier ladder honest to actual content shape # rather than "which tier gives me headroom I'll never use." Big Files and # Everything envelopes unchanged. basic_per_file = "5MB" basic_total = "10GB" small_files_per_file = "500MB" small_files_total = "100GB" big_files_per_file = "20GB" big_files_total = "500GB" everything_per_file = "20GB" everything_total = "500GB" [tier_bytes] # Binary units (KB = 1024 B). Must match [tier_limits] display strings above. basic_per_file = 5242880 # 5 MB basic_total = 10737418240 # 10 GB small_files_per_file = 524288000 # 500 MB small_files_total = 107374182400 # 100 GB big_files_per_file = 21474836480 # 20 GB big_files_total = 536870912000 # 500 GB everything_per_file = 21474836480 # 20 GB everything_total = 536870912000 # 500 GB [annual_discount] # Annual billing is 10% off the monthly × 12 total at every tier, founder # and standard. Two-digit discount, clean pitch. Decision 2026-05-18. # # What this actually covers: # - Stripe per-transaction fees ($0.30 each, charged 12x for monthly vs 1x # for annual): saves MNW ~$3.30/yr per customer regardless of tier. # - The 2.9% percent fee is identical either way (a wash). # - At Basic, 10% off ≈ the literal Stripe saving (close to pass-through). # - At Everything, 10% off ($36/yr) > the Stripe saving ($3.30/yr); MNW # absorbs the difference (~$15/yr per Everything customer at founder # pricing, ~$32/yr per Everything customer at sticker) as a cashflow + # reduced-billing-failure benefit. Defensible but not pure pass-through. # # Computed values (monthly × 12 × 0.9, rounded to nearest dollar): # Founder: $86 / $130 / $194 / $324 # Standard: $173 / $259 / $389 / $648 multiplier = 0.9 months_equivalent_free = 1.2 # 10% of 12 months # ─── Tier mix (canonical: tier_mix.md) ──────────────────────────────────── [tier_mix.assumed] # Pre-launch placeholder distribution. A4 in assumptions.md. # Updated monthly from SQL once creators exist. basic_pct = 0.40 small_files_pct = 0.30 big_files_pct = 0.20 everything_pct = 0.10 # ─── Reserve policy (canonical: reserve_policy.md) ──────────────────────── [reserve] # OPEN: all values in this block per reserve_policy.md §8 T_fixed_months = 12 # OPEN: §8 item 1 S_legal = 50000 # OPEN: §8 item 2 — pre-quote S_shock = 5000 # OPEN: §8 item 3 R_opp = 10000 # OPEN: §8 item 4 rho_annual = 0.50 # OPEN: §8 item 5 — max % of R_opp/yr rho_incident = 0.25 # OPEN: §8 item 6 — max % of R_opp/decision surplus_split_reserve = 0.20 # OPEN: §8 item 7 — steady state surplus_split_earnback = 0.80 # Sum must equal 1.0 transition_buffer_pct = 1.00 # OPEN: §8 item 10 — multiplier on R_cap before personal-to-company transition # ─── Cohort (canonical: pricing.md) ─────────────────────────────────────── [cohort] # OPEN: all values per pricing.md §7 cap_count = 1000 # Decided 2026-05-31; raised from 500 to match public copy in tiers.md / pricing.md. cap_display = "1,000" # Display string with thousands separator; substituted via {{ cohort.cap_display }}. Keep in sync with cap_count. cap_months = 12 # OPEN: §7 item 1 — or first M months (whichever first) lock_duration = "lifetime" # OPEN: §7 item 4 — lifetime-of-subscription # Counting rule (cumulative vs active) — see sops/founding-cohort-tracking.md, founder decision pending # ─── Per-creator marginal cost inputs (canonical: assumptions.md A1-A10) ─ # All currently unmeasured — pre-launch placeholders. [creator_marginal] storage_basic_gb = 0.05 # A1: ~50 MB (right-sized 2026-07-08 alongside 10GB Basic envelope) storage_small_files_gb = 2 # A2: ~2 GB storage_big_files_gb = 10 # A3: ~10 GB storage_everything_gb = 30 # A10: tier-dependent, placeholder storage_cost_per_gb_per_month = 0.0065 # = €5.99/TB-mo × FX egress_origin_hit_assumed_pct = 0.10 # A6: Cloudflare cache absorbs ~90% (assumed) chargeback_rate_tier_subs = 0.001 # A12: 0.1% for recurring (lower than one-shot) # ─── Derived values (for docengine feature; NOT stored — computed) ──────── # # These are documented here for the future implementation. The docengine # feature should compute them from the values above. Listed as commented # pseudo-code; actual implementation belongs in Rust. # # R_cap = T_fixed_months · F_monthly + S_legal + S_shock # = 12 · 580 + 50000 + 5000 = 61960 # # ARPU_founding = Σ (tier_mix[k] · tiers.founding[k]) # = 0.4·8 + 0.3·12 + 0.2·18 + 0.1·30 = 13.40 # # ARPU_standard = Σ (tier_mix[k] · tiers.standard[k]) # = 0.4·16 + 0.3·24 + 0.2·36 + 0.1·60 = 26.80 # # stripe_fee(amount) = stripe.percent · amount + stripe.fixed # stripe_fee_basic_std = 0.029 · 16 + 0.30 = 0.76 # stripe_fee_small_std = 0.029 · 24 + 0.30 = 1.00 # stripe_fee_big_std = 0.029 · 36 + 0.30 = 1.34 # stripe_fee_ev_std = 0.029 · 60 + 0.30 = 2.04 # # break_even(rate_class) = F_monthly / (ARPU_{rate_class} − marginal_avg) # break_even_standard = 580 / (26.80 − 1) ≈ 22.5 creators # break_even_founding = 580 / (13.40 − 1) ≈ 46.8 creators # # surplus(N, rate_class) = N · (ARPU − marginal) − F_monthly # surplus(100, "standard") = 100 · 25.80 − 580 = 2000 # surplus(500, "standard") = 500 · 25.80 − 580 = 12320 # # fill_time_months(N) = (R_cap + R_opp) / surplus(N, "standard") # fill_time(100) = 71960 / 2000 ≈ 36 mo # fill_time(500) = 71960 / 12320 ≈ 6 mo # # infra_cost(tier, N) = storage_{tier}_gb · storage_cost_per_gb_per_month # + egress_share + stripe_fee(price) + F_monthly/N # # Validation rules the build step should enforce: # - 100 < F_monthly < 10000 (typo guard) # - sum(tier_mix.*) = 1.00 (tier mix must sum to 100%) # - surplus_split_reserve + surplus_split_earnback = 1.00 # - 0 < rho_annual ≤ 1 # - 0 < rho_incident ≤ 1 # - rho_incident ≤ rho_annual (single decision ≤ annual budget) # - All `tiers.founding[k]` ≤ `tiers.standard[k]` # - cap_count > 0, cap_months > 0 # ─── Runway disclosure (transparency block on /docs/about/economics) ───── # # Operator-set. Refreshed quarterly at sprint close, alongside the # /changelog recap. The paying-creator count on the page is pulled live # from the DB; this number is the cash-runway bucket. # # Units: quarters at current burn (whole-number bucket; we round down # rather than mislead in either direction). # # A value of 0 means "not yet published" — the page renders without a # number until the first quarterly refresh sets it. [runway] # 17 = $190K committed funding ÷ $3,580/mo current actual burn (ops + $3K # salary), rounded down. Excludes the draw-on-request $50K reserve (would be # 22). Basis: server-internal/business/financial_dashboard.md. Refresh at # sprint close — bump if burn rises (Year 2 $5K salary) or funding changes. quarters = 17 last_updated_iso = "2026-06-04" # ─── Pricing calculator (canonical: this file + stripe_fees.md) ─────────── # # Powers the public /pricing comparison. Consumed by MNW's # `src/pricing_comparison.rs` (parsed directly from this file, not through # docengine's scalar lookup — arrays of tables aren't substitutable). The # net-revenue formula and every competitor's parameters live HERE, not in # client JS, so the public "you keep more" claim has one source of truth and # a pinning test (`pricing_comparison::tests`). Stripe fees are read from the # [stripe] block above (percent, fixed). # # Per-competitor net(revenue) is computed as: # txns = max(1, round(revenue / pricing_calc.avg_transaction)) # pct = platform_pct_over if (platform_pct_threshold set AND rev > it) # else platform_pct # ap = revenue * (1 - pct) - txns * platform_per_tx # net = { none: ap # , percent: ap - ap*stripe.percent # , percent_and_fixed: ap - ap*stripe.percent - txns*stripe.fixed } # - flat_monthly # MNW's own row = platform_pct 0, percent_and_fixed, flat_monthly = tier price. # # Fee structures are each platform's public pricing as of 2026-06. Verify at # the linked page before editing; a change here updates the live page + test. [pricing_calc] avg_transaction = 25 # assumed average sale, for per-tx fee estimation [[competitors]] name = "Patreon" url = "https://www.patreon.com/pricing" fee_label = "10% + ~3% + $0.30" platform_pct = 0.10 stripe_mode = "percent_and_fixed" [[competitors]] name = "Gumroad" url = "https://gumroad.com/pricing" fee_label = "10% + $0.50/tx + ~3%" platform_pct = 0.10 platform_per_tx = 0.50 stripe_mode = "percent" [[competitors]] name = "Bandcamp" url = "https://bandcamp.com/pricing" fee_label = "15% (10% after $5k) + ~3%" platform_pct = 0.15 platform_pct_over = 0.10 platform_pct_threshold = 5000 stripe_mode = "percent" [[competitors]] name = "Ko-fi Free" url = "https://ko-fi.com/pricing" fee_label = "0% tips, 5% shop/memberships + ~3%" platform_pct = 0.05 stripe_mode = "percent" [[competitors]] name = "Ko-fi Gold" url = "https://ko-fi.com/pricing" fee_label = "$12/mo + 0% + ~3%" flat_monthly = 12 stripe_mode = "percent_and_fixed" [[competitors]] name = "Itch.io" url = "https://itch.io/docs/creators/faq" fee_label = "10% default + ~3%" platform_pct = 0.10 stripe_mode = "percent" [[competitors]] name = "Lemon Squeezy" url = "https://www.lemonsqueezy.com/pricing" fee_label = "5% + $0.50/tx (incl. processing)" platform_pct = 0.05 platform_per_tx = 0.50 stripe_mode = "none" [[competitors]] name = "Buy Me a Coffee" url = "https://www.buymeacoffee.com/about" fee_label = "5% + ~3%" platform_pct = 0.05 stripe_mode = "percent" [[competitors]] name = "Substack" url = "https://substack.com/going-paid" fee_label = "10% on paid subs" platform_pct = 0.10 stripe_mode = "percent"