<?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: Morten Pradsgaard</title>
    <description>The latest articles on DEV Community by Morten Pradsgaard (@morten_pradsgaard_5bfde55).</description>
    <link>https://dev.to/morten_pradsgaard_5bfde55</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%2F4174348%2F3c37dfef-7d3f-4d42-8ca9-d22c5be6841a.png</url>
      <title>DEV Community: Morten Pradsgaard</title>
      <link>https://dev.to/morten_pradsgaard_5bfde55</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/morten_pradsgaard_5bfde55"/>
    <language>en</language>
    <item>
      <title>Per-subscriber vs per-send email pricing, worked out</title>
      <dc:creator>Morten Pradsgaard</dc:creator>
      <pubDate>Sun, 11 Oct 2026 19:09:44 +0000</pubDate>
      <link>https://dev.to/morten_pradsgaard_5bfde55/per-subscriber-vs-per-send-email-pricing-worked-out-191o</link>
      <guid>https://dev.to/morten_pradsgaard_5bfde55/per-subscriber-vs-per-send-email-pricing-worked-out-191o</guid>
      <description>&lt;p&gt;Almost every email tool meters one of two things: how many people are on your list, or how many emails you send them. Which is cheaper is not a matter of opinion. You can work it out with one division.&lt;/p&gt;

&lt;p&gt;I build an email tool that bills by sends, so I am not neutral here. That is exactly why the arithmetic below is worth doing on your own numbers instead of taking anyone's word for it, mine included.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one number that decides it
&lt;/h2&gt;

&lt;p&gt;Take the number of emails you send in a typical month and divide it by the number of people on your list. Call it send intensity.&lt;/p&gt;

&lt;p&gt;20,000 subscribers, two campaigns a month to all of them: 40,000 sends over 20,000 subscribers, an intensity of 2. The same list with one campaign every other month: 10,000 over 20,000, an intensity of 0.5.&lt;/p&gt;

&lt;p&gt;Low intensity favours a per-send meter, because you pay for the small number rather than the large one. High intensity favours a per-subscriber meter, because the subscriber count is fixed no matter how often you hit send. Everything else is detail.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Subscribers&lt;/th&gt;
&lt;th&gt;Cadence&lt;/th&gt;
&lt;th&gt;Sends / month&lt;/th&gt;
&lt;th&gt;Intensity&lt;/th&gt;
&lt;th&gt;Better meter&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;20,000&lt;/td&gt;
&lt;td&gt;Every other month&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;Per send&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20,000&lt;/td&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;20,000&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;Roughly level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20,000&lt;/td&gt;
&lt;td&gt;Weekly&lt;/td&gt;
&lt;td&gt;80,000&lt;/td&gt;
&lt;td&gt;4.0&lt;/td&gt;
&lt;td&gt;Per subscriber&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2,000&lt;/td&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;60,000&lt;/td&gt;
&lt;td&gt;30.0&lt;/td&gt;
&lt;td&gt;Per subscriber, clearly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why software teams usually land below 1
&lt;/h2&gt;

&lt;p&gt;A list compounds. Every launch, every mention, every month of organic signups adds people, and nothing removes them except unsubscribes and bounces.&lt;/p&gt;

&lt;p&gt;Sending does not work like that. For a product team, sending is gated by having something worth announcing, which is gated by engineering time. You do not ship twice as often because your list doubled. So the numerator stays roughly flat while the denominator climbs, and intensity falls month after month.&lt;/p&gt;

&lt;p&gt;That is why per-subscriber pricing feels increasingly unfair to a growing software company and feels fine to a daily newsletter. The newsletter's intensity is pinned high by its format. The software company's drifts down by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  The quiet cost of a per-contact meter
&lt;/h2&gt;

&lt;p&gt;When contacts cost money, every decision about your list becomes partly a billing decision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do we put trial signups on the list, or only paying customers?&lt;/li&gt;
&lt;li&gt;Should we delete the contacts who have not opened anything in a year?&lt;/li&gt;
&lt;li&gt;Do we keep the churned accounts, in case they come back?&lt;/li&gt;
&lt;li&gt;Can we afford to keep the people who unsubscribed, as a record that they did?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of those has a correct answer from your business's point of view and a different answer from your invoice's. Keeping no record of who opted out is actively risky: if you cannot see that someone unsubscribed in 2024, nothing stops a fresh import mailing them in 2026.&lt;/p&gt;

