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. domains are all reached from there.
**Membership:** a verified-email account is **free** — you get a personal Docker **Membership:** a verified-email account is **free** — you get a personal Docker
pod and a homepage at `https://bbs.profullstack.com/~name`. **Premium** ($10 pod and a homepage at `https://bbs.profullstack.com/~name`. **Founding Lifetime
one-time, lifetime) adds a personal `name@bbs.profullstack.com` email (via Member** ($99 one-time, first 1,000 accounts only) adds, for life: a personal
forwardemail.net) and custom domains. `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 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 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 - **Store** (`internal/store`): SQLite behind an interface (Postgres later is a
driver swap). Users, sessions, scores, pod subscriptions. driver swap). Users, sessions, scores, pod subscriptions.
- **Payments** (`internal/payments`): CoinPay REST API (coinpayportal.com) for - **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 ## License

View file

@ -5,7 +5,7 @@
// ssh bbs@host the BBS hub, guests welcome (play@/guest@ are aliases) // 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 <name>@host the hub as a member/agent (SSH key required)
// ssh join@host onboarding: registers your key, confirms your email with an // 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 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 domain@host point your own domain at your homepage (Premium; add/rm/list)
// ssh admin@host the operator admin console ($AGENTBBS_ADMINS only) // 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 // 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 // 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) { func (a *app) handleJoin(s ssh.Session) {
fp := auth.Fingerprint(s.PublicKey()) fp := auth.Fingerprint(s.PublicKey())
if fp == "" { if fp == "" {
@ -432,7 +432,7 @@ func (a *app) handleJoin(s ssh.Session) {
" homepage https://" + a.host + "/~" + u.Name, " homepage https://" + a.host + "/~" + u.Name,
}, "\n")) }, "\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) a.offerPremium(s, &u)
_ = s.Exit(0) _ = 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) { func (a *app) showPremiumWelcome(s ssh.Session, u store.User) {
lines := []string{ lines := []string{
"", "",
" ★ Premium — thanks! Your perks:", " ★ Founding Lifetime Member — thanks! Your perks:",
"", "",
" email " + a.fe.Address(u.Name), " email " + a.fe.Address(u.Name),
" forwards " + u.Email, " forwards " + u.Email,
@ -592,7 +592,7 @@ func (a *app) showPremiumWelcome(s ssh.Session, u store.User) {
wish.Println(s, strings.Join(lines, "\n")) 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 // 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. // 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 // 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{ lines := []string{
"", "",
" Upgrade to Premium — " + payments.PremiumPriceLabel + ", one-time:", " ★ Founding Lifetime Member — $" + payments.PremiumAmount() + ", one-time",
" • your own email " + a.fe.Address(u.Name) + " (forwards to you)", " 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", " • 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 { if c, ok, err := payments.CreatePremiumCharge(ref); ok && err == nil {
@ -739,7 +745,7 @@ func (a *app) handleDomain(s ssh.Session) {
_ = s.Exit(1) _ = s.Exit(1)
return 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. // catches a payment that settled since their last visit.
if !a.ensurePremium(&u) { if !a.ensurePremium(&u) {
wish.Println(s, strings.Join([]string{ 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 return store.User{}, false
} }
if !a.ensurePremium(&u) { 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) _ = s.Exit(1)
return store.User{}, false 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 // CoinPay (coinpayportal.com). It talks to the CoinPay REST API directly over
// HTTP — no `coinpay` CLI needs to be installed on the host. // 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_…) // COINPAY_API_KEY Bearer key for the CoinPay API (cp_live_…)
// AGENTBBS_COINPAY_MERCHANT_ID merchant/business id payments are created under // AGENTBBS_COINPAY_MERCHANT_ID merchant/business id payments are created under
// AGENTBBS_COINPAY_API_URL API base (default https://coinpayportal.com/api) // 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_CURRENCY fiat currency (default USD)
// AGENTBBS_PREMIUM_BLOCKCHAIN settlement chain (default eth) // 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 // PremiumPriceLabel is the human-readable price for the one-time lifetime
// membership offered at join@. // 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 // Premium charge parameters — overridable via env so the offer can change
// without a rebuild. // 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 PremiumCurrency() string { return envOr("AGENTBBS_PREMIUM_CURRENCY", "USD") }
func PremiumBlockchain() string { return envOr("AGENTBBS_PREMIUM_BLOCKCHAIN", "eth") } 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 // SetPremiumPayment records the CoinPay payment id of a pending premium
// charge so a later visit can verify whether it settled. // charge so a later visit can verify whether it settled.
SetPremiumPayment(userID int64, payID string) error SetPremiumPayment(userID int64, payID string) error
// GrantPremium marks the account as a lifetime premium member (the $10 // GrantPremium marks the account as a lifetime premium member (the $99
// one-time membership), recording the CoinPay payment reference. Idempotent. // Founding Lifetime membership), recording the CoinPay payment reference. Idempotent.
GrantPremium(userID int64, paymentRef string) error GrantPremium(userID int64, paymentRef string) error
RecordSession(userID int64, username, remote, route string) (int64, error) RecordSession(userID int64, username, remote, route string) (int64, error)