Rebrand paid tier to $99 Founding Lifetime Member

Replace the $10 "Premium" pitch with a $99 Founding Lifetime Member
offer (first 1,000 accounts) and list every bonus feature in the join@
upgrade prompt: personal email + webmail, custom domains, Tor access,
and lifetime locked-in pricing.

- payments: PremiumPriceLabel/default amount $10 -> $99, add FoundingCap
- join@ offer + already-paid welcome + gated-route upsell copy
- README + interface doc comments

Price stays env-overridable via AGENTBBS_PREMIUM_AMOUNT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Anthony Ettinger 2026-06-14 12:46:23 +00:00
parent 52b06d8cfe
commit 2a9d841ddb
4 changed files with 30 additions and 19 deletions

View file

@ -13,9 +13,10 @@ Just two SSH front doors: **`join@`** to onboard a new key, then
domains are all reached from there.
**Membership:** a verified-email account is **free** — you get a personal Docker
pod and a homepage at `https://bbs.profullstack.com/~name`. **Premium** ($10
one-time, lifetime) adds a personal `name@bbs.profullstack.com` email (via
forwardemail.net) and custom domains.
pod and a homepage at `https://bbs.profullstack.com/~name`. **Founding Lifetime
Member** ($99 one-time, first 1,000 accounts only) adds, for life: a personal
`name@bbs.profullstack.com` email + webmail (via forwardemail.net), custom
domains, and Tor access (`ssh tor@` — fetch URLs & join IRC over Tor).
No browser, no install, no client download. The BBS is a hub of hot-swappable
plugins around one shared account system; the full product plan is in
@ -137,7 +138,7 @@ server. Full details: [`docs/irc.md`](docs/irc.md).
- **Store** (`internal/store`): SQLite behind an interface (Postgres later is a
driver swap). Users, sessions, scores, pod subscriptions.
- **Payments** (`internal/payments`): CoinPay REST API (coinpayportal.com) for
the $10 lifetime membership + HMAC payment references; manual grant for ops.
the $99 Founding Lifetime membership + HMAC payment references; manual grant for ops.
## License

View file

