<?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: John Farrell</title>
    <description>The latest articles on DEV Community by John Farrell (@jfreal).</description>
    <link>https://dev.to/jfreal</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F154910%2F208d4fb9-e2d3-42cb-b370-2c3baf2f7b9c.png</url>
      <title>DEV Community: John Farrell</title>
      <link>https://dev.to/jfreal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jfreal"/>
    <language>en</language>
    <item>
      <title>Build vs. buy in 2026: how a solo dev replaced 6 SaaS tools with ~$100/mo of AI</title>
      <dc:creator>John Farrell</dc:creator>
      <pubDate>Mon, 15 Jun 2026 14:18:13 +0000</pubDate>
      <link>https://dev.to/jfreal/build-vs-buy-in-2026-how-a-solo-dev-replaced-6-saas-tools-with-100mo-of-ai-3c06</link>
      <guid>https://dev.to/jfreal/build-vs-buy-in-2026-how-a-solo-dev-replaced-6-saas-tools-with-100mo-of-ai-3c06</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Cross-posted from &lt;a href="https://pheidi.training/articles/how-i-built-my-running-app-without-saas/" rel="noopener noreferrer"&gt;pheidi.training&lt;/a&gt;. This is the dev cut — same decisions, more code and auth detail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most build-vs-buy advice is stale. For most of my career, "don't roll your own X" was just true: engineering time was the bottleneck, so you rented anything that wasn't your core product. In 2026 the bottleneck moved. A focused feature that was a week of typing is now an afternoon of prompting and reviewing. So I started re-running the calculation, and I keep landing on &lt;em&gt;build&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Some context: I'm the only person on &lt;a href="https://pheidi.training/" rel="noopener noreferrer"&gt;Pheidi&lt;/a&gt;, an adaptive running-training app. The stack is ASP.NET Core + Blazor, C#, Azure SQL. You hand it a race, a schedule, and your history; it generates and continuously re-plans around your real life. No team, no contractors. Every architectural call gets filtered through "can one person own this forever?"&lt;/p&gt;

&lt;p&gt;Let me start with the number that reframes the whole post: my entire AI bill — the assistant I pair with plus the tokens my agents spend — is roughly &lt;strong&gt;$100/month&lt;/strong&gt;. The SaaS stack it displaced (feedback, email automation, analytics, an AI-features layer, plus a couple of stragglers) would each, individually, cost about that same $100. Bundle six of them and you're at 3–4× my total spend, indefinitely, with the price ratcheting up at every renewal.&lt;/p&gt;

&lt;p&gt;Three categories everyone rents. Here's what building them looked like.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback: 300 lines instead of $79/mo
&lt;/h2&gt;

&lt;p&gt;The default is to bolt on Canny, Intercom, or UserVoice. Solid tools — but they open around &lt;strong&gt;$79/month&lt;/strong&gt;, inject a third-party widget into my UI, and store the single most valuable signal I have (what runners are asking for) on infrastructure I don't control.&lt;/p&gt;

&lt;p&gt;My version is three pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A slide-out feedback panel in the navbar — one Razor component, &lt;strong&gt;197 lines&lt;/strong&gt;. Type, title, body, plus the originating page URL grabbed automatically.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /api/feedback&lt;/code&gt; backed by a &lt;strong&gt;44-line&lt;/strong&gt; service that persists to a &lt;code&gt;FeedbackSubmissions&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;A background notification to my own inbox after the write.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total: &lt;strong&gt;~300 lines&lt;/strong&gt;, and conspicuously &lt;em&gt;no admin dashboard&lt;/em&gt;. That omission is the feature. Every submission shows up in email, where I already triage, star, and reply all day. If I ever want aggregate views, it's a SQL query against data I own — not a report someone else decided to expose.&lt;/p&gt;

&lt;p&gt;The only decision worth flagging is ordering: &lt;strong&gt;persist first, notify second, and never let the notification block.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FeedbackSubmissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;submission&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;        &lt;span class="c1"&gt;// durable before anything else&lt;/span&gt;
&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;submission&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// fire-and-forget; an SMTP blip can't eat feedback&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lose the email? Annoying. Lose a user's words? Never.&lt;/p&gt;

&lt;h2&gt;
  
  
  Email automation: ~1,200 lines instead of a per-contact bill
&lt;/h2&gt;

&lt;p&gt;This is the budget line people actually feel. Mailchimp / ConvertKit / Customer.io get expensive the instant you want a welcome, a drip, and behavioral triggers — and the meter runs on list size.&lt;/p&gt;

&lt;p&gt;Pheidi sends five flavors, all hand-rolled:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Welcome&lt;/strong&gt; at signup, &lt;code&gt;Reply-To&lt;/code&gt; set to my real inbox so a reply is a conversation, not a ticket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5 AM workout reminder&lt;/strong&gt; in each runner's own timezone — today's session, pace target, and a heat tweak computed from the live forecast. A hosted service sweeps every 10 minutes, capped at one per runner per day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase transitions&lt;/strong&gt; (base→build→peak→taper), scanned hourly, deduped per plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day-3 check-in&lt;/strong&gt; that branches on whether you've built a plan yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day-14 nudge&lt;/strong&gt; if you have a plan but zero logged runs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole scheduler/template/eligibility layer is &lt;strong&gt;~1,200 lines of C#&lt;/strong&gt;. The piece I'd actually show another dev is the dedup. No queue, no distributed lock — just an atomic conditional &lt;code&gt;UPDATE&lt;/code&gt; that's safe across multiple app instances:&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="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Plans&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;PhaseEmailSentUtc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;planId&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;PhaseEmailSentUtc&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;-- @@ROWCOUNT == "did I win the race?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@@ROWCOUNT = 0&lt;/code&gt; means another instance already sent it, so I bail. The database is the coordination primitive. Unsubscribe is one-click and &lt;strong&gt;RFC 8058&lt;/strong&gt;-compliant, so Gmail surfaces its native button.&lt;/p&gt;

