This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

disjecta / docs / pricing-model.md
7.2 kB

Pricing model — spec (v0, prices unset)#

Status: structure only. Every price below is a variable with a placeholder. Numbers get tuned in the analysis pass; this doc just pins down what each fee attaches to, how it's charged, and the formulas that turn prices → revenue/margin.

1. The three SKUs (the tunable variables)#

Variable Type Unit Placeholder
PRICE_RULE_MONTHLY recurring per rule / month $TBD
PRICE_CHANNEL_BACKFILL one-time per single-channel backfill run $TBD
PRICE_GUILD_BACKFILL one-time per whole-guild backfill run $TBD

A "rule" is a row in the rules table — one guild/channel/author filter → destination pipe. Rules belong to a job (atproto identity), which belongs to a user (Discord account). Per-rule monthly means a user pays N × PRICE_RULE_MONTHLY for their N active rules, regardless of how those rules spread across their atproto identities. This is finer-grained and more usage-aligned than today's per-account model — see §6.1 for the migration implication.

2. Mapping to the data model + Stripe mechanics#

PRICE_RULE_MONTHLY — recurring, per rule#

  • Attaches to: each enabled rules row. Today users.active + users.stripe_customer_id gate dispatch for the whole account; this moves the billable unit all the way down to the individual rule.
  • Stripe: one Subscription per users customer, with a recurring Price whose quantity = count of that user's enabled rules (or one subscription item per rule). Adding/removing/enabling/disabling a rule → adjust quantity → subscription.updated webhook → reconcile.
  • Gating: a rule can dispatch only if it's covered by a paid subscription item. The codebase already disables rules on lapse (disableAllRulesForUser) and the rules.enabled flag exists — per-rule billing maps onto that machinery: a paid rule is enabled, an unpaid one is disabled. Extends dispatchGateClause from per-user to per-rule.

PRICE_CHANNEL_BACKFILL — one-time, per single-channel run#

  • Triggers at: POST /rules/:id/backfill (the single-channel enqueue).
  • Stripe: Checkout Session in payment mode (one-time), or an invoice item. Enqueue the backfill_jobs row only after checkout.session.completed.

PRICE_GUILD_BACKFILL — one-time, per whole-guild run#

  • Triggers at: POST /rules/:id/backfill/all (the fan-out; one job per readable channel).
  • Stripe: same one-time mechanism; gate the fan-out behind payment.
  • Note: one guild run currently fans out to all readable channels (this guild = 40). Whether the flat guild price should scale with channel count is a tuning question (§6).

3. Cost variables to net against revenue#

Variable Meaning Known value
STRIPE_PCT Stripe % per charge 0.029 (2.9%)
STRIPE_FIXED Stripe fixed fee per charge $0.30
INFRA_GUILD_BACKFILL marginal Railway cost / guild run ~$0.003 (measured, ~40 ch)
INFRA_CHANNEL_BACKFILL marginal Railway cost / channel run < $0.001 (measured)
BASE_INFRA_MONTHLY always-on Railway service (fixed) $TBD (amortize across jobs)

Measured this session: a ~40-channel, ~1,800-record guild backfill consumed only ~5 CPU-seconds and ~5 MB egress — single-digit-tenths-of-a-cent of marginal infra. Infra is noise; Stripe's per-transaction fee is the real cost driver on the one-time SKUs.

4. Revenue, cost & margin formulas#

Usage inputs (per month): R = active rules, C = channel backfill runs, G = guild backfill runs. (U = paying users / customers, ≤ R, used for the once-per-invoice Stripe fixed fee.)

MRR              = R × PRICE_RULE_MONTHLY
OneTimeRevenue   = C × PRICE_CHANNEL_BACKFILL + G × PRICE_GUILD_BACKFILL
GrossRevenue     = MRR + OneTimeRevenue

# Stripe takes pct + fixed PER CHARGE. Monthly: 1 invoice per customer (fixed
# applies once per customer, not per rule-item). One-time: 1 charge per run.
net(price)       = price × (1 − STRIPE_PCT) − STRIPE_FIXED
StripeFees       ≈ GrossRevenue × STRIPE_PCT
                   + STRIPE_FIXED × (U + C + G)   # one monthly invoice per user U

NetRevenue       = GrossRevenue − StripeFees
                   − G × INFRA_GUILD_BACKFILL − C × INFRA_CHANNEL_BACKFILL
                   − BASE_INFRA_MONTHLY

Per-unit net margin (what survives Stripe + infra on one run):

GuildMargin   = PRICE_GUILD_BACKFILL   × 0.971 − 0.30 − INFRA_GUILD_BACKFILL
ChannelMargin = PRICE_CHANNEL_BACKFILL × 0.971 − 0.30 − INFRA_CHANNEL_BACKFILL

5. The Stripe-fixed-fee trap (worked, placeholder prices)#

The $0.30 flat fee dominates small one-time charges:

PRICE_GUILD_BACKFILL Net after Stripe + infra Stripe's cut
$0.50 $0.14 72%
$1.00 $0.62 38%
$3.00 $2.61 13%
$5.00 $4.50 10%

Implication: price one-time backfills at ≥ ~$3 or the processor eats the margin. Alternatives to consider in tuning: sell backfill credits in bundles (one Stripe charge, N runs), or fold a backfill allowance into PRICE_RULE_MONTHLY and treat historical backfill as the upsell over included live dispatch.

6. Open decisions (set these before tuning prices)#

  1. Billing unit for monthly: per rule — confirmed. Greenfield: no pricing has shipped yet, so there are no existing subscribers to migrate or grandfather. The current per-account active/stripe_customer_id gating is just code scaffolding, not a live paid product — per-rule billing replaces it outright.
  2. One-time fee = per run or per target? Charge every click, or once per channel/guild with re-runs free? Re-runs are idempotent (deterministic rkeys) — e.g. re-running to repair the 1 socket-errored record this session. Charging again for a repair feels bad; "once per target, re-runs free" may be friendlier.
  3. Flat guild price vs scaled: should PRICE_GUILD_BACKFILL be flat regardless of channel count / message volume, or scale (e.g. base + per-channel)? A 40-channel guild and a 4-channel guild cost us nearly the same (≈ $0), but value differs.
  4. Included allowance: does an active monthly job include any backfill (e.g. live dispatch included, history is paid)? Changes whether one-time SKUs are add-ons or the only backfill path.
  5. Failure/refund policy: refund a cancelled or heavily-errored backfill? Define a threshold (e.g. error_count / dispatched > X).
  6. Capacity, not cost: guild backfills are bounded by the PDS write rate limit (eurosky ~5000/hr), not infra. Concurrent paid guild backfills could queue behind each other — worth a note if we sell volume.
  7. Currency / tax: eurosky-leaning audience → price in EUR? Stripe Tax on one-time charges?

7. Next step#

Pick values for the §6 decisions, then plug real usage assumptions (J, C, G) and candidate prices into §4 to find the price points that clear the Stripe trap and hit a target margin. This doc is the model; the analysis pass tunes the numbers.