<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>devil.services - Field notes</title><description>Field notes from someone who ships real MCP products solo, fast. Build-in-public teardowns, hard opinions on engineering leadership, and the AI-assisted workflow behind it.</description><link>https://devil.services/</link><language>en-gb</language><item><title>I replaced Postmark with a pigeon</title><link>https://devil.services/blog/posts/i-replaced-postmark-with-a-pigeon/</link><guid isPermaLink="true">https://devil.services/blog/posts/i-replaced-postmark-with-a-pigeon/</guid><description>Twenty client domains made Postmark&apos;s per-domain math ugly, so I built Balodis: a multi-tenant transactional email service on Amazon SES. One day, 110 tests, about $13 a month. With an interactive calculator so you can run the build-vs-buy numbers yourself.</description><pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import StatRow from &apos;../../components/StatRow.astro&apos;;
import EmailCostCompare from &apos;../../components/EmailCostCompare.astro&apos;;
import Aside from &apos;../../components/Aside.astro&apos;;&lt;/p&gt;
&lt;p&gt;Transactional email is the mail nobody thanks you for: password resets, order
confirmations, the invoice PDF. I send it for every client site I build and operate,
which means many sending domains, which means I finally read
&lt;a href=&quot;https://postmarkapp.com/pricing&quot;&gt;Postmark&apos;s price list&lt;/a&gt; the way an accountant reads it.
Basic allows 5 sending domains. Pro allows 10. Past that you are on Platform: $18 a
month plus $1.20 per extra 1,000 emails, and DMARC monitoring is another $14 per domain
per month, so twenty domains pay $280 a month for someone to watch DNS records.
&lt;a href=&quot;https://aws.amazon.com/ses/pricing/&quot;&gt;Amazon SES&lt;/a&gt; charges $0.10 per 1,000 and has never
heard of a per-domain fee.&lt;/p&gt;
&lt;p&gt;So I built the thing I kept not building. Balodis (Latvian for pigeon) is a
multi-tenant transactional email service on SES: every domain I operate, one €10.69
Hetzner box, my own safety rails. The pigeon delivers.&lt;/p&gt;
&lt;p&gt;&amp;lt;StatRow
stats={[
{ value: 1, label: &quot;day, empty repo to deployed&quot; },
{ value: 110, label: &quot;unit tests&quot; },
{ value: 13, prefix: &quot;$&quot;, label: &quot;a month total, all domains&quot; },
{ value: 12, suffix: &quot;x&quot;, label: &quot;marginal price gap&quot; },
]}
caption=&quot;From the build log, not a pitch deck. The $13: one Hetzner box plus SES pennies, covering every domain at once. The 12x: Postmark Platform charges $1.20 per extra 1,000 emails, SES charges $0.10.&quot;
/&amp;gt;&lt;/p&gt;
&lt;h2&gt;What Postmark actually sells&lt;/h2&gt;
&lt;p&gt;Calling the SES API is an afternoon. That was never the product. Postmark&apos;s product is
everything around the send: bounce and complaint handling, suppression lists, an
activity feed your support person can read, per-client isolation, and a deliverability
team whose job is keeping the shared IPs clean. SES gives you almost none of that. It
is a firehose with a reputation meter, and if your bounce rate crosses 5%, the meter
is AWS deciding whether your account still sends tomorrow.&lt;/p&gt;
&lt;p&gt;So the build day was not spent on sending. It was spent on rails:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Per-tenant circuit breakers.&lt;/strong&gt; Warn at 2% bounce / 0.05% complaint over a rolling
day, auto-pause the tenant at 3% / 0.08%. The pause lands at SES level, not just in
my database: a paused tenant cannot send even if my app has a bug.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A kill switch&lt;/strong&gt; that stops the whole account in one call, because at 2am you want
one lever, not a runbook debate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Suppression checked on every send&lt;/strong&gt;, mirrored from SES bounce events, so an address
that hard-bounced for one client is not quietly burned by another client&apos;s mail.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A 45-day activity feed&lt;/strong&gt; with the rendered email stored. That window is deliberate:
it is Postmark&apos;s own retention default, and the feed is the feature clients actually
ask about (&quot;did the reset email go out or not?&quot;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DNS onboarding that hands out every record at once&lt;/strong&gt;: three DKIM CNAMEs, a custom
return-path subdomain (Outlook filters the default one), and a DMARC record.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Read that list twice before you build. It is the honest price tag, and the calculator
below does not include it.&lt;/p&gt;
&lt;aside&gt;
  Production access in eu-central-1 came with 50,000 emails a day and 14 per second.
  Raising either is a support request, not a pricing tier. AWS holds the whole account
  to 5% bounce / 0.1% complaint; my per-tenant breakers trip earlier than that on
  purpose.
&lt;/aside&gt;
&lt;h2&gt;Run your own numbers&lt;/h2&gt;
&lt;p&gt;Postmark&apos;s pricing has two levers that matter. The domain caps push you up tiers
regardless of volume, and every extra 1,000 emails costs 12 times SES list price. The
sliders make both visible.&lt;/p&gt;

&lt;p&gt;Three things fall out every time I drag these:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The domain trap is volume-independent.&lt;/strong&gt; Eleven domains sending almost nothing is
still Platform money. On the SES side a domain is a free identity; twenty of them
cost what one costs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The DMARC add-on is the quiet multiplier.&lt;/strong&gt; At twenty domains it outbills the
actual sending. A free external DMARC report reader does the same job.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Below about 10,000 emails a month, the saving is coffee.&lt;/strong&gt; The build case down
there is domains, EU data residency, and owning your message log. Not dollars.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The bug I shipped on day one&lt;/h2&gt;
&lt;p&gt;Idempotency keys. A client sends a key with each request; if the same key arrives
again, you return the original result instead of sending twice. I wrote the obvious
version: insert the message row with its key, call SES, replay the stored row on any
repeat. Then a send failed after the insert. The row existed, the key was recorded,
and my replay logic served that dead row to every retry. Forever. The caller did
everything right, retried, got a success response, and the email did not exist. An
email service that says &quot;sent&quot; and means &quot;no&quot; is worse than no email service.&lt;/p&gt;
&lt;p&gt;The fix: a failed send now releases the key, and an exhausted SES throttle comes back
as an honest 429 with a Retry-After header instead of a fake success. One gap survived
into this post&apos;s first draft: crash in the gap between the insert and the SES call and
the row still stuck with its key burned. Writing the paragraph you are reading shamed
me into building the reconciler the same day. A job now sweeps queued rows older than
15 minutes and asks the event stream what actually happened: no SES evidence means the
send never left, so the row fails and the key comes free; evidence means ingestion
hiccupped, so the event gets re-applied. The remaining honest trade: one rare race can
produce a visible duplicate, which I will take over a silent loss every time. Day one
ships, day three debugs day one, and the blog post debugs day three.&lt;/p&gt;
&lt;aside&gt;
  Shared SES IPs are fine and mostly boring, but nobody at AWS curates your
  neighborhood the way Postmark curates theirs. And complaint rates undercount
  reality, because Gmail has no feedback loop: a quiet dashboard is absence of
  evidence, not proof of health. My domains stay dual-verified on Postmark for a full
  rollback month before anything gets cancelled.
&lt;/aside&gt;
&lt;h2&gt;Should you build this?&lt;/h2&gt;
&lt;p&gt;Buy Postmark if you run one product with a handful of domains and no appetite for
servers. $15 a month for their bounce handling and their deliverability people is
excellent insurance, and I recommended exactly that for years.&lt;/p&gt;
&lt;p&gt;Build when the shape of your business is many small senders: ten or more domains,
clients who ask where their data lives (mine lives in eu-central-1 and a Postgres I
can pg_dump), and an existing habit of running your own boxes. The economics improve
with every domain you add, because SES has no per-domain line and Postmark&apos;s model
&lt;em&gt;is&lt;/em&gt; the per-domain line.&lt;/p&gt;
&lt;p&gt;My evidence boundary, stated plainly: Balodis carries agency-scale volume. Past
50,000 emails a day I would be warming a dedicated IP and re-reading Postmark&apos;s pitch
with more sympathy.&lt;/p&gt;
&lt;p&gt;Balodis is a devil.services product with exactly one distribution channel: being my
client. You cannot sign up for it, and that is deliberate. The safety model (one
shared account, per-tenant breakers) only works because I control every sender on
it, so it carries mail for the sites I build and run, and nothing else. If I run
your site, your password resets ride the pigeon. For everyone else the math is
public and the sliders are up there: if your email bill has a per-domain line in
it, you now know what the pigeon knows.&lt;/p&gt;
</content:encoded><category>email</category><category>amazon-ses</category><category>postmark</category><category>build-vs-buy</category><category>self-hosted</category><author>Ivars Gmizo</author></item></channel></rss>