Writing
Notes from the work: decisions, trade-offs and the occasional mistake, written up so I don't have to relearn them. Each post is grounded in a system I built or run.
2026
Idempotent batch APIs with an external_id
Designing a certification platform's provider API so retried batches never duplicate certificates and partners can integrate without talking to us.
A channel taxonomy in one CSV
The most useful thing to come out of a dbt spike was fifteen lines of CSV: one place where every vendor's channel name maps to ours.
A week with dbt Core and its sharp defaults
A time-boxed spike to decide whether dbt fits our silver-to-gold layer: it did everything asked, and showed how easily it produces silently wrong data.
Test your row-level security as a non-superuser
RLS tests that run as the postgres superuser prove nothing. How the Intelect test suite exercises the policies for real, and what that costs.
Two layers of tenant isolation: ORM hooks and Postgres row-level security
How a multi-tenant CRM on one shared PostgreSQL database keeps tenants apart twice: once in SQLAlchemy, once in the database itself.
Migrating a WordPress site to Postgres without running MySQL
Two education platforms moved from WordPress to FastAPI and Postgres 16. The migration pipeline is reproducible, keeps a safety net, and lets old passwords keep working.
Secrets hygiene for a small platform
The written rules InkPigeon follows for passwords, API keys, DKIM keys, logging, configuration and migrations, and why they are rules rather than habits.
Where port 25 works: hosting an email platform in 2026
Most cloud providers will not let you send mail on port 25. How that constraint shaped where and how InkPigeon runs.
Two privilege escalations I shipped, and the rules that came out of it
InkPigeon's custom-roles feature passed its tests and still had two ways to escalate privileges. What a dedicated adversarial pass found, and the rules it produced.
The mail queue lives in PostgreSQL
InkPigeon's delivery workers claim messages with FOR UPDATE SKIP LOCKED instead of a Redis or Celery queue. The reasoning and the trade-off.
A modular monolith on one VPS
InkPigeon is one Python codebase deployed as about eight containers that scale independently. Why that beats microservices on a single server.
Build or adopt: why InkPigeon doesn't run on Mailu or Postal
I evaluated five open-source mail products before building InkPigeon's own pipeline. Here is what each one is, and why only two components survived.
AI coding sessions need a standards file
I use AI coding assistants heavily. The thing that keeps them from drifting is not a better prompt but a written standards file that outranks the model.
The cutover playbook: swap a base URL, add a pipe, prove it on auth first
How we move frontend modules from legacy endpoints to a new backend one constructor at a time, and why the first cutover is the one that matters.
Deleting endpoints safely: a code audit is not enough
Seven groups of old API endpoints have zero callers in our code. We still are not deleting them until the access logs agree.
Replacing a five-year rewrite with a bounded migration
Our plan to migrate all 18 modules to a new architecture would have taken 3.5 to 6 years. Here is how we replaced it with a target the company could actually reach.
Who owns which layer: Python, dbt and Celery
Before choosing a transform tool for our ETL v2 platform, we fixed which layer each component owns. The boundaries outlived the tool decision.
Idempotent ingestion: run IDs, file hashes and row hashes
How the bronze layer of our ETL v2 design makes every load safe to re-run, and why a data contract has to exist before the first table does.
A security gate that doesn't block delivery
How I built our GitHub Actions staging pipeline to fail on security findings by default while letting a reviewer approve a deployment with a record.
One hotel, five identifiers: reconciling identities across vendor APIs
Why the hardest problem in a multi-source analytics platform is agreeing on which hotel a row belongs to, and how we made that explicit.
Grounding LLM reports in your own APIs
How we added LLM-written narrative reports to an analytics platform without letting the model invent a single number.
What a coding agent costs per pull request
Why I built a per-run cost ledger into my coding agent, and how knowing the price of a pull request changed which tickets I give it.
Four tools are enough: designing a bounded coding-agent loop
How I built an agent that turns well-specified tickets into pull requests with four tools, hard limits and a provider abstraction.
Missing data should be a state, not a silence
How the analytics platform I lead turned silent gaps in vendor data into tracked states with monitors, triggers and automatic resolution.
Running periodic vendor syncs with Celery beat and a database scheduler
Why the analytics platform I lead keeps its sync schedules in PostgreSQL rather than in code, and what went wrong before it did.