July 18, 2022
I performed a bit of high-pressure grape surgery this week with exactly one production issue (a far cry less than I was worried about slash expecting) — all drafts are now emails. What that means: where there was once a “drafts” table for, well, drafts, and an “emails” table for things that were emails, there is now one single table with a status
enum (draft, sent, in flight, et cetera) combining them.
And with that, one of the last poor architectural decisions of 2016 Buttondown has been erased.
This is one of those technical debt things that is probably hard to glean the value of from outside the codebase. It has become increasingly obvious over the past few years that “drafts” and “emails” should be the same thing:
- The interface for editing a sent-out email is extremely janky because the big, fancy writing interface can only really interact with drafts;
- a common desire is to be able to preview the true web archive of a draft before its published, which is impossible since that rendering pipeline only happens on emails and not drafts;
- adding undo support for publishing drafts was tricky because history had to be tracked across multiple tables;
- and so on.
Each one of these things could be solved with a series of kludges, but my contention for the past few months was that it was the cost of reversing this decision was only going to increase monotonically over the years, and the time to handle it was now.
The PR itself was not bad; around a 500 LOC delta, plus a fifty-line script to backfill the drafts. It helped that the tables were incredibly similar as is (good heuristic: if you’ve got two tables that share 80% of their columnar space and you’re not doing any clever polymorphism shenanigans with your ORM, maybe just combine them!).
I mentioned one production issue, and this was one that’s bitten me a couple hundred times in the past: stale bundles. A number of people have Buttondown open in a tab indefinitely (well, not indefinitely, but for days or weeks at a time) and when I landed the PR to combine the two tables I changed the internal API endpoint as well, meaning folks who tried to save drafts on stale bundles were greeted with a big, scary warning saying “Buttondown cannot save your draft”. And so I got a dozen or so emails informing me of this, but I didn’t realize immediately that this was the issue, so I got to spend a very high-pressure fifteen minutes worrying about breaking drafts on master.
(Fun fact: this general problem was the first thing I worked on when I was at Stripe. I am tempted to rebuild my solution but Stripe’s dashboard gets a lot more traffic than Buttondown’s authorship tools do at this point, and I am not so sure the number of breaking changes I’ll actually be making to these internal endpoints in a given year is worth the investment.)
Anyhoo. I find myself increasingly charmed with how much nicer Buttondown’s codebase has grown over the past two months, and being able to translate these debt paydowns into actual features being shipped has been lovely. In particular, this unlocks some of the gnarlier work of this coming week — rebuilding and modernizing the writing interface, starting by moving the modals into durable URLs.