跳到正文
DEV Community· Saloni Girhepuje·· 3 小時前AI 評分45

Hotly:構建可自行託管的付費訊息收件匣時學到的事(Go + Next.js + Postgres)

What we learned building a paid inbox you can self-host (Go + Next.js + Postgres)

AI 導讀

Hotly 團隊分享構建可自行託管付費訊息收件匣的設計取捨,並於本月以 Apache 2.0 授權開源;每個安裝只服務一人。團隊以一名擁有 536K Instagram 粉絲的創作者試行,217 人付費留言,累計形成 921 段對話。

正文

We built Hotly, an inbox where people pay to send someone a note and that person replies when they have time. It started as a product for one creator whose DMs had become unmanageable. This month we open-sourced it under Apache 2.0, so anyone can run their own.

This isn't a launch post. It's the handful of decisions that shaped the code, and why we made them.

1. One person per install

The obvious design is multi-tenant: one database, many creators, a creator_id on every row. We went the other way. Each install serves exactly one person, with one Stripe account and one inbox.

What that bought us:

  • Money goes straight into the owner's own Stripe. There's no platform account in the middle, no Connect onboarding, no payouts to reconcile.
  • The data model stays small. No tenant scoping on every query, no "can user A see user B's notes" bugs.
  • Leaving is trivial. Your database, your media, your Stripe customers are already yours.

The cost: an agency running it for ten people runs ten installs. We think that's the right trade for something that handles personal messages and money.

2. The people writing in don't get accounts

Asking someone to create an account before they can pay to send one note kills the flow. So guests don't sign up. They pay through Stripe Checkout, and the return from Checkout sets an HTTP-only cookie that ties their browser to the conversation. If they lose it (new phone, cleared cookies), they request a recovery link by email.

Only the owner signs in, with Google via Firebase. That's the one place we used a managed auth provider, and it's the first thing people ask us to replace with generic OIDC.

3. Prices live on the server

Checkout uses named tiers. The browser says "the 500-character note", never "$10", and the server ignores any amount or currency the client sends. Notes are priced by length: $5 for 250 characters, $10 for 500, $15 for 1,000 by default, all configurable. A custom amount must sit above the top tier.

Pricing by length turned out to matter. It prices the owner's reading and reply time, not the message. A weekly subscription works the same way: $15 a week for a 2,000-character writing allowance, refilled each billing week.

4. Replies can be locked

A reply can carry a photo or video, free or paid to unlock. The owner sets the price from a few multiples of their top tier. Media sits in Garage, a small S3-compatible store, behind the API. Nothing is served from a public bucket.

5. A boring stack in one Compose file

Go (Chi, pgx, sqlc) for the API, Next.js on Bun for the web app, Postgres 18, Garage for media, and Caddy for TLS. docker compose up starts all five; migrations run before the API.

We tried to keep the PaaS story honest: this is five services with volumes for data and media, so single-service platforms don't fit. A Linux VPS does. Because there are no prebuilt images yet, it builds on the box, and the build peaks around 2.7 GB of RAM. Idle, it sits at a few hundred MB.

6. Being upfront about AI

A good part of the code, tests and docs was written with an AI coding assistant. We review and run everything, and CI runs the Go tests and type checks on every pull request and release tag. We'd rather say that than have someone find AGENTS.md and wonder.

Does anyone pay?

We ran it with one creator, 536K followers on Instagram, who told the fans she already had. 217 people paid to write to her, across 921 conversations. One creator isn't a market, but it was enough to convince us the mechanic works.

Try it

We'd love feedback, especially on the one-person-per-install boundary and on what would stop you from running it.

來源:DEV Community · dev.to