&lt;p&gt;Here's the boundary I &lt;em&gt;don't&lt;/em&gt; cross: actual delivery. &lt;strong&gt;Azure Communication Services&lt;/strong&gt; puts mail on the wire and charges me fractions of a cent per message. That's the rule I'd hand anyone — &lt;strong&gt;rent the infrastructure, build the workflow.&lt;/strong&gt; Transport, DNS, hosting are commodities with genuine economies of scale. But &lt;em&gt;which human gets which message and when&lt;/em&gt; is product logic, and that's precisely what the SaaS marks up. It was never hard. It was tedious, and tedious is the thing AI assistants vaporize.&lt;/p&gt;

&lt;p&gt;Side effect of co-locating the logic: my 5 AM email calls the &lt;em&gt;same&lt;/em&gt; weather-adjustment code as the in-app plan. They literally cannot drift, because they're one implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agentic tools: a few dollars of tokens instead of an AI middleware tier
&lt;/h2&gt;

&lt;p&gt;Newest category, and the one I'd argue hardest for today. The reflex is to buy "AI features" as a layer that sits between you and your own data — the analytics-dashboard pattern, reborn. For a solo dev that's inverted. Spend a few dollars of tokens and let an agent touch the real database and the real product directly. No middle tier, no per-seat tax.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built thing #1: an MCP server inside the app.&lt;/strong&gt; Eight tools over the Model Context Protocol — &lt;code&gt;get_today_workout&lt;/code&gt;, &lt;code&gt;list_upcoming_workouts&lt;/code&gt;, &lt;code&gt;record_workout&lt;/code&gt;, &lt;code&gt;report_injury&lt;/code&gt;, &lt;code&gt;add_vacation&lt;/code&gt;, &lt;code&gt;update_profile&lt;/code&gt;, &lt;code&gt;submit_feedback&lt;/code&gt;, &lt;code&gt;get_active_plan&lt;/code&gt;. An MCP client like Claude connects and the runner just talks: &lt;em&gt;"calf felt tight on today's run — log it and back me off this week."&lt;/em&gt; That fans out to &lt;code&gt;report_injury&lt;/code&gt; + &lt;code&gt;record_workout&lt;/code&gt;, and the plan re-plans identically to the UI path.&lt;/p&gt;

&lt;p&gt;The tools themselves were trivial — &lt;strong&gt;418 lines&lt;/strong&gt;. The real work was auth: a full &lt;strong&gt;OAuth 2.1 + PKCE flow with dynamic client registration&lt;/strong&gt;, so an assistant can attach to a user account without me minting API keys by hand. Call it &lt;strong&gt;~500 lines and a stack of RFCs&lt;/strong&gt;, but it's paid once. Every current and future MCP client now works with Pheidi with zero per-vendor integration on my side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built thing #2: the same idea aimed inward.&lt;/strong&gt; No feedback dashboard, remember? When I want product signal I point a coding agent at my own DB — &lt;em&gt;"cluster last month's feature requests by theme"&lt;/em&gt; — for pennies of tokens against data I own. A SaaS dashboard is a frozen set of someone else's queries with chrome on top. An agent with raw read access is &lt;em&gt;every&lt;/em&gt; query, including the ones nobody thought to pre-build.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest caveats
&lt;/h2&gt;

&lt;p&gt;"Just build it" is overcorrectable, so the boundaries matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This scales &lt;em&gt;down&lt;/em&gt;, not up. With a team, a tool's RBAC, audit log, and onboarding can be worth every cent. At a million sends a month, deliverability is a job title, not a feature.&lt;/li&gt;
&lt;li&gt;Some things I will never own: payments, raw mail transport, hosting. &lt;em&gt;Rent infrastructure, build workflow&lt;/em&gt; — and "infrastructure" is a bigger set than it looks.&lt;/li&gt;
&lt;li&gt;The maintenance is permanently mine. A bug in the day-3 email has exactly one assignee. That's been fine precisely because ~300-line, no-dashboard systems have almost no surface area. I keep them small deliberately.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Every subscription is more than a charge. It's another dashboard, another login, another integration to babysit, another export to fret over. Twenty of them is a part-time ops job before any of them earns its keep. And they compound quietly: $29 + $79 + ... each defensible alone, several hundred a month together, forever, for features that barely speak to each other.&lt;/p&gt;

&lt;p&gt;My ~$100/month buys the opposite shape: &lt;strong&gt;code I own outright&lt;/strong&gt; instead of rent that only climbs. The reason this works now has nothing to do with me being a strong engineer — it's that focused, boring, well-trodden code (a feedback form, a drip engine, an OAuth dance) got cheap, and assistants are &lt;em&gt;excellent&lt;/em&gt; at well-trodden problems. The cost was always the typing.&lt;/p&gt;

&lt;p&gt;So the question I now ask before every purchase: &lt;strong&gt;product, or plumbing?&lt;/strong&gt; Plumbing → rent it. Product → it might be an afternoon to own it, with your data in your database, doing exactly what you need and not one thing more.&lt;/p&gt;

&lt;p&gt;The app all this plumbing serves is &lt;a href="https://pheidi.training/" rel="noopener noreferrer"&gt;Pheidi&lt;/a&gt;, a plan that adapts to your life. But seriously — build the feedback form first.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>webdev</category>
      <category>ai</category>
      <category>indiehackers</category>
    </item>
  </channel>
</rss>
