<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Saloni G</title>
    <description>The latest articles on DEV Community by Saloni G (@saloni_girhepuje_d13f0820).</description>
    <link>https://dev.to/saloni_girhepuje_d13f0820</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4196414%2Fce879f6d-c152-448a-8aad-8c46a013741d.png</url>
      <title>DEV Community: Saloni G</title>
      <link>https://dev.to/saloni_girhepuje_d13f0820</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saloni_girhepuje_d13f0820"/>
    <language>en</language>
    <item>
      <title>What we learned building a paid inbox you can self-host (Go + Next.js + Postgres)</title>
      <dc:creator>Saloni G</dc:creator>
      <pubDate>Wed, 21 Oct 2026 11:05:12 +0000</pubDate>
      <link>https://dev.to/saloni_girhepuje_d13f0820/what-we-learned-building-a-paid-inbox-you-can-self-host-go-nextjs-postgres-5b0m</link>
      <guid>https://dev.to/saloni_girhepuje_d13f0820/what-we-learned-building-a-paid-inbox-you-can-self-host-go-nextjs-postgres-5b0m</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;This isn't a launch post. It's the handful of decisions that shaped the code, and why we made them. The rest of this series goes deeper on each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. One person per install
&lt;/h2&gt;

&lt;p&gt;The obvious design is multi-tenant: one database, many creators, a &lt;code&gt;creator_id&lt;/code&gt; on every row. We went the other way. Each install serves exactly one person, with one Stripe account and one inbox. The &lt;code&gt;creators&lt;/code&gt; table enforces it with a singleton column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;singleton&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What that bought us:&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The people writing in don't get accounts
&lt;/h2&gt;

&lt;p&gt;Asking someone to sign up before they can pay to send one note kills the flow. So guests never sign up. They pay through Stripe Checkout, and once the webhook confirms the payment, the success page swaps the Checkout session for an HTTP-only session cookie. If they lose it, they request a single-use recovery link by email.&lt;/p&gt;

&lt;p&gt;Only the owner signs in, with Google via Firebase. If you'd rather use generic OIDC, that's the piece to swap.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Prices live on the server
&lt;/h2&gt;

&lt;p&gt;Checkout uses named tiers. The browser says "the 500-character note", never "$10", and the server ignores any amount or currency the client sends. When Stripe's webhook arrives, the server checks the amount again.&lt;/p&gt;

&lt;p&gt;Notes are priced by length: $5 for 250 characters, $10 for 500 and $15 for 1,000 by default, all editable by the owner. A custom amount has to beat the top tier.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  4. Replies can be locked
&lt;/h2&gt;

&lt;p&gt;A reply can carry a photo or video, free or paid to unlock. Media sits in Garage, a small S3-compatible store that only listens inside the Compose network. Every file goes through an API route that checks the session and the unlock. There are no public bucket URLs.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. A boring stack in one Compose file
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Go (Chi, pgx, sqlc) for the API&lt;/li&gt;
&lt;li&gt;Next.js on Bun for the web app&lt;/li&gt;
&lt;li&gt;Postgres 18&lt;/li&gt;
&lt;li&gt;Garage for media&lt;/li&gt;
&lt;li&gt;Caddy for TLS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;docker compose up&lt;/code&gt; starts them in order, and a one-shot migrate container runs before the API.&lt;/p&gt;

&lt;p&gt;We tried to keep the hosting story honest. This is five long-running services with volumes for data and media, so single-service platforms don't fit, but a Linux VPS does. There are no prebuilt images yet, so it builds on the box. The build peaks around 2.7 GB of RAM; idle, it sits at a few hundred MB.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Being upfront about AI
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does anyone pay?
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/designAtHotly/hotly-os" rel="noopener noreferrer"&gt;https://github.com/designAtHotly/hotly-os&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;On your laptop, with no provider keys: &lt;code&gt;./scripts/install.sh --local --skip-firebase --skip-stripe --skip-sendgrid&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If you'd rather not run a server, there's a hosted version: &lt;a href="https://hotly.com/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=os-launch&amp;amp;utm_content=devto-overview" rel="noopener noreferrer"&gt;hotly.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

</description>
      <category>opensource</category>
      <category>go</category>
      <category>nextjs</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>What we learned building a paid inbox you can self-host (Go + Next.js + Postgres)</title>
      <dc:creator>Saloni G</dc:creator>
      <pubDate>Tue, 20 Oct 2026 15:56:28 +0000</pubDate>
      <link>https://dev.to/saloni_girhepuje_d13f0820/what-we-learned-building-a-paid-inbox-you-can-self-host-go-nextjs-postgres-2emj</link>
      <guid>https://dev.to/saloni_girhepuje_d13f0820/what-we-learned-building-a-paid-inbox-you-can-self-host-go-nextjs-postgres-2emj</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;This isn't a launch post. It's the handful of decisions that shaped the code, and why we made them.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. One person per install
&lt;/h2&gt;

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

&lt;p&gt;What that bought us:&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The people writing in don't get accounts
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Prices live on the server
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Replies can be locked
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. A boring stack in one Compose file
&lt;/h2&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Being upfront about AI
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does anyone pay?
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/designAtHotly/hotly-os" rel="noopener noreferrer"&gt;https://github.com/designAtHotly/hotly-os&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Locally: &lt;code&gt;./scripts/install.sh --local&lt;/code&gt; runs everything with external services skipped&lt;/li&gt;
&lt;li&gt;If you'd rather not run a server, there's a hosted version: &lt;a href="https://hotly.com/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=os-launch" rel="noopener noreferrer"&gt;hotly.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

</description>
      <category>opensource</category>
      <category>go</category>
      <category>nextjs</category>
      <category>selfhosted</category>
    </item>
  </channel>
</rss>