&lt;p&gt;When subscribers are free, all four questions collapse into one answer: keep the accurate list.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to check beyond the meter
&lt;/h2&gt;

&lt;p&gt;Three mechanics change the real cost more than the rate does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overage behaviour.&lt;/strong&gt; Does going over your allowance charge you automatically, throttle you, or stop sending? An automatic overage on a per-send plan can turn a mistake into an invoice, which is why a hard cap is worth having.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollover.&lt;/strong&gt; Most allowances reset monthly. An occasional sender should size the plan to the month they actually send, not to their annual average.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whether transactional counts.&lt;/strong&gt; If your app sends password resets and receipts, running them through the same allowance is usually cheaper than a second vendor, but only if the allowance is sized for both. Add them to the numerator before you pick a tier.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where I land
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://day3.app" rel="noopener noreferrer"&gt;day3&lt;/a&gt; on the per-send side of this: unlimited subscribers on every plan, transactional email through the API from the same allowance, and sending pauses at the cap instead of billing past it. Plans run from $1/mo for 1,000 emails to $220/mo for 1,000,000.&lt;/p&gt;

&lt;p&gt;The honest summary: if your intensity is comfortably below 1 and your list is growing, a per-send tool is built for you. If you send a daily letter to a few thousand people, it is not, and a per-subscriber tool will serve you better.&lt;/p&gt;

&lt;p&gt;What is your intensity? I am curious where most dev.to readers land.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>saas</category>
      <category>startup</category>
    </item>
    <item>
      <title>Ask the agent, don't read the screen</title>
      <dc:creator>Morten Pradsgaard</dc:creator>
      <pubDate>Sat, 10 Oct 2026 00:13:26 +0000</pubDate>
      <link>https://dev.to/morten_pradsgaard_5bfde55/ask-the-agent-dont-read-the-screen-42j8</link>
      <guid>https://dev.to/morten_pradsgaard_5bfde55/ask-the-agent-dont-read-the-screen-42j8</guid>
      <description>&lt;p&gt;I run several coding agent sessions at once. The one that has waited twenty minutes for my permission is always the one at the bottom of the pile. So I built Horadric, a desktop app for Windows and macOS that shows every session as a small tile and turns it amber when the agent needs me.&lt;/p&gt;

&lt;p&gt;The interesting part is not the tile. It is how Horadric knows which tile to light.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tempting way is wrong
&lt;/h2&gt;

&lt;p&gt;The obvious design is to watch the terminal. Read the text, look for "Do you want to proceed?", guess the state. It works on day one and breaks on the next release of the agent, because you are parsing a user interface that was never meant to be parsed.&lt;/p&gt;

&lt;p&gt;Horadric never does that. It reads state from the agent's own lifecycle hooks. Claude Code posts each event to Horadric over localhost. A prompt submitted means working. A permission request means waiting. A stop means done. The agent says what it is doing. I just listen.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Horadric installs hooks into the agent's settings. Each one is an &lt;code&gt;http&lt;/code&gt; hook that posts the event to a listener on &lt;code&gt;127.0.0.1:43117&lt;/code&gt;. The hook config carries a header, &lt;code&gt;X-Horadric-Session&lt;/code&gt;, filled from an environment variable that Horadric sets when it starts the session. So every event arrives already tagged with the session it belongs to.&lt;/p&gt;

&lt;p&gt;The state machine on the other side is small. Prompt and tool events mean working. Permission and notification events mean waiting. &lt;code&gt;Stop&lt;/code&gt; means done. &lt;code&gt;SessionEnd&lt;/code&gt; means ended. Subagent events and compaction restarts change nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The guard that matters
&lt;/h2&gt;

&lt;p&gt;Hooks live in the agent's settings, which are global. Install them naively and every &lt;code&gt;claude&lt;/code&gt; on your machine reports to you, including the ones you never asked Horadric to watch.&lt;/p&gt;