@ -5,7 +5,7 @@
// ssh bbs@host the BBS hub, guests welcome (play@/guest@ are aliases)
// ssh <name>@host the hub as a member/agent (SSH key required)
// ssh join@host onboarding: registers your key, confirms your email with an
// emailed code, then offers $10 lifetime Premium (CoinPay)
// emailed code, then offers $99 Founding Lifetime (CoinPay)
// ssh pod@host your personal Linux pod — free for verified members
// ssh domain@host point your own domain at your homepage (Premium; add/rm/list)
// ssh admin@host the operator admin console ($AGENTBBS_ADMINS only)
@ -372,7 +372,7 @@ func readLine(s ssh.Session, in *bufio.Reader) (string, error) {
// handleJoin runs onboarding interactively in one SSH session: register the
// visitor's key, confirm their email with a code we email them, then offer the
// $10 lifetime Premium membership (CoinPay). It then disconnects.
// $99 Founding Lifetime membership (CoinPay). It then disconnects.
func (a *app) handleJoin(s ssh.Session) {
fp := auth.Fingerprint(s.PublicKey())
if fp == "" {
@ -432,7 +432,7 @@ func (a *app) handleJoin(s ssh.Session) {
" homepage https://" + a.host + "/~" + u.Name,
}, "\n"))
// 2) Premium ($10 lifetime): personal @host email + custom domains.
// 2) Founding Lifetime ($99 one-time): personal @host email + custom domains.
a.offerPremium(s, &u)
_ = s.Exit(0)
}
@ -577,7 +577,7 @@ func (a *app) ensurePremium(u *store.User) bool {
func (a *app) showPremiumWelcome(s ssh.Session, u store.User) {
lines := []string{
"",
" ★ Premium — thanks! Your perks:",
" ★ Founding Lifetime Member — thanks! Your perks:",
"",
" email " + a.fe.Address(u.Name),
" forwards " + u.Email,
@ -592,7 +592,7 @@ func (a *app) showPremiumWelcome(s ssh.Session, u store.User) {
wish.Println(s, strings.Join(lines, "\n"))
}
// offerPremium pitches the $10 lifetime membership — a personal @host email and
// offerPremium pitches the $99 Founding Lifetime membership — a personal @host email and
// custom domains. When CoinPay can mint a charge in-session it shows the exact
// amount and deposit address; otherwise it falls back to a pay command.
// Non-blocking: the member pays out of band and perks unlock on their next
@ -607,9 +607,15 @@ func (a *app) offerPremium(s ssh.Session, u *store.User) {
lines := []string{
"",
" Upgrade to Premium — " + payments.PremiumPriceLabel + ", one-time:",
" • your own email " + a.fe.Address(u.Name) + " (forwards to you)",
" ★ Founding Lifetime Member — $" + payments.PremiumAmount() + ", one-time",
" Only the first " + payments.FoundingCap + " accounts. Pay once, keep it for life.",
"",
" Everything in your free membership stays free — founding adds these",
" bonus features, forever:",
" • your own email " + a.fe.Address(u.Name) + " (forwards to you, webmail included)",
" • custom domains point yourdomain.com at your homepage",
" • Tor access ssh tor@" + a.host + " — fetch URLs & join IRC over Tor",
" • locked-in price founding rate is yours for life — never renew, never pay again",
"",
}
if c, ok, err := payments.CreatePremiumCharge(ref); ok && err == nil {
@ -739,7 +745,7 @@ func (a *app) handleDomain(s ssh.Session) {
_ = s.Exit(1)
return
}
// Custom domains are a Premium perk ($10 lifetime). ensurePremium also
// Custom domains are a Founding Lifetime perk ($99 one-time). ensurePremium also
// catches a payment that settled since their last visit.
if !a.ensurePremium(&u) {
wish.Println(s, strings.Join([]string{
@ -883,7 +889,7 @@ func (a *app) torMember(s ssh.Session, route string) (store.User, bool) {
return store.User{}, false
}
if !a.ensurePremium(&u) {
wish.Println(s, " "+route+" is a Premium feature ($10 lifetime). Upgrade: ssh join@"+a.host)
wish.Println(s, " "+route+" is a Founding Lifetime Member feature ($99 one-time, lifetime). Upgrade: ssh join@"+a.host)
_ = s.Exit(1)
return store.User{}, false
}

View file

@ -1,4 +1,4 @@
// Package payments gates the one-time $10 lifetime Premium membership on
// Package payments gates the one-time $99 Founding Lifetime membership on
// CoinPay (coinpayportal.com). It talks to the CoinPay REST API directly over
// HTTP — no `coinpay` CLI needs to be installed on the host.
//
@ -7,7 +7,7 @@
// COINPAY_API_KEY Bearer key for the CoinPay API (cp_live_…)
// AGENTBBS_COINPAY_MERCHANT_ID merchant/business id payments are created under
// AGENTBBS_COINPAY_API_URL API base (default https://coinpayportal.com/api)
// AGENTBBS_PREMIUM_AMOUNT fiat amount (default 10)
// AGENTBBS_PREMIUM_AMOUNT fiat amount (default 99)
// AGENTBBS_PREMIUM_CURRENCY fiat currency (default USD)
// AGENTBBS_PREMIUM_BLOCKCHAIN settlement chain (default eth)
//
@ -35,11 +35,15 @@ const PodTerm = 31 * 24 * time.Hour
// PremiumPriceLabel is the human-readable price for the one-time lifetime
// membership offered at join@.
const PremiumPriceLabel = "$10 (lifetime)"
const PremiumPriceLabel = "$99 (lifetime)"
// FoundingCap is the marketing cap on Founding Lifetime memberships — the offer
// is pitched as available to the first this-many accounts.
const FoundingCap = "1,000"
// Premium charge parameters — overridable via env so the offer can change
// without a rebuild.
func PremiumAmount() string { return envOr("AGENTBBS_PREMIUM_AMOUNT", "10") }
func PremiumAmount() string { return envOr("AGENTBBS_PREMIUM_AMOUNT", "99") }
func PremiumCurrency() string { return envOr("AGENTBBS_PREMIUM_CURRENCY", "USD") }
func PremiumBlockchain() string { return envOr("AGENTBBS_PREMIUM_BLOCKCHAIN", "eth") }

View file

@ -82,8 +82,8 @@ type Store interface {
// SetPremiumPayment records the CoinPay payment id of a pending premium
// charge so a later visit can verify whether it settled.
SetPremiumPayment(userID int64, payID string) error
// GrantPremium marks the account as a lifetime premium member (the $10
// one-time membership), recording the CoinPay payment reference. Idempotent.
// GrantPremium marks the account as a lifetime premium member (the $99
// Founding Lifetime membership), recording the CoinPay payment reference. Idempotent.
GrantPremium(userID int64, paymentRef string) error
RecordSession(userID int64, username, remote, route string) (int64, error)