&lt;p&gt;The header fixes that. A &lt;code&gt;claude&lt;/code&gt; started outside Horadric sends an empty session header, and the event is dropped. Horadric only tracks sessions it started. Everything else is ignored. I would call this the one rule of the design: do not instrument the whole machine. The design notes in the repo say a similar project got this wrong and wrote it up, and the guard is the lesson I took from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  It still needs care
&lt;/h2&gt;

&lt;p&gt;The tag alone is not enough. Claude Code's background sessions are started by one shared daemon, which copies the environment of whichever session started it. Every background session after that posts under that first session's tag, whoever asked for it. I saw this with a probe on 2026-09-26: a background session started with its own tag arrived under the daemon's. So background sessions get tiles of their own, and I stopped trusting the tag as the whole answer.&lt;/p&gt;

&lt;p&gt;Another gap: Claude Code sends no hook until the first prompt. A fresh session would be invisible. So &lt;code&gt;horadric run&lt;/code&gt; posts a registration event of its own before it starts Claude.&lt;/p&gt;

&lt;h2&gt;
  
  
  What hooks give you for free
&lt;/h2&gt;

&lt;p&gt;Because the events carry real data, a lot comes without parsing anything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hooks fired inside a subagent carry its &lt;code&gt;agent_id&lt;/code&gt;, so a tile can show subagents without adding a hook to anyone's settings.&lt;/li&gt;
&lt;li&gt;Tool hooks tell Horadric which files changed, and whether a test run or a commit succeeded, from the tool input and the success or failure event.&lt;/li&gt;
&lt;li&gt;Every hook carries Claude's session id, which is how a session resumes with &lt;code&gt;claude --resume&lt;/code&gt; after a crash, an update or a reboot.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where hooks run out
&lt;/h2&gt;

&lt;p&gt;Not everything is a hook. No hook carries usage, and there is no public API for a subscription's limits. Claude Code hands those only to the status line command, as JSON on stdin after each reply. So Horadric starts each of its own sessions with a settings file that makes &lt;code&gt;horadric status&lt;/code&gt; the status line, and that passes the JSON on to the same localhost listener. Same rule as before: a &lt;code&gt;claude&lt;/code&gt; started anywhere else is not touched.&lt;/p&gt;

&lt;p&gt;A plain terminal has no hook either. For those, the tile reads the terminal title and its output. I would rather say that than pretend the rule has no exception.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;Nothing much. Horadric is pure Rust, drawing straight to Win32 and Direct2D. About 45 MB with four sessions open, and no CPU between events. No Electron. There is no telemetry. The only thing that leaves the machine is the daily update check against GitHub.&lt;/p&gt;

&lt;p&gt;It works with Claude Code, Codex and Grok Build. The &lt;code&gt;http&lt;/code&gt; hook above is Claude Code's. Codex only runs &lt;code&gt;command&lt;/code&gt; hooks, and Grok Build refuses &lt;code&gt;http://&lt;/code&gt; hook URLs, so those two take other routes. It is free and MIT licensed, for Windows 10 and 11 and macOS 11 or later. Claude Code wrote almost all of it. I made the calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  One warning
&lt;/h2&gt;

&lt;p&gt;The Windows downloads are not code signed yet. SmartScreen will warn you the first time. Choose "More info", then "Run anyway". The Mac app is not notarized yet, so it installs with one line, &lt;code&gt;curl -fsSL https://horadric.dev/install.sh | sh&lt;/code&gt;, which Gatekeeper does not stop. Updates after that are checked against Horadric's own signature. I would rather you read that here than be surprised by it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Site: &lt;a href="https://horadric.dev" rel="noopener noreferrer"&gt;https://horadric.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source: &lt;a href="https://github.com/Mopra/horadric.dev" rel="noopener noreferrer"&gt;https://github.com/Mopra/horadric.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Download: &lt;a href="https://github.com/Mopra/horadric.dev/releases/latest" rel="noopener noreferrer"&gt;https://github.com/Mopra/horadric.dev/releases/latest&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Design notes, including the hook decisions: &lt;a href="https://github.com/Mopra/horadric.dev/blob/main/docs/PLAN.md" rel="noopener noreferrer"&gt;https://github.com/Mopra/horadric.dev/blob/main/docs/PLAN.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>rust</category>
      <category>claude</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
