Applied CartographyEssays, notes, and esoterica from Justin Duke2024-03-18T16:28:04Zhttps://jmduke.comJustin Dukeme@jmduke.comGlobals in Histoire2024-03-17T00:00:00Zhttps://jmduke.com/posts/microblog/globals-in-histoire/<p><a href="https://github.com/histoire-dev/histoire">Histoire</a>, like so many other tools in the Vue ecosystem, is a bit of a neglected younger sibling to <a href="https://storybook.js.org/">Storybook</a> — a little bit uglier, with worse documentation and a couple rough edges, but much more tightly integrated with Vue and Vite. <sup class="footnote-ref"><a href="https://jmduke.com/posts/microblog/globals-in-histoire/#fn1" id="fnref1">[1]</a></sup></p>
<p>One thing that was not particularly obvious with using Histoire was how to declare a global variable. (There are a number of global variables Buttondown uses, but here's a common example: we drop Stripe's public client ID in to the Vue app by vending it in the request.)</p>
<p>Histoire doesn't document this, but the solution is pretty easy:</p>
<ol>
<li>Vite lets you declare globals via the <code>define</code> namespace within <code>vite.config.js</code>.</li>
<li>Histoire lets you add vite overrides specific for histoire within the <code>histoire.vite</code> namespace.</li>
</ol>
<p>Combine these and you get the answer:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> defineConfig <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"vite"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> vue <span class="token keyword">from</span> <span class="token string">"@vitejs/plugin-vue"</span><span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineConfig</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
<span class="token literal-property property">histoire</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token literal-property property">vite</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token literal-property property">define</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token constant">STRIPE_CLIENT_ID</span><span class="token operator">:</span> <span class="token number">123</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// The rest of your vite config.</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Hope this saves you ten minutes of Googling!</p>
<hr class="footnotes-sep" />
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>I know Storybook 8 — released only a few days ago — purports to be better out of the box here, but in my brief time investigating this is not the case. Pulling in React and an entire slew of tools <em>just</em> for snapshotting was a bridge too far for me. <a href="https://jmduke.com/posts/microblog/globals-in-histoire/#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
There's no such thing as a zero-marginal-cost free tier2024-03-16T00:00:00Zhttps://jmduke.com/posts/microblog/no-such-thing-as-zero-margins/<p>In the <a href="https://mostlytechnical.com/episodes/27-the-layoff">latest episode of Mostly Technical</a>, Ian brings up ConvertKit as an example of a company for which <a href="https://convertkit.com/pricing">having a free tier</a> makes more sense than PlanetScale, since there are no marginal costs — you don't need to spin up a large amount of fixed-cost resources for every free customer, and you get those sweet network effects for nothing.</p>
<p>I preface this with the disclaimer that I am confident Ian and Aaron know everything I'm about to say already, and "zero marginal costs" was just a shorthand for "negligible." But, as you might imagine, "free tier for an email service" is a topic near and dear to my heart, and so I must briefly opine.</p>
<p><strong>The truth is, <em>of course</em> there's a cost!</strong></p>
<p>There's the marginal database load; the fractional cost in <em>sending emails</em> (back when Buttondown had a much more permissive free tier, something like 40% of the outgoing volume was for non-paying users — and the transactional cost of sending emails was and is Buttondown's single largest line-item expense!); the customer support burden; the noise and distraction.</p>
<p>This doesn't mean they, or I, or you, should / shouldn't have a free tier. But the free tier is (or should be) a <em>marketing</em> decision, not a product one. <sup class="footnote-ref"><a href="https://jmduke.com/posts/microblog/no-such-thing-as-zero-margins/#fn1" id="fnref1">[1]</a></sup></p>
<p>If you're a conventional SaaS, by offering a free tier you are allocating money (in the form of ops load, compute, and various other things) in order to acquire customers. Whether or not that offering is correct is not subjective or qualitative: it's just like any other avenue of paid acquisition, where you look at CAC and you look at LTV and you look at your runway and you call the shot.</p>
<p>When I cut the knees off of Buttondown's free tier, it wasn't because I hated free users and it wasn't because I thought they weren't particularly valuable. It was because the napkin math showed that the cost of the free tier had begun to outweigh its return.</p>
<hr class="footnotes-sep" />
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>Or at least insofar as every marketing decision is a product one and vice-versa. <a href="https://jmduke.com/posts/microblog/no-such-thing-as-zero-margins/#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
Notes on Zed2024-03-10T00:00:00Zhttps://jmduke.com/posts/microblog/zed/<p>I was late to the VS Code zeitgeist, and as penitence I try to go out of my way to try new editors whenever I see them — which is why this morning I installed <a href="https://zed.dev/">Zed</a>, which makes its bones on performance (yay!) and teams functionality (irrelevant for my use cases, but seems abstractly fertile.)</p>
<p>First: it is <em>quite</em> fast, and feels good in many of the ways VS Code does not as a first-run experience. I like almost all of the choices they made (having spent a few hours working in it), and going back to VS Code feels <em>worse</em> to the extent that interface lags and rough edges to which I was previously inured feel newly painful.</p>
<p>That being said: I am going back to VS Code, because too many things that to me are deeply important to my normal day-to-day engineering workflow (integrated test runner; <a href="https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens">Error Lens</a>; <a href="https://jmduke.com/posts/microblog/kolo/">Kolo</a>) do not have parity in Zed. But if/when they do, I'll be back.</p>
PSA: mess around with Kolo2024-03-03T00:00:00Zhttps://jmduke.com/posts/microblog/kolo/<p>I had bookmarked <a href="https://kolo.app/">Kolo</a> many months ago to try out and finally got a chance to integrate it with Buttondown — a process that I expected to take a couple hours on a lazy Sunday and in fact took ten minutes and three lines of code.</p>
<p>If you have a Django app, I think you should drop everything you're doing and install it right now. It is tremendously, <em>immediately</em>, terrific in a way that I haven't felt about a dev tool in quite some time: modern, ergonomic, immediately valuable, trivial to set up.</p>
<p>So much of my feeling on Django and the Python ecosystem writ large has felt <a href="https://knowyourmeme.com/memes/squidward-looking-out-the-window">Squidward-esque</a> — feeling comfortable with my architectural decisions and comfort with the language while watching Rails, Typescript, and even the PHP ecosystem advance their respective tech trees much more rapidly. This is a rare case where I am <em>delighted</em> to have chosen Python, though I suspect it is only a matter of time until this tool (or at least this genre of tool) is language-agnostic.</p>
Years are magic wands2024-02-27T00:00:00Zhttps://jmduke.com/posts/microblog/years-are-magic-wands/<p>A couple folks wrote in responding to <a href="https://jmduke.com/posts/microblog/vibes-and-years/">Vibes and years</a> asking how I did annual planning.</p>
<p>I start with a question: "if I had a magic wand, what things would I want to change about the business?" My answers can be concrete or abstract; they can be lofty or object-level. They just have to be the right answers, and they have to be honest.</p>
<p>For instance, when I went through this exercise in December the first few answers that came to mind were:</p>
<ul>
<li>I'd make it so that there's an actual top-of-funnel system to drive user acquisition, instead of the "write a bunch of content and hope that it works" system that's been the watchword thus far.</li>
<li>I'd get rid of the janky editor and replace it with something better.</li>
<li>I'd have a magically evergreen series of walkthrough videos.</li>
</ul>
<p>And so on.</p>
<p>Once I have a list of answers that feel <em>correct</em>, I shape them into concrete deliverable projects:</p>
<ul>
<li>Build a top-end marketing engine that is self-documented and legible enough to hand off to a talented writer, with clear analytics and lifecycle emails.</li>
<li>Rewrite the core editor abstraction.</li>
<li>Plan out a list of sixty walkthrough videos and find a freelancer who can execute on them.</li>
</ul>
<p>You get the idea.</p>
<p>There's no real shaping or scoping or sizing or any of that — the exercise is to get the project into a single sentence that is clear and falsifiable. Then it becomes a project for the year.</p>
<p>That's it!</p>
<p>I think it's easy to underestimate how long a year is, and how much good work — when it's focused wisely and tactically — can be done. Even now, a "bad" week in terms of technical productivity means only fifteen or so hours of flow; extrapolate that out to a year, and that's 750 hours. Buttondown is not large; there are very few projects that could not be accomplished within 750 hours.</p>
<p>Giving yourself permission to devote yourself to a task is a superpower.</p>
Vibes and years2024-02-24T00:00:00Zhttps://jmduke.com/posts/microblog/vibes-and-years/<p><a href="https://twitter.com/xhfloz/status/1760671916627734811">XH</a> asks:</p>
<blockquote>
<p>How do indie developers/small teams keep track of and prioritize long-term roadmaps?</p>
<p>I've been basically work off my gut + Feeling of the Day for the past few years, and that's getting a bit unsustainable</p>
</blockquote>
<p>Buttondown's roadmapping has existed for the past three years in a bimodal fashion:</p>
<ol>
<li>In November—December, I spend a lot of active and passive time thinking about what the most important <em>big</em> investments I need to make are. By design, these tend to be infrastructural, ambitious, and nebulous — "introduce localization", "rebuild analytics pipeline", "re-design email editor." These are <em>never</em> quantitative/measurable; I've never found metrics-based business goals useful in the context of Buttondown. <sup class="footnote-ref"><a href="https://jmduke.com/posts/microblog/vibes-and-years/#fn1" id="fnref1">[1]</a></sup></li>
<li>I keep a <a href="https://github.com/buttondown-email/roadmap/issues">big, gnarly, public roadmap</a> and pop things off the roadmap as I deem fit, in response to various exogenous factors (what I find interesting to work on in a given day; what customers have flagged recently; what strikes me as particularly time-sensitive.)</li>
</ol>
<p>Of the time I spend working as an engineer, probably 40% of my time is spent on the big ambitious stuff and 60% of my time is spent on the reactive/vibes-based stuff. This distribution should probably be closer to 50-50, if I'm being honest, but it works well for my goals.</p>
<p>IMHO: roadmaps with horizons greater than a year are only useful in large organizations where you need long-term resource planning and financial allocation. (Even then, they're mostly onanistic: every single 3—5 year plan that I've worked on has become consigned to history by Year 2.)</p>
<p>Conversely, roadmaps with horizons <em>less</em> than a year incur a productivity tax in exchange for legibility. Sometimes that tax is worth paying (coordinating comms with writers; working on multiple large projects in parallel with other engineers); right now, it's not worth paying for Buttondown.</p>
<p>Metrics, roadmaps — all of these things are tools, and often they can point you in a given direction in exchange for a cost in time and/or money. Vibes cost nothing; they might not be perfectly accurate, but I think your most important job as an early founder is to have a preternaturally strong sense of what direction to go in absent external stimuli.</p>
<p>("Why have an annual plan at all, if vibes are so useful?", you might ask. Because I was never a two-marshmellow child, and I am personally quite bad at keeping important-but-not-urgent work top-of-mind. It's hard for me to wake up and suddenly think "hm, maybe this is the day to start rebuilding the editor", and the ritual of committing to that kind of project for a year keeps me from spending all my time snacking on lower-ROI but more easily-actionable work.)</p>
<hr class="footnotes-sep" />
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>I find metrics-based goals very useful for operational or performance-related tasks (drive p90 response time for /v1/subscribers down to one second), though. <a href="https://jmduke.com/posts/microblog/vibes-and-years/#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
You should use Helpscout2024-02-18T00:00:00Zhttps://jmduke.com/posts/microblog/helpscout/<p>Was digging through old issues of the personal site and found this draft snippet:</p>
<blockquote>
<p>I was evaluating HelpScout as a potential first step in a series of many, <em>many</em> steps to onboard a dedicated support engineer for Buttondown, and it turns out that the process of adding custom content to a given conversation (ie the Stripe information, account details, and most recently sent email, that kind of thing) is <em>remarkably</em> easy. I think I am conditioned to assume pain when I think of the intersection of "customer support software" and "loading content remotely onto a separate SaaS", but it's actually tremendously evening: stand up a single route that takes a POST request and returns HTML, and HelpScout takes care of the rest.</p>
</blockquote>
<p>I wrote that in 2022. Two years later, I could not be more thrilled with the decision to use Helpscout and put
data into the app to make my support team's life easier. They've done a great job making it easy to put data in without going "all in" on using Helpscout (unlike other helpdesk suites which try very aggressively to get their tenterhooks in ya.)</p>
11ty2024-02-18T00:00:00Zhttps://jmduke.com/posts/microblog/11ty/<ul>
<li><code>.njk</code> as the default templating language is an odd choice, and I find myself stubbing my toe on it a good amount. Maybe that's a me thing!</li>
<li>It is <em>extremely</em> fast. This site has around 2300 pages; Eleventy is compiling it in around two seconds.</li>
<li>The extensibility ergonomics are terrific. This is all it takes to declare a new filter, for instance:</li>
</ul>
<pre class="language-js"><code class="language-js">eleventyConfig<span class="token punctuation">.</span><span class="token function">addFilter</span><span class="token punctuation">(</span><span class="token string">"toStars"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">rating</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token string">"★"</span><span class="token punctuation">.</span><span class="token function">repeat</span><span class="token punctuation">(</span><span class="token punctuation">(</span>rating <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<ul>
<li>I can keep the Markdown content relatively pure, which (after poor and brief dalliances with <code>.mdx</code> et al) is now Very Important to me.</li>
</ul>
Buttondown Analytics 3.02024-02-11T00:00:00Zhttps://jmduke.com/posts/microblog/analytics-3.0/<p>Working on a new analytics engine — a scant eleven months after <a href="https://buttondown.email/changelog/2023-03-28">the previous 'new analytics engine'</a>.</p>
<p>Calling this <code>3.0</code> is a bit of a misnomer: most of the code, design, and plumbing from the 2023 redesign is sticking around, just in a more modular format. The goal here is to address a couple shortfalls in the previous architecture:</p>
<ul>
<li>Business and presentation logic was split between the backend and the frontend. This made shipping faster, but made it harder to do things like build <em>weekly reports</em> which are calculated entirely from the backend.</li>
<li>Analytics were very tightly coupled in groups of "breakout metrics", which made in-line calculations very quick but makes it harder to incrementally ship <em>new</em> metrics that are useful but don't fall neatly into existing archetypes.</li>
<li>The <em>density</em> of the metrics seemed very nice and virtuous to me, but was hard for folks who just wanted to reach for a few KPIs that they really cared about.</li>
</ul>
<p>The goal of this new tranch of work is threefold:</p>
<ol>
<li>Analytics calculations should exist entirely on the backend, in a modular architecture that makes it trivial to add new calculations and filters over time</li>
<li>Analytics should be <em>portable</em> in a way that makes it easy to embed certain KPIs outside the core analytics page (e.g. in a splash page or in weekly/monthly reporting emails)</li>
<li>Make it easier for folks to answer lifetime-style KPIs</li>
</ol>
<p>It's interesting — it's <em>really</em> hard to find 'good' analytics experiences on the web. One reason I suspect I've had so much design-level churn here (three designs in three years!) is that this feels very much like the wild west, unlike other parts of the app where we've generally settled on what the right designs are for the 80/20 of people.</p>
Gosling’s Old Rum2024-02-07T00:00:00Zhttps://jmduke.com/posts/microblog/old-rum/<p>Virginia has draconian liquor laws, which means I have to get interesting bottles shipped from [REDACTED], a site on which I am very prone to judging a book by its cover; every month I'll end up purchasing a bottle of something purely because it looks interesting and I can vaguely imagine it fitting into a couple cocktails down the line.</p>
<p>This time, it was <a href="https://www.goslingsrum.com/goslings-family-reserve-old-rum/">Gosling Old Rum</a>, a perfectly reasonable thing to have. It is a fine bottle that would be nice value at $40 but not worth the $65 I paid for it; a couple artificial notes of vanilla and caramel do little to rescue what is ultimately a pretty thin and two-dimensional bottle of rum. I'll be able to throw it in a lot of things (I like Gosling's for its versatility, not its quality), but I should have just sprung for some O.F.T.D. instead.</p>
Migrating someone who's on Stripe Connect Express2024-01-13T00:00:00Zhttps://jmduke.com/posts/microblog/express-connect/<p>Most Stripe accounts on Substack are “Standard Connect”, which essentially means that:</p>
<ol>
<li>the author has full agency over their account and is the merchant of record;</li>
<li>they can revoke OAuth access from Substack (or whomever) at any time;</li>
<li>Substack continues to take that 10% as an application fee, though the author (or another connected account) can remove that fee once the OAuth access is revoked.</li>
</ol>
<p>A couple edge cases accounts, though, are “Express Connect” (like Talia!) This means that:</p>
<ol>
<li>the author has full agency over their account and is the merchant of record;</li>
<li>but they <em>cannot</em> eject their account from Substack — the account is irrevocably tied to being a “subaccount” of Substack.</li>
</ol>
<p>For such accounts, though, you can still remove yourself and retain subscriber state/information with two steps:</p>
<ol>
<li><strong>Migrate</strong> customer data from the original account to a new “standard” account using Stripe’s built-in migration tooling;</li>
<li><strong>Export</strong> subscriptions from the original account via CSV, and then recreate those original subscriptions based on the details within the CSV</li>
</ol>
Mass renaming files in fish on macOS2023-12-17T00:00:00Zhttps://jmduke.com/posts/microblog/mass-renaming-files/<p>One of many two-liners to come as I migrate things from the old site onto Obsidian:</p>
<pre><code>brew install rename
rename "s/.mdx/.md/" **.md
</code></pre>
Postgres batch enqueuing in ten lines of Django2023-12-15T00:00:00Zhttps://jmduke.com/posts/microblog/postgres-batch-enqueuing/<p>It hasn't failed me yet:</p>
<pre class="language-python"><code class="language-python">BATCH_SIZE <span class="token operator">=</span> <span class="token number">100</span>
<span class="token keyword">def</span> <span class="token function">batch_proess</span><span class="token punctuation">(</span>queryset<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token boolean">None</span><span class="token punctuation">:</span>
count <span class="token operator">=</span> queryset<span class="token punctuation">.</span>count<span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> count <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">:</span>
time<span class="token punctuation">.</span>sleep<span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span>
<span class="token keyword">return</span>
<span class="token keyword">with</span> transaction<span class="token punctuation">.</span>atomic<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
batch <span class="token operator">=</span> <span class="token builtin">list</span><span class="token punctuation">(</span>
queryset<span class="token punctuation">.</span>select_for_update<span class="token punctuation">(</span>of<span class="token operator">=</span><span class="token punctuation">(</span><span class="token string">"self"</span><span class="token punctuation">,</span><span class="token punctuation">)</span><span class="token punctuation">,</span> skip_locked<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span><span class="token punctuation">.</span>values_list<span class="token punctuation">(</span>
<span class="token string">"id"</span><span class="token punctuation">,</span> flat<span class="token operator">=</span><span class="token boolean">True</span>
<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token punctuation">:</span>BATCH_SIZE<span class="token punctuation">]</span>
<span class="token punctuation">)</span>
<span class="token comment"># Do whatever you need to do with the batch here.</span>
<span class="token keyword">return</span></code></pre>
Use weird tests to capture tacit knowledge2024-03-04T00:00:00Zhttps://jmduke.com/posts/essays/weird-tests-tacit-knowledge/<p>Working on Buttondown — or any mature, complex codebase — effectively and quickly requires a lot of tacit knowledge
that I've done a hitherto-poor job of documenting, a fact I am learning more and more quickly as I start to scale up
the number of folks working on the codebase.</p>
<p><em>Documentation</em> in the literal sense is a good first step and final step, of course, but when a codebase is in the "process" of being documented writing down "this is how you do X" does often not actually <em>solve</em> the problem of making sure everyone can do X safely and quickly.</p>
<p>One thing that I've found useful, in the spirit of shifting process to the left, is capturing steps in tests. Here's a simple (but real!) example: adding new Django modules to the codebase. Whenever you run <code>python manage.py startapp</code>, you <em>also</em> need to add the new app to a bunch of different places:</p>
<ul>
<li><code>pytest.ini</code>, so tests are run;</li>
<li><code>pyproject.toml</code>, so files are linted;</li>
<li><code>modules.txt</code>, so metrics are exported.</li>
</ul>
<p>The <em>perfect</em> solution to this problem is creating a script that automatically adds a new app to all the relevant places and stuffing it into a Justfile, but that's a pretty big piece of work that requires thought and error handling and a whole slew of other stuff. Instead, it's comparatively easy to just capture these constraints in a test:</p>
<pre class="language-python"><code class="language-python"><span class="token comment"># This test suite ensures that, when we create or rename a module,</span>
<span class="token comment"># we update all the relevant configurations so that we lint/test/etc. that module.</span>
<span class="token keyword">from</span> django<span class="token punctuation">.</span>conf <span class="token keyword">import</span> settings
RELEVANT_FILES <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"./pyproject.toml"</span><span class="token punctuation">,</span> <span class="token string">"./pytest.ini"</span><span class="token punctuation">,</span> <span class="token string">"./modules.txt"</span><span class="token punctuation">]</span>
<span class="token keyword">def</span> <span class="token function">pytest_generate_tests</span><span class="token punctuation">(</span>metafunc<span class="token punctuation">)</span><span class="token punctuation">:</span>
parameters <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
<span class="token keyword">for</span> filename <span class="token keyword">in</span> RELEVANT_FILES<span class="token punctuation">:</span>
<span class="token keyword">for</span> module <span class="token keyword">in</span> settings<span class="token punctuation">.</span>BUTTONDOWN_APPS<span class="token punctuation">:</span>
parameters<span class="token punctuation">.</span>append<span class="token punctuation">(</span><span class="token punctuation">(</span>module<span class="token punctuation">,</span> filename<span class="token punctuation">)</span><span class="token punctuation">)</span>
metafunc<span class="token punctuation">.</span>parametrize<span class="token punctuation">(</span><span class="token string">"module,filename"</span><span class="token punctuation">,</span> parameters<span class="token punctuation">)</span>
<span class="token keyword">def</span> <span class="token function">test_module_is_present_in_pytest</span><span class="token punctuation">(</span>module<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">,</span> filename<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token boolean">None</span><span class="token punctuation">:</span>
<span class="token keyword">assert</span> module <span class="token keyword">in</span> <span class="token builtin">open</span><span class="token punctuation">(</span>filename<span class="token punctuation">)</span><span class="token punctuation">.</span>read<span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre>
<p>This approach also works well when you're trying to enforce a norm or invariant for all <em>new</em> code. (At Stripe, we called this approach "ratchet testing", though initial Googling seems to indicate that this metaphor has not exactly spread like wildfire.)</p>
<p>Another example: Buttondown uses Django-Ninja to generate an OpenAPI spec from the live API. OpenAPI is great, but
it <a href="https://github.com/OAI/OpenAPI-Specification/issues/348">sadly lacks an ergonomic ability to document each value of an enum</a>, so we maintain a separate <code>enums.json</code> file that needs to be updated whenever a relevant enum has a new addition — even though some enum values are undocumented!</p>
<p>A similar approach works well here:</p>
<pre class="language-python"><code class="language-python"><span class="token keyword">import</span> json
ENUMS_FILENAME <span class="token operator">=</span> <span class="token string">"../shared/enums.json"</span>
OPENAPI_SPEC_FILENAME <span class="token operator">=</span> <span class="token string">"assets/autogen/openapi.json"</span>
RAW_ENUMS <span class="token operator">=</span> json<span class="token punctuation">.</span>load<span class="token punctuation">(</span><span class="token builtin">open</span><span class="token punctuation">(</span>ENUMS_FILENAME<span class="token punctuation">)</span><span class="token punctuation">)</span>
RAW_OPENAPI_SPEC <span class="token operator">=</span> json<span class="token punctuation">.</span>load<span class="token punctuation">(</span><span class="token builtin">open</span><span class="token punctuation">(</span>OPENAPI_SPEC_FILENAME<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token keyword">def</span> <span class="token function">pytest_generate_tests</span><span class="token punctuation">(</span>metafunc<span class="token punctuation">)</span><span class="token punctuation">:</span>
parameters <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
enums <span class="token operator">=</span> RAW_ENUMS<span class="token punctuation">.</span>keys<span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">for</span> enum_name <span class="token keyword">in</span> enums<span class="token punctuation">:</span>
extant_enum_values <span class="token operator">=</span> RAW_OPENAPI_SPEC<span class="token punctuation">[</span><span class="token string">"components"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"schemas"</span><span class="token punctuation">]</span><span class="token punctuation">[</span>enum_name<span class="token punctuation">]</span><span class="token punctuation">[</span>
<span class="token string">"enum"</span>
<span class="token punctuation">]</span>
<span class="token keyword">for</span> enum_value <span class="token keyword">in</span> extant_enum_values<span class="token punctuation">:</span>
parameters<span class="token punctuation">.</span>append<span class="token punctuation">(</span><span class="token punctuation">(</span>enum_name<span class="token punctuation">,</span> enum_value<span class="token punctuation">)</span><span class="token punctuation">)</span>
metafunc<span class="token punctuation">.</span>parametrize<span class="token punctuation">(</span><span class="token string">"enum_name,enum_value"</span><span class="token punctuation">,</span> parameters<span class="token punctuation">)</span>
<span class="token comment"># Do not add more items to this ratchet unless you need to!</span>
KNOWN_MISSING_PAIRS <span class="token operator">=</span> <span class="token punctuation">[</span>
<span class="token punctuation">(</span><span class="token string">"CreateSubscriberErrorCode"</span><span class="token punctuation">,</span> <span class="token string">"metadata_invalid"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">(</span><span class="token string">"ExternalFeedAutomationCadence"</span><span class="token punctuation">,</span> <span class="token string">"daily"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">(</span><span class="token string">"UpdateSubscriberErrorCode"</span><span class="token punctuation">,</span> <span class="token string">"email_already_exists"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token comment"># ... and so on.</span>
<span class="token punctuation">]</span>
<span class="token comment"># This technically does not exercise Python code; it's testing that `shared/enums.json` is up to date.</span>
<span class="token keyword">def</span> <span class="token function">test_enum_is_exhaustively_documented</span><span class="token punctuation">(</span>enum_name<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">,</span> enum_value<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token boolean">None</span><span class="token punctuation">:</span>
<span class="token keyword">assert</span> <span class="token punctuation">(</span>
enum_name <span class="token keyword">in</span> RAW_ENUMS
<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string-interpolation"><span class="token string">f"Enum </span><span class="token interpolation"><span class="token punctuation">{</span>enum_name<span class="token punctuation">}</span></span><span class="token string"> is not documented in </span><span class="token interpolation"><span class="token punctuation">{</span>ENUMS_FILENAME<span class="token punctuation">}</span></span><span class="token string">"</span></span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>enum_name<span class="token punctuation">,</span> enum_value<span class="token punctuation">)</span> <span class="token keyword">in</span> KNOWN_MISSING_PAIRS<span class="token punctuation">:</span>
<span class="token keyword">return</span>
<span class="token keyword">assert</span> <span class="token punctuation">(</span>
enum_value <span class="token keyword">in</span> RAW_ENUMS<span class="token punctuation">[</span>enum_name<span class="token punctuation">]</span>
<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string-interpolation"><span class="token string">f"Potential value </span><span class="token interpolation"><span class="token punctuation">{</span>enum_value<span class="token punctuation">}</span></span><span class="token string"> of enum </span><span class="token interpolation"><span class="token punctuation">{</span>enum_name<span class="token punctuation">}</span></span><span class="token string"> is not documented in </span><span class="token interpolation"><span class="token punctuation">{</span>ENUMS_FILENAME<span class="token punctuation">}</span></span><span class="token string">"</span></span></code></pre>
<p>What I find <em>most</em> lovely about this approach is that test-driven invariants are self-documenting. A task like "adding a new value to an existing enum" is not obviously a thing that should require searching an internal knowledge base, but a test that captures information about it can contain code pointers, technical explanation, <em>and</em> a way to fix it.</p>
<p>In general, a good mental exercise whenever you're reviewing a PR is "could a test have caught this?", and then reminding yourself that a test should be defined less as "a thing that exercises business logic" and more as a "script that exercises your codebase".</p>
Scattered thoughts on buying software businesses2024-01-17T00:00:00Zhttps://jmduke.com/posts/essays/buying-software-businesses/<p><a href="https://thirdsouth.capital/">My friends and I</a> bought five software businesses last year, on the thesis that:</p>
<ol>
<li>despite the recent uptick of interest in the space, it was still a fairly undervalued class of asset;</li>
<li>it would be kind of fun and neat.</li>
</ol>
<p>We purchased these businesses after around eight months of sourcing and searching; we've been operating them for a little less than half a year.</p>
<p>A lot of people have asked a lot of questions about almost every part of the process: sourcing, negotiating, closing, operating. Rather than actually be <em>useful</em> and write a two-thousand word screed on our tactics in finding and closing deals that we liked, I thought it would be more interesting to jot down a list of answers to the following question:</p>
<p><strong>What do we know now that you didn't know a year ago?</strong></p>
<hr />
<ul>
<li>There are no perfect businesses.</li>
<li>There might be a perfect business <em>for you</em>, but that mostly depends on your definition of perfect. It's important to figure out what your level of time and resource commitment is, and you can walk that back into deal size and operational threshold.</li>
<li>When a seller says "this takes me one or two hours a day to deal with", it means it will take three times that amount for you; even if they're being genuine, they've got a lot of time and experience and muscle memory that you don't.</li>
<li>Every business is going to have some sort of looming existential risk: maybe it's platform risk, maybe it's well-financed competitors, maybe it's passwords stored in plaintext. Your goal is not to find a business without risk; your goal is to find one whose risk profile matches your appetite. (If you don't have <em>any</em> appetite for risk, you should not buy a software business — or any business.) If any of these businesses passed every single heuristic on a Berkshire-style checklist, it probably wouldn’t be for sale.</li>
<li>You should have at least <em>one</em> compelling thesis as to how you can improve the business relative to the current owners.</li>
<li>Assuming the business is otherwise in solid health, the failure case for your acquisition is much less likely to be "something fundamentally broke this business and it zeroes overnight" and more likely to be "I no longer want to run or operate this business and it either dies slowly or I sell for a loss."</li>
<li>You should adjust up/down a company's acquisition and retention numbers based on your rough estimate of their hustle: an otherwise unsticky business might have great churn numbers not due to the underlying asset but because the owner is particularly good at hustling. (The inverse can be true; some owners are so checked out of the business that you can meaningfully bend its trajectory by answering emails within two business days.)</li>
<li>Letters of intent are cheap, especially on free-for-all platforms like Acquire. (In general, the online two-sided marketplaces have a supply problem; they're good to browse and to develop some muscles around high-level characteristics, but brokerages like Quietlight and FE International have a much better signal-to-noise ratio.)</li>
<li>There is a tremendous amount of signal in the reason <em>why</em> sellers are selling their business.</li>
<li>Similarly, if a business has been sitting out on the market soliciting offers for more than a few months at what looks like a reasonable valuation — there’s a reason why.</li>
<li>Financial engineering gets a bad rep, and is a good way to defray risks. Worried about significant platform risk? Add a two-year seller note! Think the business has a huge amount of knowledge transfer required? Add a twelve-month consulting retainer!</li>
<li>Ask for usage data — as much of it as you can get. Try and get a pretty solid number for "what percentage of revenue is actually using this product?"</li>
<li>Ask for pricing strategy, and in particular how much potential energy there is in the business for pricing experimentation. Some owners haven't touched prices in a decade; others have slyly moved up prices recently in an effort to make the business more attractive.</li>
<li>First-time buyers and first-time sellers alike underestimate the value of legible financial systems and a clean set of books. A fractured set of accounting statements might not sink a deal, but they can make it <em>much</em> harder to quickly close.</li>
<li>Sourcing is really tough, and "tough" shifts from "there's a whole lot of volume and a whole lot of data and I'm not sure how to make decisions about what is or isn't interesting" to "there's absolutely no deal flow for the shape of business that I'm interested in". The market for these businesses is still fairly illiquid, and it can take many months to find businesses that are worth putting serious effort into.</li>
<li>Sellers keep tons of information in their head: abandoned marketing ideas, undocumented operational issues, one-off lifetime deals, problematic customers, launched-but-underused features, incoming annual renewals, basic support steps, basic customer relationships. Get as much of this into a durable artifact (Loom recording, plaintext, whatever) as you can.</li>
<li>Price is downstream of deal quality, not vice-versa. A business that may be in many respects "bad" (no obvious marketing channels, relatively high churn) could still be a worthwhile acquisition at 2.3X EBITDA, and we never regretted being forthright with our reasons for valuing a business the way we did when pitching sellers on a low valuation.</li>
<li>The activation energy required to do <em>anything</em> in the first few months post-close is much higher than you expect.</li>
<li>In general, it's easier to do work on the marketing, ops, and pricing side of an acquired business than on the product side. Accordingly, be wary of businesses that purport to require a lot of product work to maintain their current position.</li>
<li>Platform risk is also, for lack of a more clever phrase, platform reward. Having a growing platform to take care of GTM for you can make up for a lot of deficiencies.</li>
<li>Cold sourcing potential businesses sounds like searching for needles in a haystack (and it is), but any potential sellers you come across will have <em>much</em> less competition than ones who are selling through a brokerage or marketplace.</li>
<li>Contractors who you trust and like are worth their weight in gold, and much harder to find than you might otherwise imagine.</li>
<li>There are few better ways to spend time and energy during diligence than dog-fooding the product for a few hours.</li>
<li>Not all payment providers are created equal.</li>
<li>At the end of the day, you’re buying a job (even if it’s one with low hours!) — so make sure that you’re buying a job that you find either fun or worthwhile.</li>
</ul>
<hr />
<p>(Thank you to Colin and Harrison for — well, for many things, but in particular for reading and improving this essay.)</p>
<p>If you have any questions — or are interested in selling us your software business — feel free to reach out at <a href="mailto:hi@thirdsouth.capital">hi@thirdsouth.capital</a>.</p>
20232023-12-30T00:00:00Zhttps://jmduke.com/posts/essays/2023/<p>(You may be interested in <a href="https://arcana.computer/years/2022">last year's annual review</a>.)</p>
<h2>Health</h2>
<p>I wrote last year:</p>
<blockquote>
<p>I am, as I write this, in the best shape of my life</p>
</blockquote>
<p>In retrospect, this was:</p>
<ul>
<li>a hilarious way to start an essay</li>
<li>true, and no longer so.</li>
</ul>
<p>I committed to hitting the 1/2/3/4 club this year, which I accomplished (yay!), but shortly thereafter I fell into what I would describe as a pleasant fitness malaise: my only real goal was improving at bouldering, which I didn't <em>quite</em> commit myself to enough to hit my resolution of sending a V6 route. As such, I mostly spent the year in maintenance mode: jogging, lifting, climbing, and staying active, albeit not quite as much as I should have.</p>
<p>The past two months have been better! I picked back up a lifting routine that, while brutal, has always been effected (<a href="https://apps.apple.com/us/app/nsuns-5-3-1/id1265494052">nSuns</a>, for those curious) and have been more nutritionally conscious than usual (we're generally pretty good about eating healthy in the house, though there were a few too many cocktails and burgers than there probably should have been.) <sup class="footnote-ref"><a href="https://jmduke.com/posts/essays/2023/#fn1" id="fnref1">[1]</a></sup></p>
<p>Beyond that: no news is good news.</p>
<h2>Family</h2>
<p>Haley and I got married! It was the best day of my life.</p>
<p>I think almost everything that can be said about weddings has already been said <sup class="footnote-ref"><a href="https://jmduke.com/posts/essays/2023/#fn2" id="fnref2">[2]</a></sup>, but a thing that you can only appreciate <em>while it's happening</em> is just that one possible version of a wedding — our version of a wedding — is spending a full day with your favorite people in the entire world in a very beautiful place, and while such a description always made a certain level of <em>abstract</em> sense to me I don't think it quite hits you, <em>really</em> hits you, until you realize it is suddenly six p.m. and everyone who you care about in life is in the same room as you, well-dressed and flush-faced.</p>
<p>The only absent member of the family at our nuptials was, of course, <a href="https://telemachus.dog/">Telemachus</a>, who spent the weekend at a farm with his brothers and sisters. I feel as though I have run out of ways to describe Telly: he is a perfect dog, strange and soft, filled with the kind of love and loyalty only canines can provide.</p>
<p>Being married is not particularly different than being engaged; I wake up every morning and go to bed every evening next to my best friend and favorite person in the world. I get to it now with a ring on my finger; all I hope for is to never be so dumb as to take it — even for an instant! — for granted.</p>
<h2>Buttondown</h2>
<p>There's a slightly more self-serving <a href="https://buttondown.email/blog/2023">version of this on the official Buttondown blog</a> but — <em>man, what a year.</em></p>
<p>Buttondown's functionality doubled and the core experience got even stronger; I hired part-time writers, engineers, and support staff to scale out its growth; it suffered fewer bouts of downtime or serious incidents than the year prior; every single financial metric improved.</p>
<p>The users of Buttondown now include columnists at the Washington Post, New York Times, and The Atlantic; winners of the Hugo, of the Pulitzer, of the Apple Design Award; bloggers I've read for decades; maintainers of Open Source Software used by millions; publishers whose games I've played for hundreds of hours; poets, artists, high schoolers, jazz troupes; Ivy League departments and Mutual Aid organizations; skeeball, pickleball, and bouldering leagues.</p>
<p>It has officially grown beyond my wildest dreams, and done so while hitting a 99.8% CSAT score.</p>
<p>More than that, it remains a business of which I can carry a high level of moral pride; it is a reflection of my ideals about what software can do and how a business can be run.</p>
<p>All of which either underlines or belies a larger point: this is easily the hardest I've worked in my professional life. I am grateful that all of this hard work is for a project I care so deeply about, and for a project whose users find it so valuable.</p>
<p>My goal entering this year was an amorphous thing that I had informally referred to as "Buttondown 2.0":</p>
<blockquote>
<p>In 2023, I want to — need to — transition my relationship with Buttondown from that of a successful project to a stable, growing, mature business.</p>
</blockquote>
<p>Mission accomplished.</p>
<p>The question that the industry trains us to ask is: <strong>what's next?</strong> I think that's obvious from a product perspective but a little less so from an organizational or business one: but my high-level goal for next year is to make a full-time hire, and all of the financial/existential commitments entailed therein.</p>
<h2>Third South Capital</h2>
<p>My friends and I <a href="https://thirdsouth.capital/">started a holding company</a>. We purchased five businesses, and it's gone pretty well; they've all grown, and we're going to make another purchase or two next year.</p>
<p>Our goal with this first year was, more than anything else, to accept or reject the null hypothesis: regardless of financial return, was this a fun and interesting way for us to spend our limited time and energy? The answer has been <em>yes</em>. The three of us all have different backgrounds and are all getting different things out of it: for me, I'm loving the intellectual exercise of being able to evaluate and implement strategies on businesses with which I have slightly less emotional attachment. <sup class="footnote-ref"><a href="https://jmduke.com/posts/essays/2023/#fn3" id="fnref3">[3]</a></sup></p>
<p>I haven't written or posted too much about this experience (in no small part because it's not just my experience to share!) but it has been <em>much</em> more rewarding than I expected, and expect more writing on this front in 2024.</p>
<h2>Writing</h2>
<p>I wrote 22,000 words in a personal blogging capacity last year; I know not what that number was for 2023, but I am very confident it was much less. I am <strong>very</strong> proud of my more serious essay-writing over at <a href="https://www.applied-cartography.com/">Applied Cartography</a>, even if it was few and far between. (And <a href="https://arcana.computer/">arcana.computer</a> remains alive, albeit in stasis — I'm still keeping my media journals updated, but MDX is now so slow at processing my thousands of files that builds are broken. I will try to fix this in 2024, though I'm not quite sure what form a "fix" will take.)</p>
<h2>Media</h2>
<p>My reading this year was fairly flat (34 books, compared to 32 last year); my gaming was down (5 games, compared to 7 last year); my films were way up (53, compared to 25 last year!). Some favorites across the entire spectrum:</p>
<ul>
<li><strong>The Conversation</strong> and <strong>The Green Knight</strong> were two <em>sensational</em> films that belong in everyone's canon;</li>
<li><strong>Baldur's Gate 3</strong> is just as good as everyone says it is (and I say that despite having not finished it!); <strong>13 Sentinels: Aegis Rim</strong> is a triumph of postmodernism.</li>
<li>I was very close to having not read a single book that I truly <em>loved</em> this year until I finished up — only last week — <strong>The Name of the Rose</strong>, which is now one of my favorite ten books of all time, and one whose many messages and ideas will be roiling around in my head and heart for many years to come.</li>
</ul>
<h2>Miscellany</h2>
<ul>
<li>I spent two thirds of the year ending every evening by spending fifteen minutes on some quantified self work: where I spent my time, how many drinks I had, how much caffeine I had, etc., with a goal of making sure I wasn’t going to burn myself out or otherwise ruin my health. I think I gleaned some amount of insight from this (I realized that I was not spending enough time on non-Buttondown things; I caught myself when I was drinking on more weekdays than I wanted to) but I abruptly stopped this practice in September and am no worse for the wear.</li>
<li>Feels deeply weird to, for the first time in ~ten years or so of doing this, not have a section dedicated to some external employer.</li>
<li>I find my relationship with social media in a generally good-but-frustrating place. The town square of Twitter has been replaced with six different fora that I need to keep tabs on — bsky, Discord, LinkedIn, Signal, Mastodon, Twitter — and the sheer frustration of doing so has resulted in me spending less time in consumptive mode.</li>
<li>I find myself missing not just a town square but a water cooler. I am grateful to my friends Sumana and Harrison for providing some palliative care in this nature: having coworking and mastermind sessions to break up the week and connect with others is extremely gratifying.</li>
<li>No new projects; I don't foresee any others in the coming year as well (much to my chagrin).</li>
<li>Emojis on the rise in 2023: 🫡, 🐬, 🍳.</li>
<li>Emojis on the decline in 2023: 😎, 🙏, 😅.</li>
<li>This was our first full year living in Richmond, and while we didn't spend as much time exploring it and becoming more entrenched in the culture as we would have liked (this is perhaps reasonable, given the aforementioned wedding and honeymoon and general sense of happy chaos that permeated the year) we love it more than ever. We've found our neighborhood bar, we've found (and lost) our favorite restaurant and cidery, we've re-found a favorite restaurant, we re-finished our backyard and in general have done a lot of fun stuff. It is a great privilege to have a home you love.</li>
</ul>
<h2>Resolutions</h2>
<ul>
<li>Bench three plates.</li>
<li>Publish twenty essays.</li>
<li>Open-source four major components of Buttondown.</li>
<li>Remodel my garage.</li>
</ul>
<h2>Coda</h2>
<p>For the past few weeks I've been mulling over an essay with the working title of <em>Something's Gotta Give</em>, and indeed that feels like the main lesson I learned this year. I wrote last year that I felt like I was entering 2023 with a clarity of purpose that had been lacking in my professional life for some time; that clarity has given way to lots of great work, many happy memories, and a sidelining of things whose importance or urgency feels marginal.</p>
<p>The median day of 2023 looked something like this: eight hours of sleep, a good workout, ten hours of exhausting intellectual work growing a business, some puttering around the house and tending the garden, two dog walks, an evening spent unwinding with my wife, an episode of anime with my brother. There are a lot of things I wish I could fit into that day: more time writing, more time exploring new technologies, more time playing video games, more time exploring Richmond. At the same time: I think I chose wisely.</p>
<p>2024 purports to be much of the same. If there's any change I could make, it'd be making sure that some of that more passive time — the sixty quiet minutes on the couch after dinner, the thirty minutes between lunch and an afternoon meeting — is spent more mindfully, as opposed to picking up a random round of Brotato and/or brainlessly refreshing analytics.</p>
<h2>Thank you</h2>
<p>To: my wife, Haley, the light of my life; to everyone who attended our wedding; to my parents and my brother, for too many reasons to enumerate; to Harrison and Colin, the best business partners a guy could ask for (and for five lovely and earnest counterparties who entrusted us with their businesses); to everyone who became a Buttondown customer this year (or reported a bug, or referred a user, or pushed code); to the hodgepodge of former coworkers, emoji-filled Discords and group-chat emigres whom I consider a water cooler in lieu of an employer-sponsored Slack; to Jimmy Butler and Zach Lowe; to you, dear reader, for getting this far.</p>
<hr class="footnotes-sep" />
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>On the cocktail front: this is the first year where it's been <em>aggressively</em> obvious how much a single drink will hit my energy for the following day or two. I know teetotaling is a bit of a meme at the moment (and it's certainly not something I'm advocating for), but I've had to really budget my energy with the knowledge that a happy hour or a late dinner with drinks means I won't be nearly as close to 100% the following day. <a href="https://jmduke.com/posts/essays/2023/#fnref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><p>Though keen readers of my writing would quickly note that such a fact has never stopped me from bloviating anyway! <a href="https://jmduke.com/posts/essays/2023/#fnref2" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn3" class="footnote-item"><p>Buttondown is, more than anything else, my baby: there are <em>tons</em> of "correct business decisions" that I could make with Buttondown that I refuse to out of emotional attachment. <a href="https://jmduke.com/posts/essays/2023/#fnref3" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
What Elephants Taste Like2023-10-31T00:00:00Zhttps://jmduke.com/posts/essays/elephants/<p>When I wrote <a href="https://www.applied-cartography.com/goon-squad">Befriending the Goon Squad</a> — an essay on the importance of patience and discipline when building a product — I received dozens of variations of the following question:</p>
<blockquote>
<p>So how do you <em>stay</em> disciplined? How do you avoid getting distracted by new ideas or dismayed by bad weeks, even when you only have a few users? <a class="absolute right-3 bottom-3 no-underline not-italic text-orange-700">Various people</a></p>
</blockquote>
<p>I think the reason this question is so common is because people incorrectly assume the launch to be "the hard part", largely due to misconceptions perpetuated by the zeitgeist. As far as I can tell, there are two prevailing stereotypes of what building a company "is really like":</p>
<ol>
<li>It is the easiest and most rational decision ever, a path to economic freedom and personal fulfillment paved with bottles of champagne and hockey-stick MRR charts. The only thing stopping you is gumption, courage, and — conveniently! — a $199.99 info product that the author happens to be hawking.</li>
<li>It is like <em>chewing glass</em>, a gauntlet of horrors and difficulties equally Sisyphean and Herculean, an unending cavalcade of eighteen-hour days that ravage your body and devour your mind. We must honor the company-builder, for theirs is a plight (and therefore glory) unparalleled.</li>
</ol>
<p>Neither of these are accurate: life as a founder <sup class="footnote-ref"><a href="https://jmduke.com/posts/essays/elephants/#fn1" id="fnref1">[1]</a></sup> is long and difficult, sure, but not in the ways that tend to make for good hagiography, and the journey <em>begins</em> (rather than ends) with the initial launch.</p>
<p>Your day is neither as glamorous as margaritas on the beach nor as dire as a never-ending sharknado storm. It's mostly stuff like:</p>
<ul>
<li>grappling with the right approach for modeling permissions for multi-team users;</li>
<li>wondering if that logo user churned because they're having hard times, or because they hate you;</li>
<li>date night interrupted by PagerDuty.</li>
<li>sweating over microcopy tweaks like <em>activate</em> versus <em>launch</em>;</li>
<li>debugging yet another fucking bug in some iOS mail app's rendering engine;</li>
<li>an uneasy sense of dread knowing that the day is mostly done and forty decisions still need to be made;</li>
<li>sleep interrupted by PagerDuty.</li>
</ul>
<p>In short: you will be spending a lot of time, thousands of hours, doing work that is somewhere on the spectrum between "mildly unpleasant" and "evokes daydreams of working as a barista". This is time that, if you napkin-math it out, values your labor at $20/hr. If you're doing the absolutely <em>right</em> thing at any given point in time, you're spending more time making less money doing something you probably find less fun than any other job you've had.</p>
<p>So why do this? Why spend a lot of time and energy and effort doing work that, in its best form, can be lovingly described as <em>malaise punctuated by fleeting ecstasy?</em></p>
<p>This is a fair question, given that Buttondown despite that is the thing that I've spent more time on in my life (both in terms of wall clock time and hours spent working) than anything else.</p>
<p>If I was purely motivated by compensation, I would be working in Big Tech <sup class="footnote-ref"><a href="https://jmduke.com/posts/essays/elephants/#fn2" id="fnref2">[2]</a></sup>; if I was purely motivated by getting to hack on interesting problems all day I would be working for a small startup or freelancing <sup class="footnote-ref"><a href="https://jmduke.com/posts/essays/elephants/#fn3" id="fnref3">[3]</a></sup>.</p>
<p>The answer, I think, is some combination of <strong>satisfaction</strong> and <strong>agency</strong>:</p>
<ol>
<li><strong>Satisfaction</strong> — If I had a Mount Rushmore of “best moments of my career”, three out of the four <sup class="footnote-ref"><a href="https://jmduke.com/posts/essays/elephants/#fn4" id="fnref4">[4]</a></sup> are some variation of “a person used Buttondown, liked it so much that they paid me thousands of dollars, and then still liked it so much that they told all of their friends and colleagues about it.” If you are the kind of person whose favorite day at a company is when you get to send a launch email for a project: imagine that, times three hundred.</li>
<li><strong>Agency</strong> — every morning, I wake up and get to decide exactly how I want to build Buttondown. Granted, I rarely do that — I try to be disciplined, and spend most of my time doing what is Pareto optimal for the business, even if it’s stuff I’d rather not do (see the prior list re: debugging rendering bugs and sales calls.) But if I am ever feeling a little antsy, I can completely wipe my slate clean and start working on an ambitious refactor or feature branch just because I want to. Almost all of the best parts of Buttondown have been built not out of a well-strategized, market-researched ten-day deep dive but out of me waking up with an idea stuck in my craw that only eight hours of flow work will dislodge.</li>
</ol>
<p>Those words, vague as they are, are not universal: I’m sure if you talk to a dozen other founders in a similar phase of their journey you’ll get a dozen different answers. <strong>But they’ll all have an answer.</strong> (You might need to get a couple shots of whiskey in them first to have them shake whatever their sound-bite-shaped answer is — “I really want to make the world a better place by letting people chat with PDFs” is marketing, not self-reflection.)</p>
<p>If you're thinking that you want to build a product (whether it's a nights-and-weekends thing, or you want to raise a few rounds of VC money, or whatever) — ask yourself <em>why</em>. Make sure your answer is really, really good; that answer will be equal parts amulet and bulwark.</p>
<hr />
<p>I can't resist ending on a little lagniappe, especially when the rest of this essay can feel a little, uh, dour:</p>
<p>This month my wife and I went on our honeymoon, a two-week affair spanning Paris and Tuscany. There is no PTO when you're running your own business; there are the systems and structures you were wise enough to place ahead of time, and that is all.</p>
<p>But I managed to only spend a mere fifteen minutes every morning (typically mid-croissant) checking emails to make sure nothing had burned down — and nothing did. <sup class="footnote-ref"><a href="https://jmduke.com/posts/essays/elephants/#fn5" id="fnref5">[5]</a></sup> And when I arrived back home stateside, travel-weary and jet-lagged, I of course had a mountain of emails waiting for my reply — life does not stop just because you're touring Montmartre or Monteriggioni.</p>
<p>But I also had received a raise.</p>
<p>Amidst the bug reports and unanswered sales calls and low-priority checker failures, Buttondown's MRR had grown — not a huge amount, but not a small one either — while I was eating my body weight in carbohydrates.</p>
<p>That, admittedly, was pretty cool.</p>
<hr class="footnotes-sep" />
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>A term that, after all these years, I'm still not quite <em>comfortable</em> with, for all of its Eisenberg-ian connotations <a href="https://jmduke.com/posts/essays/elephants/#fnref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><p>Anyone who says that Big Tech is not the best risk-adjusted way of maximizing your career earnings is lying, fyi! <a href="https://jmduke.com/posts/essays/elephants/#fnref2" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn3" class="footnote-item"><p>Your reward for a successful product is to spend less and less time on the <em>product</em> itself, and more on the business surrounding it. <a href="https://jmduke.com/posts/essays/elephants/#fnref3" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn4" class="footnote-item"><p>The remaining fourth moment: hiring people at Stripe. Being a line manager at a big tech company has lots of downsides but recruiting and growing awesome people outweighs pretty much all of them. <a href="https://jmduke.com/posts/essays/elephants/#fnref4" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn5" class="footnote-item"><p>Thank you, Ben! <a href="https://jmduke.com/posts/essays/elephants/#fnref5" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
Befriending the Goon Squad2023-07-12T00:00:00Zhttps://jmduke.com/posts/essays/goon-squad/<h2>TL;DR</h2>
<ol>
<li>The single biggest asset that side projects and "nights and weekends" businesses have on their side is near-infinite runway.</li>
<li>If you have near-infinite runway, your largest risk is yourself: you get bored, you get demotivated, you get distracted, etc.</li>
<li>If you're sufficiently patient and disciplined, you can wait out the competition and rely on slow, compounding growth.</li>
<li>This doesn't work forever, but "five years of slow growth" is often the difference between a side project and a lifestyle business.</li>
</ol>
<hr />
<h2>A Visit From The Goon Squad</h2>
<blockquote>
<p>Time’s a goon, right? You gonna let that goon push you around?”</p>
<br />
Scotty shook his head. “The goon won.<a href="https://bookshop.org/p/books/a-visit-from-the-goon-squad-jennifer-egan/229047" class="absolute right-3 bottom-3 no-underline not-italic text-orange-700">—A Visit From The Goon Squad</a>
</blockquote>
<p>A service I admire a lot is <a href="https://blot.im/">blot.im</a>, a headless CMS of sorts — you provide a list of files, connect Dropbox/Git/etc., and it turns it into a full blog (or portfolio, or reference, or whatever.)</p>
<p>Blot's about page contains a list of alternatives, a move I think is always equal parts kind and useful, but with a bit of a twist:</p>
<p><img src="https://jmduke.com/img/blot.png" alt="blot.im's about page" />
<em>More dead competitors than alive.</em></p>
<p>I've chatted with <a href="https://lllllllllllllllll.com/">David</a>, who runs Blot, a few times; I've never quite asked him how explicit the intent of this section is. Is it a warning? A boast? A bit of both?</p>
<p>To me, it sends a clear message: <strong>there are many competitors that have come before; there will be many that follow; despite that all, Blot remains.</strong></p>
<p>This is an important message for independent developers to internalize: especially in crowded markets, <strong>survival, not victory, is the success condition</strong>.</p>
<p>I run <a href="https://buttondown.email/">Buttondown</a>, a newsletter platform. Buttondown shares very little with Blot, but they are both in very crowded spaces — awash with competitors, some of whom are amateurish, some of whom are quite well-funded.</p>
<p>I've been asked many times some variation of the question "how do you deal with the competition?" There are many answers to this question; in this essay, I want to give one: <strong>wait until they die</strong>.</p>
<hr />
<h2>Buttondown's first year</h2>
<p>I launched <a href="https://buttondown.email/">Buttondown</a> in the summer of 2017. The time seemed ripe: the New York Times was writing thinkpieces about the "email newsletter renaissance", and the tool du jour was not just <em>bad</em> but was <em>actively deprecated</em>.</p>
<p>Unfortunately, I am not a terribly unique person. By my count, <em>eight</em> other newsletter platforms launched in the same year as Buttondown, most of which were either VC-backed or had a significant amount of press coverage.</p>
<p>This was <em>extremely</em> discouraging. I was a solo founder, working on nights and weekends, with no funding and no real marketing budget, fighting for every new user. Seeing a glossy Verge write-up or a handshake Twitter integration felt viscerally painful
in a way that was both unactionable and paralyzing.</p>
<p><img src="https://jmduke.com/img/double-bounce.png" alt="" />
<em>This puff piece was published literally one week after I launched Buttondown. Devastating!</em></p>
<p><img src="https://jmduke.com/img/revue.png" alt="" />
<em>Also in 2021, Revue — which launched shortly after Buttondown — was acquired by Twitter. I was fairly terrified at the prospect of Twitter deeply embedding a newsletter and long-form creation into their tooling; they unceremonially shuttered their business in early 2023.</em></p>
<p><img src="https://jmduke.com/img/bulletin.png" alt="" />
<em>Facebook Bulletin, a newsletter platform, launched in 2021. It lasted less than two full years.</em></p>
<h2>How to make time your ally</h2>
<p>In reality — many of these services lacked staying power. Two of them were shuttered by freelancers who got bored; one was quickly acquihired and succumbed to bitrot; others scuttled along in various degrees of non-success.</p>
<p>Buttondown, meanwhile was growing. Slowly, <em>slowly</em>, it grew, customer by paying customer. It had time on its side: I had zero burn, it wasn't my full time job (and thus I didn't need to pay myself), and I had no investors to answer to.</p>
<p>Moreover, many of the things functions that Buttondown relied on (word of mouth marketing; beneficial unit cost economics that come from SMTP; SEO juice) all benefited from <em>wall clock time</em>.</p>
<p>Five years passed. I hacked on the product with a dogged sense of preserverance and faith. Some months were "good months" (no churns, two paid new users); some months were good months (competitor shuts down).</p>
<p>There was no big bang; there was no moment of arrival. You sit, and wait, and water your garden every morning; one day, you wake up and discover a farm.</p>
<p>"A farm", in my case, was a business that in its first year felt stuck at sub-$500 MRR that now was large enough and stable enough to support me full-time.</p>
<hr />
<h2>Plateaus versus finish line</h2>
<p>This approach works — to an extent. Time will help your organic growth and cull weaker competitors from the fold, but it won't magically solve significant gaps
in your product's feature set or fundamental flaws in your acquisition and retention model.</p>
<p>After a certain point in time, the diminishing returns of "just let it sit" will be indistinguishable from zero, and you'll have a product in some sort of terminal state.</p>
<p>What to <em>do</em> with that terminal state depends on your relationship with the product:</p>
<ul>
<li>If it's humming along at a relatively small scale and you have no real desire to grow it, then congratulations! You've built a lifestyle business.</li>
<li>If it's humming along at a relatively small scale and you <em>do</em> have a desire to grow it, then you're going to have to start investing time and energy into it again. Maybe the "nights and weekends" is no longer enough; maybe you need to start hiring and/or working on it yourself.</li>
<li>If the hum is either too quiet or altogether absent, then it's time to call the thing a failure.</li>
</ul>
<p>I chose the middle path: torn between keeping Buttondown in a level of suspended animation and rolling up my sleeves to work on it full-time and break through to the next level of success, I chose the latter.</p>
<p><img src="https://jmduke.com/img/buttondown-customer-count.png" alt="" />
<em>It has gone pretty well thus far.</em></p>
<h2>Via negativa</h2>
<p>There are entire <em>swaths</em> of project for which this approach is not applicable. If the project you're working on:</p>
<ul>
<li>requires a very large amount of up-front capital and/or energy</li>
<li>requires constant maintenance and/or upkeep even if well-architected</li>
<li>is unappealing to you in the long term</li>
<li>does not lend itself to organic growth</li>
<li>cannot be indefinitely maintained alongside your current lifestyle and workload</li>
<li>is not obviously monetizable</li>
</ul>
<p>then this approach is not for you.</p>
<p>However! <em>Inverting</em> that list of criteria is a good way to find projects that <em>do</em> lend themselves to this approach. If you're looking for a side project, I'd recommend looking for projects that:</p>
<ul>
<li>Require little up-front capital and energy</li>
<li>Can be maintained with minimal upkeep</li>
<li>Are appealing to you as a decades-long project</li>
<li>Lend themselves to organic growth</li>
<li>Can be indefinitely maintained alongside your current lifestyle and workload</li>
<li>Has already been monetized and validated in some way</li>
</ul>
<p>If you find an idea that checks all of those boxes, then you have yourself a project
whose greatest risk for failure is <strong>you</strong>: you get bored, you get demotivated, you get distracted,
you get busy. Apply enough rigor, discipline, and patience, and you can find yourself hacking on your life's work.</p>
<h2>Have ye a yeoman's heart?</h2>
<p>Most of this work is unglamorous; much of it is unfulfilling. (Anyone who suggests otherwise is probably more interested in selling you something than steering you in the right direction.)</p>
<p>I spent five years waiting and working and, in a very priviliged sense, sacrificing:</p>
<ul>
<li>nights and weekends are spent debugging edge cases with role-based access control instead of bar-hopping with friends;</li>
<li>my hourly compensation was a fraction (not a small fraction, but a fraction!) of what you could be making at a FAANG or a post-PMF startup;</li>
<li>energy working on things that are <em>solved problems</em> instead of doing interesting engineering.</li>
<li>a truly terrible PagerDuty rotation (as in, there was no rotation, there was only me).</li>
</ul>
<p>In exchange, I reaped:</p>
<ul>
<li>a deep sense of ownership and pride over my work;</li>
<li>a level of freedom and agency impossible in a traditional job;</li>
<li>a calendar that contains only the meetings I wish it to.</li>
</ul>
<p>This may be an uninteresting trade for you; for me, it brought me to the happiest place I've ever been in my career.</p>
<hr />
<h2>Postscript (I)</h2>
<p>To close out this essay, I'd like to bring up perhaps the most famous example of this working in practice: <a href="https://blog.pinboard.in/2017/06/pinboard_acquires_delicious/">Pinboard's 2017 acquisition of Delicious</a>,
which was something of a watershed moment in the niche indie software community of that era. (<a href="https://news.ycombinator.com/item?id=14462384">800 points on Hacker News!</a>):</p>
<blockquote>
<p>As for the ultimate fate of the site, I'll have more to say about that soon. Delicious has over a billion bookmarks and is a fascinating piece of web history. Even Yahoo, for whom mismanagement is usually effortless, had to work hard to keep Delicious down. I bought it in part so it wouldn’t disappear from the web.</p>
<br />
This is the fifth time Delicious has been sold. Founded in 2003, the site received
funding from Union Square Ventures in 2005, and sold to Yahoo later that year for
somewhere between $15-$30M. In December of 2010, Yahoo announced it was ‘sunsetting’
Delicious, an adventure I wrote about at length. The site was sold to the YouTube
founders in 2011. They subsequently sold it to Science, Inc. in 2014. Science sold
it to Delicious Media in 2016, and last month Delicious Media sold it to me.
<br />
Do not attempt to compete with Pinboard.
<a href="https://blog.pinboard.in/2017/06/pinboard_acquires_delicious/" class="absolute right-3 bottom-3 no-underline not-italic text-orange-700"> —Maciej Cegłowski </a>
</blockquote>
<p>Flash forward six years, and Pinboard appears to be on death's door; the last post is from <a href="https://blog.pinboard.in/2020/07/pinboard_is_eleven/">2020</a>, a year which marked three consecutive years of revenue decline. HN posts <a href="https://news.ycombinator.com/item?id=34062802">so many essays complaining about its stagnation</a> that it has become a bit of a cliche unto itself. And successor technologies — Anybox, Raindrop, and so on.</p>
<p>"Live forever" lacks infinite viability; you cannot in good faith be perfectly confident that the you of a dozen years from now maintains the rigor and excitement and wherewithal to water and lovingly tend to the seedlings you plant today.</p>
<p>And yet. That's no reason not to try.</p>
<h2>Postscript (II)</h2>
<p>In the three week chasm between my starting and finishing this essay, one of Buttondown's competitors <a href="https://techcrunch.com/2023/06/21/beehiiv-a-newsletter-platform-gets-12-5m-in-its-inbox/">raised $12.5 million</a>.</p>
<p>I am excited to revisit this writing in 2033.</p>
<h2>Postscript (III)</h2>
<p>Thank you to:</p>
<ul>
<li><a href="https://www.instagram.com/burtonhf/">Haley</a> and <a href="https://twitter.com/shepwalker">Shep</a> for reading drafts of this essay, and more importantly for carrying me through the months in which my patience was tested.</li>
<li><a href="https://twitter.com/kwuchu">Iheanyi</a>, Buttondown's original hype man and de facto CTO, for keeping me from making too many terrible architectural decisions in those early years.</li>
<li>Everyone who signed up for Buttondown back in those 2018—2019 days, who suffered and stuck through some truly terrible settings interfaces. You all will always be my favorites.</li>
</ul>
Spoonbill (2016—2023)2023-06-11T00:00:00Zhttps://jmduke.com/posts/essays/spoonbill/<h2>The start</h2>
<p>I would be lying to you if I told you I remember exactly what was going on when I came up with the idea for Spoonbill.</p>
<p>I know I was still in what I would call my "year in the wilderness" — in a not-so-great relationship, in a not-so-great job, trying to compensate for both
by moonlighting on a bunch of projects and drinking too many Manhattans. I do not mean this to sound dramatic, but working sixteen hour days is not good for you, and much
of the artifacts of those years — even good ones, like Spoonbill — are slightly hard to trace back to their origin.</p>
<p>I remember this: at some point I stumbled upon a site entitled "Bio is Changed", which did exactly what you might expect: it was a big vertical feed of people whose bio
had changed on Twitter. Most notably, I stumbled upon it from someone complaining that it was "going away" — the creators no longer wished to maintain it, and it would be slowly
going the way of many other bitrot platforms.</p>
<p><img src="https://jmduke.com/img/bioischanged.png" alt="" />
<em><a href="http://bioischanged.com/">Bioischanged.com</a> was not exactly a well-designed app, which tends to be a good heuristic that it did something of value.</em></p>
<p>I am not a particularly original person when it comes to interesting projects — I have found that the most successful process is something along the lines of:</p>
<ol>
<li>Find a tool that people clearly like despite massive flaws.</li>
<li>Build a better version of it.</li>
</ol>
<p>And so, I dedicated the next few weekends to building Spoonbill.</p>
<p><img src="https://jmduke.com/img/spoonbill.png" alt="" />
<em>A testament to how little I've thought about Spoonbill in the past three years: I did not realize there was a feedback widget on the site.</em></p>
<h2>The stack (briefly)</h2>
<p>I cannot emphasize enough how little the stack mattered, but: Django, Postgres, Heroku, and a labyrinth of cron jobs. None of these things changed over Spoonbill's existence: I worked with the tools with which I was most comfortable, and they all worked well enough to not require replacement.</p>
<h2>The launch</h2>
<p>One of the nice things about Spoonbill's being birthed from the ashes of a defunct product is that I had a bit of a built-in marketing funnel:</p>
<ol>
<li>Wait for someone to tweet about BioIsChanged shutting down.</li>
<li>Reply to them with a link to Spoonbill.</li>
</ol>
<p>This worked surprisingly well, especially because most of the folks in step one were in the journalism/VC/thought-leader-y space. A few weeks after launch, Spoonbill had met my relatively meager success criteria: a thousand or so folks were using it, people seemed to like it, and the Heroku bill was only $20 or so.</p>
<p>Then <a href="https://twitter.com/rrhoover">Ryan Hoover</a> posted it on Product Hunt and things got a bit more interesting.</p>
<p><img src="https://jmduke.com/img/rrhoover.png" alt="" /></p>
<p>The 'hunt', such as it was, only made it to #5 or so but user count <em>quintupled</em> overnight and the ball was officially rolling, leading to what is even now, the best launch and series of reactions I've ever had. People <em>loved</em> Spoonbill.</p>
<p>I heard from the founder of BioIsChanged:</p>
<p><img src="https://jmduke.com/img/bio2.png" alt="" />
<em>No greater endorsement than that from the founder of the product which inspired yours.</em></p>
<p>I heard from no small number of VC firms interested in learning more:</p>
<p><img src="https://jmduke.com/img/bain.png" alt="" /></p>
<p><img src="https://jmduke.com/img/paul.png" alt="" />
<em>This genre of email was both common and surreal.</em></p>
<p>I got written up in a bunch of places:</p>
<p><img src="https://jmduke.com/img/vox.png" alt="" />
<em>Vox.</em></p>
<p><img src="https://jmduke.com/img/craas.png" alt="" />
<em>All SEO is good SEO, as the adage goes.</em></p>
<p>It felt really, really good. When I think back to my greatest moment — or series of moments — as a software developer, this might have been it: the sheer endorphic bliss of having clearly and obviously built something that folks <em>loved</em>.</p>
<h3>The money</h3>
<p>If you're following along, you might have two questions right about now.</p>
<ol>
<li>"So you built a free tool? How did you make money?"</li>
<li>"Wait, what happens if Twitter just shuts you down?"</li>
</ol>
<p>For the first few months, the latter answer — nothing, Spoonbill is screwed — informed the former. I didn't really think that much about monetization, not because I didn't think Spoonbill was valuable but because
first and foremost I wanted to make sure it would just stay alive.</p>
<p>Once it was clear that Spoonbill was not going to be arbitrarily axed by Twitter (my flimsy rationale for which being "multiple executives at Twitter, as well as people running the developer relations team, were all active users") I felt a little bit more comfortable thinking about monetization.</p>
<p>I settled on two main paths.</p>
<p>The first one was an amuse-bouche, a way to validate if people actually cared about this information enough to charge for it. I got a few customers, but I really didn't iterate on the product enough to find product-market fit.</p>
<p>The reason <em>why</em> I didn't iterate on the first path enough was because I realized just how much I could charge for the second path.</p>
<blockquote>
<p>Figure out what number you can say to them without laughing. That's your price. <a href="https://twitter.com/joshpuckett" class="absolute right-3 bottom-3 no-underline not-italic text-orange-700">Josh Puckett</a></p>
</blockquote>
<p>I did this — twice! — and managed to sell a firehose of Spoonbill data to two separate firms: one for $2,000/month, the other for $2,500/month.</p>
<p>Let me tell you something: I woke up every single day for the next two months after signing those deals, <em>convinced</em> that I had somehow broken the law and I would find in my inbox an email saying "no, sorry, this has all been a misunderstanding, you must return to us all of that money." The process of sending an invoice of that size was surreal in a way that few things since have quite been, and more than the actual financial gain it was a deeply useful lesson in understanding that the numbers which look big to a twenty-four-year-old look like rounding errors to a sophisticated company.</p>
<h3>The sunset</h3>
<p>Despite all the above — an abundance of fervor and positive feedback, people willing to pay five-digit ARR sums for the data — I ended up not spending much more further time on Spoonbill. I think there were a few reasons for this.</p>
<ul>
<li>The product was done for my personal needs. All future development would have either been on the monetization / power-user features side (for which I didn't have much personal affinity) or for expanding into other networks (which felt somewhat boring — there wasn't clear demand for any specific network other than LinkedIn, which I was quite confident would end in a lawsuit)</li>
<li>I was, frankly, distracted by shinier things: Buttondown was starting to turn the corner from "cute side project that was making some money" to "oh, I might have something here", and my work at Stripe was deep and fulfilling in a way that did not leave me with much creative energy left over at the end of the day or week.</li>
</ul>
<p>So, I let Spoonbill sit: it continued to do its thing, albeit in a worse fashion due to the dataset growing larger and the database growing slower.</p>
<p>In true ouroboros fashion, someone attempted to do to Spoonbill what Spoonbill did to its predecessor, the aforementioned BioIsChanged: <a href="https://www.flock.network/">Flock</a>, a YC-backed tool, joined the 2020 cohort. (Lest this sound like sour grapes — <a href="https://aaron.ng/">Aaron</a>, who runs an excellent blog, reached out to ask if I was interested in partnering before he submitted his application.)</p>
<p><img src="https://jmduke.com/img/flock.png" alt="" />
<em>I think Flock's value proposition — a focus on CRM tooling — made a lot of sense, though it looks to be abandoned in much the same way Spoonbill is.</em></p>
<h3>The end</h3>
<p>And so, for the better part of three years Spoonbill sat in a pleasant stasis: it worked, it paid my mortgage, and it required zero mental bandwidth. (Whenever someone asked me about Spoonbill during those years, I would tell them with pride: the best part about it is that I could sincerely not remember the last time I had to log onto admin or make a code change.)</p>
<p>Then Elon bought Twitter, and they announced API changes:</p>
<p><img src="https://jmduke.com/img/twitter_api.png" alt="" />
<em>lmao</em></p>
<p>This was a development best described as a <em>bummer</em>, even more so because the actual prices were left unannounced for many months. Once the <em>actual</em> API prices were announced ($42,000/month for any non-trivial amount of API traffic), it was obvious what would happen — Spoonbill would shut down.</p>
<p>I reached out to the remaining few customers, cancelled their recurring invoices, and scaled down the various dynos. A lovely little project had come to a lovely little end.</p>
<h3>The epilogue</h3>
<p>The "what if" game is easy. What if I had really gone all-in in 2018 — raised a huge round, figured out how to work with arbitrary additional networks, and so on?</p>
<p>Perhaps the stars would have aligned in such a way that Spoonbill would have been a huge success. The much more likely outcome, I think, is that any significant amount of product traction would have led to a series of very painful conversations with Twitter leadership.</p>
<p>Instead, I spent most of my time in two buckets that represent my best work to date: Buttondown and Stripe.</p>
<p>I think Spoonbill would have been a poor <em>company</em>, but it was a perfect <em>project</em>. I got to learn; I got to meet with people I otherwise would never have; I got to build something that people used; I got to charge money for it.</p>
<p>Per Stripe, Spoonbill grossed $220,300 over its lovely little existence. I'd say I spent one hundred hours on it in total (the vast majority of which came in its first few weeks, pre-launch) — so, $2,200/hour. Not bad!</p>
<p>Spoonbill's data sits in two places, crystalized in amber.</p>
<ol>
<li>A db.t2.medium RDS instance that will probably get shut down in the next year or two.</li>
<li>A backup USB drive that I keep in a drawer in the desk next to me.</li>
</ol>
<p>It's painfully rare for a piece of software to have a true sense of narrative closure: either it succeeds, and is immortal, or it is killed: killed by shifting priorities and shrunken budgets and changing macroeconomic headwinds and more exciting ideas.</p>
<p>Which is why I am grateful, in writing this, that I get to earn a bit of closure (even if it comes at the price of this disjointed prose — there's a reason I called this essay a eulogy!). I am very proud of what I built; I hope that, if you were a user, it brought you some small amount of insight or joy.</p>
<p>(Lastly, and most importantly: deep gratitude to <a href="https://www.instagram.com/burtonhf/">Haley</a>, <a href="https://twitter.com/joshpuckett">Josh</a>, <a href="https://twitter.com/kwuchu">Iheanyi</a>, <a href="https://twitter.com/rrhoover">Ryan</a>, <a href="https://twitter.com/shepwalker">Shep</a>, and many more for their help and support along the way.)</p>
20222022-12-29T00:00:00Zhttps://jmduke.com/posts/essays/2022/<h1>2022 in review</h1>
<p>Hello! My name's Justin. This is my annual review for 2022. You may also be interested in <a href="https://jmduke.com/years/2021">last year's</a>.</p>
<h2>Health</h2>
<p>I am, as I write this, in the best shape of my life. “Best shape” is, admittedly, vague and somewhat subjective; I could rephrase as “I am lifting more weight than I ever have before and am a better boulderer than I’ve ever been, and also don’t have as much body fat.” (I am also in the point of my life where I don’t care much about that final part; the main thing that decides when it is time to start losing weight is when the alternative option is to replace my wardrobe.)</p>
<p>I set a goal last year to join the 1/2/3/4 plate club; I managed the first two, which I find a little entertaining because I would not particularly associate myself with upper body strength. (As of this writing my maxes on squat and deadlift are 285 and 345 respectively, so not too far off but certainly more than a month’s work. So half-credit!</p>
<p>In 2023, I want to hit the 1/2/3/4 plate club — for real this time, so thirty more pounds on the squat and sixty on the deadlift.</p>
<p>I could be harsher on myself than I am going to be. The chaos of the first part of this year - moving, traveling, leaving Stripe - broke a lot of my habits when it came to healthy living, and in May I was not living up to my standards. We both dieted pretty strictly over the summer, and by August we were back to a place where we felt good about our bodies and our habits.</p>
<p>The only real 'new' thing, health-wise, is that I started bouldering again after three or four years off. There's a gym within biking distance that I've started going to with a few friends, and it makes
for great active recovery work amidst a lot of lifting. Bouldering, in many ways, feels like the opposite of lifting. Progress is tangible but not quantifiable; discrete and staccatto rather than monotonic and linear. I am not really a "good" boulderer — I am still mostly flailing around on V3s and V4s, and my training goals
are such that I'm fine with that. That being said...</p>
<p>In 2023, I want to send at least a single V6 route.</p>
<h2>Haley</h2>
<p>To everyone’s continued surprise, Haley has not called off the engagement; despite me dragging her to a completely new city and forcing her to indulge me in many a hare-brained scheme (a square-foot garden; a row house; a fifteen-minute drive from her soon-to-be in-laws) she remains stubbornly set on changing her name to Duke.</p>
<p>I am still not quite sure what I have done to deserve such luck and fortune; I can only resolve to not take such things for granted.</p>
<h2>Telemachus</h2>
<p>Telemachus is our corgi; he turned two in September. He is a perfect dog.</p>
<p>Dog is perhaps the operative word. He is no longer a puppy; he occasionally cosplays as a goblin, as a sandworm, as one of many a fantastical and bizarre creature, but he has aged. He sleeps; he cuddles; he sniffs and fetches. We are past the point of needing to walk him two miles a day; there are no tantrums, no accidents, and very little discipline.</p>
<p>How much do we love Telly? We make our dogsitters stay at the house so he’s comfortable. He is a spoiled dog; he deserves to be spoiled.</p>
<h2>Writing / Personal Site</h2>
<p>I wrote <strong>22,000</strong> words in 2022. That feels pretty good! I didn't have a goal; in fact, I am tempted to feel bad about
my writing output in this year. I think part of this is a teensy, tiny existential crisis: <em>Why do I want to write</em>? There are a couple reasons:</p>
<ul>
<li><strong>To establish myself as a personality and to build an audience.</strong> This is... really no longer a thing I care about, especially compared to 2012 when I felt like I needed a dozen blog posts on Python iterators just to get my foot in the door at a tech company. I have a good audience, and any 'real' audience building I feel obliged to do is with respect to Buttondown, which is a different beast entirely.</li>
<li><strong>To strengthen my skills as a writer</strong>. This is still true, but I am writing so much in other venues (for work, namely) that I don't need a personal blog as an outlet.</li>
<li><strong>To force myself to think critically</strong>. This is by far the biggest reason I still write; it is easy for me to write my reviews of film and games and books, and I love the process of thinking through my thoughts and articulating them.</li>
<li><strong>To have fun</strong>. This is, of course, evergreen — but some of the magic of writing has been lost, I think, and I have more reliable ways to have fun.</li>
</ul>
<p>There is... nothing that I'm particularly proud of having written.</p>
<p>On the other side of things, I spent some serious time tinkering with the scaffolding of this site: migrating it from Jekyll to Next and switching from Airtable to flat files. Both of these were done with the idea of 'modernizing' the codebase and having some fun learning new concepts (bleeding-edge Next and MDX); I succeeded on those fronts, but this codebase is frankly a bit of a mess at the moment.</p>
<p>In 2023, I want to get this site's codebase to a point where I want to brag about how clean it is.</p>
<h2>Richmond</h2>
<p>In February - what feels like, literally, a lifetime ago - Haley and I packed up and moved from Seattle to Richmond, Virginia. I grew up here; she grew up in Northern Virginia (some of my readers will know the phrase “NoVa” well, and hopefully all of them will correctly associate it with the appropriate level of disdain.)</p>
<p>It has been pretty dang lovely. Richmond is not a city for which many people have a ready mental image. Think of it in much the same vein as any of the “C-tier” East Coast cities (your Charlestons, your Ashevilles, your Providences); it is small but much larger than it used to be, with a couple capstone corporations running de-facto jobs programs, a booming student population, a number of “hip” businesses importing culture from New York on a twenty-four-month delay (or more precisely importing it from DC on a twelve-month delay, which itself imports from New York on a twelve-month delay.)</p>
<p>Our cost of living is half of what it was in Seattle; we live in a gorgeous neighborhood, walking distance from a half-dozen great breweries and two very good coffee shops and an Aldi and a Whole Foods; my parents are a fifteen minute drive away, and Haley’s parents are a scant ninety.</p>
<p>We miss Seattle; we love Seattle; we are so, so happy that we find ourselves in Richmond.</p>
<h2>Buttondown</h2>
<p>I work on Buttondown full-time now! (You know it’s serious when I change my Twitter bio to “founder”, because being a full-time independent businessman means I have to indulge the hackneyed affordances of online thought leadership.)</p>
<p>Buttondown had a good year in terms of outputs. MRR is up a significant amount; I completed some serious technical projects and the codebase is in a great place; there are some logo customers who I’m really, really thrilled about being able to count as users.</p>
<p>I did not do many of the big picture features or meta-work that I really wanted to do. I am, again, not going to beat myself up too much over this; two-thirds of the year Buttondown was very much a “thing on the side” and the past three months I’ve spent trying to build personal tactical discipline rather than commit to any very big strategic moves.</p>
<p>Still, this will change in 2023. This is the first time I’m entering a new year with Buttondown not being a fun little thing I have on the side but as the way I earn a living.</p>
<p>In 2023, I want to — need to — transition my relationship with Buttondown from that of a successful project to a stable, growing, mature business.</p>
<h2>Stripe</h2>
<p>In last year’s coda, I wrote:</p>
<blockquote>
<p>The truth is, I have operated the last nine months or so with absolutely no slack. The vast majority of days this year were bereft of even thirty minutes to relax and gather my thoughts. This is a bit of a bummer: the superstructure I built out around my life might have been feasible in a world where I was locked down and could have complete mastery of every single hour being spent or whatever, but that’s a) inherently unsustainable; b) especially unsustainable in a year where I changed my job and am once again subject to the whims of a social life.</p>
</blockquote>
<blockquote>
<p>Really, the question that is worth posing is: what do I want to do less of? Where is the time — and energy — going to come from? (And even that dichotomy is interesting to me. One of the most interesting parts of being in management now is internalizing that your time is no longer your own: something like 60% of my time is given away to others, either in the form of meetings or in the form of being in “reaction mode”.)</p>
</blockquote>
<p>I think a part of me knew as I wrote those words that the answer was largely "stop working two jobs and work one job instead". That part of me grew louder and more confident over the first few months of 2022, culminating in my decision to leave what was the best job I've ever had at a company I still admire more than any other. (I still love Stripe, in case that's not obvious. It's a different company now — one with eight thousand heads instead of eight hundred — but one that I still think is doing great work, and one whose Slack channels I miss dearly.)</p>
<p>The thing I am most proud of by far - and the thing I miss at most - of Stripe was my team. (If you are reading this and you are familiar with the Ruxpin lore - you and I are kindred spirits.)</p>
<h2>Other projects</h2>
<p>I wasn’t sure whether or not to preface this section with an “Unfortunately, “. (And, of course, with that aside, I get to have my cake and eat it too!) I did not launch anything new in 2022; I had designs to, but those designs were unfulfilled.</p>
<p>I only feel a little guilty about it, and only in a vague, abstract sense, which means that it was probably the correct choice. I can’t really say that it was out of languor or indecision: I had a good idea for a product that I still think will do well, and I did not have the time or energy to ship it. That time and energy was spent in a variety of virtuous ways:</p>
<p>work that was, pound-for-pound, more important (be it either Stripe or Buttondown)
spending time with loved ones and friends and dogs
being blissfully off-screen</p>
<p>I was chatting with a friend about time and energy (he has kids; I will probably be a parent in the next few years), and he said:</p>
<blockquote>
<p>I’d say the biggest changes to <em>me</em> with kids are that I care about other things a lot less through sheer prioritization. It’s not just a time and energy thing, it’s a where your heart is thing.</p>
</blockquote>
<p>I think I had underestimated the extent of how much my life and priorities have changed over the past five years, going from being twenty-five with no strong social commitments and all the willpower in the world to being thirty and awash in lovely, lovely burdens. Five years ago, I could trivially carve out fifteen hours in a weekend to launch a new project; now, it’s closer to four.</p>
<p>I think that’s just growing up. I’d love to take another stab at a project in this coming year, but I’m not planning on it — I want every ounce of energy and willpower dedicated towards Buttondown, and graduating it from “independent software thing” to a legitimate company.</p>
<h2>Reading</h2>
<p>I read, as you see, quite a bit this year. My goal going into the year was to read more Big Books, and I spent most of the first half of the year doing just that — wrapping up the Years of Lyndon Johnson, Middlemarch, futzing around with The Brothers Karamazov and War and Peace before setting them down for a bit (to return to in 2023, hopefully.) I read five books that I would easily and instantly include in my personal canon:</p>
<ul>
<li><a href="https://jmduke.com/media/the-secret-history/">The Secret History</a></li>
<li><a href="https://jmduke.com/media/middlemarch/">Middlemarch</a></li>
<li><a href="https://jmduke.com/media/the-supper-of-the-lamb/">The Supper of the Lamb</a></li>
<li><a href="https://jmduke.com/media/a-visit-from-the-goon-squad/">A Visit From The Goon Squad</a></li>
<li><a href="https://jmduke.com/media/master-of-the-senate/">Master of the Senate</a></li>
</ul>
<p>It is very hard to choose between these five; if you are reading this essay, please add all five of them to your library queue with all haste. For tradition’s sake, though, I must choose one, and I choose <a href="https://jmduke.com/media/the-supper-of-the-lamb/">The Supper of the Lamb</a>, whose prose and purpose I think is unique amongst its kind.</p>
<h2>Gaming</h2>
<p>I am quite pleased with my year of games, despite not finishing a slew of games that I really wanted to finish (looking at you, <a href="https://jmduke.com/media/crystal-project/">Crystal Project</a> and <a href="https://jmduke.com/media/inscryption/">Inscryption</a>) and having not even started Elden Ring, which was going to be my major sabbatical project. I played a huge number of games I loved.</p>
<p>My favorite — in terms of the one that I will think about most often and the one that brought me the most joy — was <a href="https://jmduke.com/media/tunic/">Tunic</a>. It is hard to talk too much about this game because part of what makes it so wonderful is the experience of going in unaware and letting it dazzle you, but dazzle it did. The feeling I had while playing Tunic was one of rapture and revelation — even if I had to turn down the difficulty for the final few bosses.</p>
<p>I will give an honorable mention to <a href="https://farmrpg.com/">Farm RPG</a> , the closest thing I have these days to an MMO. It is a very small and sweet game in which I’ve progressed quite far, and if you’re looking for the kind of thing with which you can spend fifteen minutes while waiting in line I could not recommend it highly enough.</p>
<h2>Film</h2>
<p>I did not watch as many movies as I wanted to this year, despite that being a big nominative emphasis. I blame, as you have heard many times, the chaos of moving: there was very little structure between January and May, and everything after that felt a bit too rushed and purposeless.</p>
<p>That being said, my choice for favorite film of the year is an easy one, and (even with a relatively small field) the quickest decision of any I’ve had to make in this year or priors. <a href="https://jmduke.com/media/drive-my-car/">Drive My Car</a> was a perfect piece of cinema from start to finish, and the final Uncle Vanya scene left a tattoo upon my heart.</p>
<p>In 2023, I want to watch 50 films. I don’t like quantitative approaches to consumption — you run the risk of doing the thing to say you did the thing, not doing the thing because you want to do the thing. But I miss movie nights with Haley and getting to spend time in worlds outside my own; I can point to very few movies that I regretted spending time with (which is absolutely not true of books, games, and television) and while habits aren’t virtuous in of themselves I think the lack of movies in my life this year was not due to distaste but due to muscle atrophy.",</p>
<h2>Music</h2>
<p>I didn’t love many albums this year; certainly I did not love any new music this year. Releases from some of my favorite contemporary artists (Carly Rae Jepsen; The 1975; Pusha T) felt like flat re-treads of previous, superior work.</p>
<p>There was a lot of crate-digging jazz that I loved (<a href="https://jmduke.com/media/waltz-for-debby/">Waltz for Debby</a>; <a href="https://jmduke.com/media/portrait-in-jazz/">Portrait in Jazz</a>). But, by Spotify’s numbers the album I loved the most was <a href="https://jmduke.com/media/the-minstrel-show/">The Minstrel Show</a>, an old Little Brother record that felt like a too-perfect mashup of what I loved most about early Kanye and Camp Lo — a sheer glee of production + verse work with skits you didn’t even feel tempted to skip.</p>
<p>I have been getting quite into house music, though, as a bit of a higher-BPM alternative to lo-fi. (I am indebted to <a href="https://twitter.com/jasdev?lang=en">Jasdev</a> for the all of the playlists.)</p>
<h2>Television</h2>
<p>You could divide my television energy pretty neatly into two halves: I watch a lot of anime with my brother and I watch a wild smorgasbord of everything else with Haley. (I very occasionally watched a show by myself — this year it was Atlanta, since I had watched the first two seasons before I met Haley.)</p>
<p>This may be a bit of recency bias, because we just finished up the season finale, but <a href="https://jmduke.com/media/andor-season-1/">Andor (Season 1)</a> is far and away my favorite thing I watched this year. Set aside the Star Wars thing for a second — it really does not matter that this is a Star Wars show. (There is, frankly, very little intellectual property in it; you could sand off the Star Wars branding edges and end up with the same product.) I wrote about it more here, but in short — it is a show that struts in with an immediate sense of confidence, elegance, and clarity of purpose, and manages to simultaneously be incredibly entertaining and a forceful piece of work with a thesis about the world that it delivers to stunning effect.</p>
<p>It’s also a first season of television that is compelling in of itself while making you desperate in anticipation for a second season, which is no easy feat. (Even some of my all-time favorites often come with a “well, muddle through the first season and then things really take off” warning — the closest thing you get to that with Andor is a begrudging acceptance that the first two episodes are deliberately slow and more than a little confusing, not unlike the first fifty pages of any le Carré.</p>
<h2>Miscellany</h2>
<p>Some things that I consider noteworthy that don't have their own section:</p>
<ul>
<li>I only had two podcast appearances this year. I would like to double that next year; public speaking (or the rough equivalent that podcasting affords) feels like a good way to check the three boxes of "a thing I am good at", "a thing that helps and informs others whom I do not normally reach", and "a thing that is good for my prospcts".</li>
<li>I've made a total of three angel investments, and am actively looking to purchase a business with a few friends. The slow and inevitable shift from labor to capital continues!</li>
<li>I spent a good but perhaps suboptimal amount of time volunteering in what felt like a relatively high-leverage capacity: first through pair programming with <a href="https://www.un-loop.org/">Unloop</a> and then by giving mock programming interviews over the past few months. I'm glad I did this, but it's not obvious to me that it's the most utile allocation of my time in terms of helping underrepresented folks enter the industry.</li>
<li>One common refrain I have from people, especially with regards to media consumption, is "where do you find the time?" This is always a funny question to get because I do not feel like I ever have the time in the day to read or play or watch all of the things I want (I probably average around sixty-to-ninety minutes a day.) The closest thing I can offer as an answer is that I am very surgical with how I spend "dead time". I check and respond to texts twice a day; I spend very little time on social media or on anything with an infinite scroll; I sub out podcasts for audiobooks.</li>
</ul>
<h2>Coda</h2>
<p>Zora Neale Hurston tells us that there are years that ask questions and there are years that answer; I think only time will tell which of those this year has been, but it has been nothing if not the one with the most life changes for me since I decided to move to Seattle a decade ago, a naive twenty-year old who couldn’t buy liquor but wanted more than anything to work at a big tech corporation. I turned thirty; I quit my job to run my own company; I moved back East.</p>
<p>I am happier than last year; I am more relaxed than last year.</p>
<p>Most of this has come from clarity of purpose. I’m writing less because the things I once wanted out of writing (“building a personal brand”, that sort of thing) are no longer things I care much about; I left Stripe because I am at the point where metis is the most important thing I want to work towards in my career; I haven’t started any new projects because the marginal value of doing so is less than working on Buttondown, and the marginal joy of doing so is less than spending time with friends and family.</p>
<p>As I write this, I have four sticky notes on my monitor.</p>
<p>Two — positioned on the right-hand side of the monitor, in a lovely looping sans-serif — are from Haley, sweet nothings that she hides around the house when I’m out of town.</p>
<p>Two — positioned on the left-hand side, in a denser and frankly uglier all-caps scratch — are my own.</p>
<p>One is a list of active projects, to make sure I don’t let my in-flight commitment recede to the background:</p>
<ul>
<li>“Finish 2022 end of year post”;</li>
<li>“Return of the Obra Dinn”;</li>
<li>“Designing for People”</li>
</ul>
<p>The second is a stacked list of what my priorities are for 2023:</p>
<ul>
<li>FAMILY</li>
<li>HEALTH</li>
<li>METIS</li>
<li>PEACE</li>
<li>WEALTH</li>
</ul>
<p>I've joked with a couple friends about 2023 being a year of "monk mode": a lot of time working <em>hard</em> in the next nine months (our wedding's in September, so that'll make for a nice finish line) to get Buttondown into a place where I can take my foot off of the gas. I'm not sure if that's the right way to think about it, but I do know that I'm looking forward to a year of more focus, more clarity, and more peace.</p>
<h2>Thanks</h2>
<p>To Harrison, Ryan, and Jasdev for reading drafts of this essay; to Sumana and John for being such excellent writing and coworking partners over the year; to Sonny for keeping a well-stocked Plex; to my parents for spoiling Telly as much as they spoil me; to Haley for everything; to you, presumably, for reading this far.</p>
20212021-12-29T00:00:00Zhttps://jmduke.com/posts/essays/2021/<h2>Personal</h2>
<div class="my-4 lg:my-8"><div class="border-subtler rounded-t-lg bg-subtler border-solid border border-b-0 uppercase font-bold text-sm py-2 px-4">Counts</div><div class="border-subtler bg-subtle text-gray-500 py-2 rounded-b-lg border-b border-x"><div class="flex px-4 border-solid"><div><div>Number of times I’ve texted “gahhhh sorry for the late response”</div></div><div class="text-right flex-1 tabular-nums font-bold">144</div></div><div class="flex px-4 border-solid"><div><div>Hours spent gaming with partner</div></div><div class="text-right flex-1 tabular-nums font-bold">117</div></div><div class="flex px-4 border-solid"><div><div>Photos of Telly taken whilst he sleeps</div></div><div class="text-right flex-1 tabular-nums font-bold">84</div></div><div class="flex px-4 border-solid"><div><div>Bottles of Veuve Clicquot consumed</div></div><div class="text-right flex-1 tabular-nums font-bold">1</div></div></div></div>
<h3>Health</h3>
<p>I ended 2020 in a period of what could generously be called “fitness stasis”;
I was working out and walking but not particularly pushing myself in any
direction. In retrospect, too, I was drinking a lot more than I should have:
it was a stressful Q4 for Haley and myself, work-wise, and a friend
bequeathing us a solid two gallons of egg-nog did not help matters.</p>
<p>
Things went better this year! I got on a solid lifting program (
<a href="https://www.strongerbyscience.com/program-bundle/">SBS 2.0</a>, and
then back to my old mainstay{" "}
<a href="https://thefitness.wiki/routines/nsuns-lp/">nSuns</a> once I got back
into the rhythm of five-a-week lifting), ran a couple 5Ks, and in general
we’ve been eating better. My lifting peak was in late 2018, and I’m more or
less back where I was back then: close-to-but-not-quite-at the 1/2/3/4 plate
club.
</p>
<div class="my-4 lg:my-8"><div class="border-subtler rounded-t-lg bg-subtler border-solid border border-b-0 uppercase font-bold text-sm py-2 px-4">Goal</div><div class="border-subtler bg-subtle text-gray-500 py-2 rounded-b-lg border-b border-x"><div class="flex px-4 border-solid"><div>In 2022, I want to hit the 1/2/3/4 plate club. (The 1/2/3/4 plate club, if you're unfamiliar, is a one-plate press (135 pounds), a two-plate bench (225 pounds), a three-plate squat (315 pounds), and a four-plate deadlift (405 pounds.) This is not a particularly aggressive achievement: I’m sitting at a 130 OHP, 215 Bench, 245 Squat, and 335 DL, so it’s mostly about getting my squat numbers up. But this was a fitness goal that, when I started lifting six or so years ago, seemed like an impossible horizon, and now it feels…very much possible, with dedication!</div></div></div></div>
<h3>Telemachus</h3>
<p>
We got Telemachus in September of 2020; he’s around a year and half years old
now. He still has lots of puppish tendencies: every new stranger is a thing of
joy, and every scary noise is a thing of terror. But he’s started to become
more of a capital-d Dog: while he’s still energetic, it’s less of a “we need
to walk him three miles a day or else he is a terror” situation and more of a
“if we walk him two miles in the morning he is more or less out of commission”
situation.
</p>
<p>
This time last year, we were very proud of him/us for removing the
play-pen/prison from our living room & office so he could move around the
house unencumbered. How quaint that seems now! The dog is our shadow, and he
like us is a creature of habit: he curls up with us in bed (a particularly
cute habit of his: he’ll jump down to his kennel as we turn out the lights
and, around fifteen minutes before sunrise, jumps back into bed and snuggles
with Haley), keeps guard from his roost atop the couch, and is the greatest
office dog we could ask for.
</p>
<p>
He is not perfect, of course. His playfulness manifests as rebellion, and I’ve
had more than a few mornings interrupted because he sprinted out of the house
chasing a mailman; our backyard needs to be resodded from two digging sprees
(and, to be fair, because my deadlifts left pairs of heavy divots). We call
him The Goblin (a term of affection, I promise), and perhaps a more accurate
moniker would be Puck; he is a prankster, a mirth-font, a lumpen bag of tricks
and joys.
</p>
<p>I have run out of laudits for the dog: he is the light of our life.</p>
<p>
(Also, I bought <a href="http://telemachus.dog/">telemachus.dog</a>.)
</p>
<h3>Haley</h3>
<p>I got engaged! :)</p>
<p>
I surprised Haley when she came back from a trip to Virginia and a subsequent
dinner with her family; she opened up the door to our house (whilst wearing
sweats, much to her continued chagrin) to me wearing a suit and carrying a
dozen roses, with candles and petals strewn across the living room. I had a
bottle of Veuve Clicquot in the fridge; I had a large pizza in the oven. I
told her I loved her very, very much and wanted to spend the rest of my life
loving her very, very much and then some other stuff that I frankly cannot
remember because it was rather emotional. She said yes.
</p>
<p>
I honestly do not know what else to write here. I have my soulmate. We spent
all of four days apart this year and they all sucked. I am very, very lucky,
and to write more would be trite.
</p>
<div class="my-4 lg:my-8"><div class="border-subtler rounded-t-lg bg-subtler border-solid border border-b-0 uppercase font-bold text-sm py-2 px-4">Goal</div><div class="border-subtler bg-subtle text-gray-500 py-2 rounded-b-lg border-b border-x"><div class="flex px-4 border-solid"><div>More date nights and lazy mornings, of which there can never be enough.</div></div></div></div>
<h3>Miscellany</h3>
<p>Some other updates that don’t quite warrant their own section:</p>
<ul>
<li>We’re moving back to Virginia! I love Seattle with all of my heart, and will
be very very sad to leave, but the timing was right: Haley’s sister just had
a baby, we found a great house in Richmond, my team is permanently remote,
and the Seattle housing market is fairly bonkers. (If you’re reading this
and live in Seattle — let’s grab a coffee/beer/sashimi! Or not. I’ll be
coming back fairly often.)</li>
<li>After spending a nice amount of time revamping my personal website
(<code>arcana.computer</code>! You’re on it! And there are a bunch of links in this
very web page to it!) I spent almost no time writing or improving it. Which
is fine — that is not important, in the grand scheme of things. But I am
increasingly enamored with the idea of using it as an outlet, and I’d like
to spend more time working on it and tinkering with it. I am coupling the
act of “writing” and “tinkering with my site” intentionally here — both of
them are done more for self-gratification and self-discovery than anything
else (I am no longer at the point in my life where anything can seriously
change from becoming known more as a blogger.)</li>
<li>This is not particularly virtuous in of itself, but I’ve done a particularly
good job in terms of sticking to a schedule and being “productive”. This was
a big goal of mine this year after the chaos of 2020; investing a lot in
personal systems helped me survive during this particularly tumultuous year.</li>
<li>I got absolutely no work done on the house. This ended up netting out okay,
since, well, we’re leaving the house to go move across the country, but I
can’t point to a single project that we spent time on in 2021.</li>
<li>I got a bunch of press! I showed up on Daring Fireball (twice) and <a href="https://www.latimes.com/business/technology/story/2021-06-14/newsletters-were-supposed-to-cut-out-the-middlemen-but-what-about-gmail">was interviewed for the Los Angeles Times</a>; chatted about Buttondown on Running in Production and <a href="https://codingcat.dev/podcast/1-27-producing-newsletters-with-buttondown?utm_source=twitter">Coding Cat</a> (and <a href="https://www.reddit.com/r/SaaS/comments/o7ayna/i_grew_my_saas_to_5000_mrr_on_nights_and_weekends/">did an AMA in /r/SaaS</a>); got to share some cute photos of the double desk on <a href="https://www.workspaces.xyz/p/102-justin-duke-engineering-manager">Workspaces</a>; and participated in a hallowed Var Talk in <a href="https://arrows.to/keep-going/">Keep Going</a>.</li>
</ul>
<div class="my-4 lg:my-8"><div class="border-subtler rounded-t-lg bg-subtler border-solid border border-b-0 uppercase font-bold text-sm py-2 px-4">Goal</div><div class="border-subtler bg-subtle text-gray-500 py-2 rounded-b-lg border-b border-x"><div class="flex px-4 border-solid"><div><div> In 2022, I want to (once again) rebuild arcana.computer. Spoiler alert: this is already almost half-done, so counting it as a 2022 goal feels like a bit like cheating. But I want to have a place where I can throw all my thoughts, and I genuinely want an easier way for me to sift through my notes and my books and all of those things, and I want to just have some fun programming it. The whole “stop rebuilding your blog and just blog” thing is so passe — you should spend your time doing whatever brings you the most joy, and if I happen to fart out some essays about management or whatever along the way, then great."</div></div></div></div></div>
<h2>Coda</h2>
<div class="my-4 lg:my-8"><div class="border-subtler rounded-t-lg bg-subtler border-solid border border-b-0 uppercase font-bold text-sm py-2 px-4">Counts</div><div class="border-subtler bg-subtle text-gray-500 py-2 rounded-b-lg border-b border-x"><div class="flex px-4 border-solid"><div><div>Twitter bio changes logged</div></div><div class="text-right flex-1 tabular-nums font-bold">55828112</div></div><div class="flex px-4 border-solid"><div><div>Emails sent</div></div><div class="text-right flex-1 tabular-nums font-bold">23000000</div></div><div class="flex px-4 border-solid"><div><div>Net new MRR</div></div><div class="text-right flex-1 tabular-nums font-bold">5000</div></div><div class="flex px-4 border-solid"><div><div>Angry emails received</div></div><div class="text-right flex-1 tabular-nums font-bold">77</div></div><div class="flex px-4 border-solid"><div><div>Production breakages</div></div><div class="text-right flex-1 tabular-nums font-bold">16</div></div><div class="flex px-4 border-solid"><div><div>Arcane DNS issues debugged</div></div><div class="text-right flex-1 tabular-nums font-bold">13</div></div><div class="flex px-4 border-solid"><div><div>Commits made after midnight</div></div><div class="text-right flex-1 tabular-nums font-bold">8</div></div><div class="flex px-4 border-solid"><div><div>DMs received from colleagues about my tweets</div></div><div class="text-right flex-1 tabular-nums font-bold">4</div></div><div class="flex px-4 border-solid"><div><div>Teddy Ruxpin dolls shipped</div></div><div class="text-right flex-1 tabular-nums font-bold">4</div></div><div class="flex px-4 border-solid"><div><div>New products launched</div></div><div class="text-right flex-1 tabular-nums font-bold">0</div></div></div></div>
<h3>Stripe</h3>
<p>I am an engineering manager now!</p>
<p>
I haven’t done a lot of soul-searching and reckoning about the transition, and
every time I try to write about it my words come out in listicle form. I think
if I could whittle things down into the three talking points I've deployed
most often, they’d be:
</p>
<ul>
<li>
<p>The single funniest — and most portentious — moment of being an EM was when
I was starting to tell folks that I had made the switch. Every single IC I
talked to said congratulations!; every single EM I talked to said good luck!</p>
</li>
<li>
<p>Tripling a team’s size in three months was the single hardest and most
rewarding thing I’ve done in the industry. Sourcing and selling are things
that you might expect to be relatively solved problems, given their ubiquity
and value; they very much are not.</p>
</li>
<li>
<p>People warned me about how hard it is to give up programming & “thinking as an IC” on a day-to-day basis. They are absolutely correct (and, frankly,
I wouldn’t have felt comfortable giving it up if I didn’t have Buttondown
and other projects to keep that part of my brain sated). The thing that they
did not sufficiently warn me about was the mental recomposition required to
shift from a proactive lifestyle — where working at a company revolves
around the metagame of pursuing maximal deep work on impactful, interesting
projects — to a reactive one, where most of the things that happen within a
given day are obscured by fog of war until they start to happen.</p>
<p>
But that says nothing about what it’s <em>like</em> to be an EM, nor why I’m
grateful for the opportunity, or what I wish I knew (besides the bit about
hiring) or anything like that. And that writing is yet to come, I promise!
</p>
</li>
</ul>
<p>
...Now, that’s a very solipsistic look at Stripe, and I will of course elide
most of the interesting details that have to be redacted for reasons. If
you’re a fellow Stripe, you might guess that I’m particularly proud of{" "}
<span class="redacted">aaaaaaaaaaaaaaaaaa</span> and that I love that we’re
finally <span class="redacted">aaaaaaaaaaaaaaaaaa</span> the{" "}
<span class="redacted">aaaaaaaaaaaaaaaaaa</span>.
</p>
<p>
If you’re not, you can at least trust me on this: I am officially in the jaded
phase of my career. My ennui is at an all-time high; I think I understand the
zeitgeist pretty well, and very little of it is novel or exciting to me. The
panacea to this ennui is my employer: I get to work on a bunch of fairly
chunky problems with the nicest, smartest people I’ve ever met to make
economies better.
</p>
<p>
(Also, <a href="https://stripe.com/jobs">we’re hiring</a>.)
</p>
<h3>Buttondown</h3>
<p>A couple notes on the financials:</p>
<ul>
<li>MRR was of course up YoY, but that belies a different trend: MoM revenue has
stalled out over the past few months, for the first time in ~ever. The free
lunch of new users from “the creator economy” is stalling out, and I’m
ending the year non-trivially south of Buttondown’s peak ($10,105 — that
sweet taste of five-digit MRR).</li>
<li>My average user is worth more, but is younger (thus the lower LTV) and
churnier (thus the higher MoM churn) than ever.{" "}</li>
<li>Operating costs increased in absolute terms but decreased in margin, thanks
to dedicated efforts last quarter.</li>
</ul>
<p>In non-financial terms, what went well:</p>
<ul>
<li>Honestly, annual planning was a very useful exercise. Every single item in
my 2020 retrospective which I completed was absolutely the right choice, and
the things that I punted on (improving the conversion funnel, for instance,
since in H1 I was deluged by organic traffic) is stuff I want to pick up
this year.</li>
<li>Buttondown is still growing, and more operationally stable than ever. This
time last year I was really at wit’s end with customer support, and there
were points of time during this year where I felt ready to declare a certain
amount of operational bankruptcy. Things are calmer now: I might not be able
to make as much day-to-day progress as I’d like, but I’ve found a very good
balance and set up strong structures to make sure that I’m not spending the
lion’s share of my time putting out fires.</li>
<li>This is very much an engineer’s thing to say, but: Buttondown’s codebase is
in the best shape it’s ever been, thanks to piecemeal investments in
TypeScript, <a href="https://docs.pytest.org/en/6.2.x/">pytest</a>,{" "}
<a href="http://storybook.js.org/">Storybook</a>, and a host of other things. I
think this is actually pretty important: an enduring tactical advantage I will
have over my competitors is agility, and I think I’m entering 2022 with more
agility than I entered 2021.</li>
</ul>
<p>What did not go particularly well:</p>
<ul>
<li>
As mentioned in the financials bit: I think the free lunch of everyone
suddenly caring about emails again is over. The space is bigger than it was,
and it is growing, but the second-order derivative is flat. This makes it
newly urgent to focus on making sure that the folks who are trying out
Buttondown stick to Buttondown.
</li>
<li>
On a personal note, I fell victim to lapses in discipline throughout the
year. Some of this is me being overly harsh on myself (a lot of things
happened! I became an EM and tripled my team size! I got engaged! I’m
moving! I have a very cute corgi!); some of it is poor planning on my part.
There are entire months where I was too lazy to plan out ambitious work and
instead settled for bite-sized commits here and there.
</li>
<li>
The two habits I wanted to improve upon this year — more content-writing,
and more metrics usage — both fell flat. Whoops!
</li>
</ul>
<h3>Spoonbill</h3>
<p><a href="https://spoonbill.io/">Spoonbill</a> is an application for tracking social
media metadata. I built it in around 2016 and it's more or less been in maintenance
mode ever since.</p>
<p>
Absolutely nothing happened in Spoonbilland in 2021. I got three more
$99/month contracts and did not write a single line of code. Not bad.
</p>
<p>
(I continue to harbor illusions/dreams about relaunching and rebuilding
Spoonbill to be a general-purpose metadata delta tracker, but there is
literally not enough time in the day to even plan on such things.)
</p>
<h3>Miscellany</h3>
<p>
No new projects, no new engagements, no new consulting — all of which was by
design, so mission accomplished there. (The only thing worth calling out, I
think, is that I spent another year mentoring for{" "}
<a href="http://un-loop.org/">Unloop</a>. If you are a cushy-situated software
developer — and if you are reading this, then odds’ are that you are — I can
think of few better ways to spend one of your precious few spare hours in a
week than finding a worthy program and offering your time as a mentor. It is
both deeply gratifying and deeply valuable.)
</p>
<p>In 2022, I do want to build something new and bring it to market. I
miss the early stages of going from zero to one, and Buttondown is in
a better place ops-wise than it’s been in 24 months. I think this will
be a very modest project, in terms of ambitions — I’m thinking
something along the lines of $50K TAM — but I like the idea of adding
to my portfolio and having some fun along the way.</p>
<h2>Art</h2>
<div class="my-4 lg:my-8"><div class="border-subtler rounded-t-lg bg-subtler border-solid border border-b-0 uppercase font-bold text-sm py-2 px-4">Counts</div><div class="border-subtler bg-subtle text-gray-500 py-2 rounded-b-lg border-b border-x"><div class="flex px-4 border-solid"><div><div>Hours spent listening to lo-fi</div></div><div class="text-right flex-1 tabular-nums font-bold">446</div></div><div class="flex px-4 border-solid"><div><div>New albums</div></div><div class="text-right flex-1 tabular-nums font-bold">118</div></div><div class="flex px-4 border-solid"><div><div>Hours spent playing Slay the Spire</div></div><div class="text-right flex-1 tabular-nums font-bold">105</div></div><div class="flex px-4 border-solid"><div><div>New words learned</div></div><div class="text-right flex-1 tabular-nums font-bold">82</div></div><div class="flex px-4 border-solid"><div><div>Books read</div></div><div class="text-right flex-1 tabular-nums font-bold">41</div></div><div class="flex px-4 border-solid"><div><div>Movies watched</div></div><div class="text-right flex-1 tabular-nums font-bold">15</div></div><div class="flex px-4 border-solid"><div><div>Games finished</div></div><div class="text-right flex-1 tabular-nums font-bold">12</div></div><div class="flex px-4 border-solid"><div><div>Number of times I successfully guessed the murderer in Poirot </div></div><div class="text-right flex-1 tabular-nums font-bold">8</div></div><div class="flex px-4 border-solid"><div><div>Absolutely awful management books read</div></div><div class="text-right flex-1 tabular-nums font-bold">5</div></div></div></div>
<h3>Books</h3>
<p>
I read (and listened to — the lion’s share of these were audiobooks, a format
that I will continue to rhapsodize about if you corner me at a party) 41 books
this year, up a fair bit from last year. This wasn’t particularly deliberate;
I think it happened naturally as I phased out most of my podcast consumption
in favor of audiobooks.
</p>
<p>There was a lot I really liked. I’m finally an <a href="https://bookshop.org/books/infinite-jest/9780316066525">Infinite Jest</a> guy (email me with all your wildest theories!); <a href="https://bookshop.org/books/no-one-is-talking-about-this/9780593189580">No One Is Talking About This</a> is probably the only good “book about the Internet’ that I’ve read, and I did a much better job reading poetry than I did in 2020 (even if I want to do even more next year; I bought more than I read!)</p>
<p>My favorite book of the year — and my lone 10/10 — was a cliche one, to be honest. <a href="https://bookshop.org/books/the-path-to-power-the-years-of-lyndon-johnson-i/9780679729457">Path to Power</a> took me the better part of two months to wade through, but it was absolutely tremendous, and to me a few steps ahead of <a href="https://bookshop.org/books/the-power-broker-robert-moses-and-the-fall-of-new-york/9780394720241">The Power Broker</a>. I am wary of the need to even provide the summary — I suspect my target audience is all-too-well-aware of Robert Caro’s ouevre — but I don’t think I’ve read a non-fiction book with greater discipline, ambition, or poise. (And I’m excited to continue along with the series, albeit with some breaks along the way.)</p>
<p>
One of the more rewarding things about this year is how nice big-ass books
have been. I think I have subconsciously shied away from them in favor of the
slight endorphin rush of smaller books and the diversity of different voices,
but I’ve loved all of the huge books that have been on my list for five-or-so
years. They’ve all been worth the investment! With that in mind…
</p>
<p>In 2022, I’m going all in on comically large books. Brothers
Karamazov; War & Peace; Middlemarch; A Little Life; Means of
Ascent; Women & Men.</p>
<h3>Games</h3>
<p>
I wrote last year that more than anything I wanted to finish more video games,
and boy did I succeed. I finished a solid 9 games this year, and none of them
(besides maybe Gnosia, which was absolutely worth it) were “padding”; they
were all sizable, non-trivial games. I would recommend every single one of
them except Trials of Mana, which was the height of mediocrity.
</p>
<p>
I struggled a lot to choose between my two runaway favorites -{" "}
<a href="https://discoelysium.com/">Disco Elysium</a> and{" "}
<a href="http://www.celestegame.com/">Celeste</a>, both of which were GOTY
contenders in their respective years and absolutely, unquestionably,
unimpeachably great. A bit of each:
</p>
<ul>
<li>
Disco Elysium is a choose-your-own-adventure where you play an amnesiac
alcoholic cop trying to solve a murder in one of the most fascinating
settings of any I’ve ever played. The writing is — this cannot be overstated
— <em>the best</em>, as in there is no game with better writing in the
world.
</li>
<li>
Celeste is a viciously difficult platformer in which you play as the titular
Celeste, a young woman trying to climb a mountain. It is very sweet: the
plot of the game is very much about the joy and difficulty in doing{" "}
<em>hard things</em>, and the vicious difficulty is perfectly balanced with
an overwriting sense of <em>fairness</em>; the game does not trick you, the
game does not punish you, the game only asks you to be very, very talented.
</li>
</ul>
<p>
In the interest of choosing one, I’ll choose Celeste, which besides having -
for my money - the best platforming gameplay of all time, was a perfect fable
about the challenge and ecstacy of playing video games (I’m a sucker for
earnest & optimistic metareference!). That being said… absolutely play
Disco Elysium. (And Bastion, and Gnosia, and Steamworld: Quest, and probably
Cave Story, and I haven’t finished it yet at the time of this writing but
definitely Shin Megami Tensei V too.)
</p>
<h3>Movies</h3>
<p>
If I succeeded beyond my wildest dreams in playing games, I did a much worse
job of watching movies: only fifteen this year. Bluntly, I didn’t find any of
them brilliant! The two movies I rated the most highly at the time were Ponyo
and Holiday, both of which were slightly shallower echoes of all-timers for me
(My Neighbor Totoro and Bringing up Baby respectively). Beyond that, I have to
tip my hat to{" "}
<a href="https://letterboxd.com/film/in-a-lonely-place/">In A Lonely Place</a>,
my birthday movie (I watch a new Bogart every year), which was certainly not a
pleasant film but a quiet and beautiful one) and <a href="https://letterboxd.com/film/promare/">
Promare
</a>, a balls-to-the-wall kaleidoscope of pastel animation with a cliche-but-not-grating
plot that mostly acts as a minecar to take you from one delightful setpiece to
another.
</p>
<p>In 2022, I want to watch 50 films. I don’t like quantitative
approaches to consumption — you run the risk of doing the thing to say
you did the thing, not doing the thing because you want to do the
thing. But I miss movie nights with Haley and getting to spend time in
worlds outside my own; I can point to very few movies that I regretted
spending time with (which is absolutely not true of books, games, and
television) and while habits aren’t virtuous in of themselves I think
the lack of movies in my life this year was not due to distaste but
due to muscle atrophy.</p>
<h3>Television</h3>
<p>
Last year I watched roughly no new television. This year I did much more,
clocking in at a final 21 seasons of television! This mostly came from two
different places:
</p>
<ol>
<li>Haley and my watch-something-during-dinner gambit evolved from completely smooth-brained television (Frasier, Degrassi) to stuff we actually were interested in watching.</li>
<li>My brother and I started watching a whole lot of anime together.</li>
</ol>
<p>The second bullet point is where my favorites of the year lied; we started off with a classic (<a href="https://myanimelist.net/anime/339/Serial_Experiments_Lain">Serial Experiments Lain</a>, which was terrific in its own way but not my absolute favorite) and then bopped around some more modern hits. <a href="https://www.crunchyroll.com/oddtaxi">Oddtaxi</a> (and <a href="https://myanimelist.net/anime/7785/Yojouhan_Shinwa_Taikei">The Tatami Galaxy</a> close behind it) were triumphs of the form: a show that could have only been told in animation, delightful in a visual form, and being deeply resonant and fun in their own rights.</p>
<p>
(Also, shout out to Netflix for putting out some of the absolute worst content
I have consumed this year, though Emily in Paris and Friends from College
quickly earned value for hitting that negative underflow of
“so-bad-you-can’t-look-away”.)
</p>
<h3>Music</h3>
<p>
Last year I listened to roughly no new music. Much like with television, I
made up for it this year, clocking in at 119 albums in total. That’s around
two a week, which sounds about right: I didn’t have any grand methodology or
framework, just idly browsed Pitchfork and jotted down notes whenever someone
mentioned something that sounded interesting.
</p>
<p>
I listened to a lot of music that I loved, to be honest: five albums that I
gave the very scientific and vaunted personal rating of 10/10. If I had to
choose one, I would choose based on just how many damn times I listened to the
thing, which would be{" "}
<a href="https://djsabrinatheteenagedj.bandcamp.com/album/charmed">Charmed</a>{" "}
by DJ Sabrina the Teenage DJ (I know. The name is bad. I know. But trust me —
it’s fun, and a solid two and a half hours of sample-heavy boppy house)
followed by <a href="https://www.leavethebones.com/">Leave the Bones</a> by
Lakou Mizik & Joseph Ray (a modernist recording of Haitian tribal pop that
recalls the epic highs of the early eighties world trend.)
</p>
<h2>Coda / 2022</h2>
<p>
As you have probably realized, rather than saving all of my goals for the end
I peppered them throughout the page so as to have some level of context and
cohesion with the twelve months that preceded them. It’s tricky, with these
things, to fall into the disease of more: I want to write more, I want to lift
more, I want to build something new, I want to watch more films, et cetera.
That’s the easy part!
</p>
<p>
The truth is, I have operated the last nine months or so with absolutely no
slack. The vast majority of days this year were bereft of even thirty minutes
to relax and gather my thoughts. This is a bit of a bummer: the superstructure
I built out around my life might have been feasible in a world where I was
locked down and could have complete mastery of every single hour being spent
or whatever, but that’s a) inherently unsustainable; b) especially
unsustainable in a year where I changed my job and am once again subject to
the whims of a social life.
</p>
<p>
Really, the question that is worth posing is: what do I want to do less of?
Where is the time — and energy — going to come from? (And even that dichotomy
is interesting to me. One of the most interesting parts of being in management
now is internalizing that your time is no longer your own: something like 60%
of my time is given away to others, either in the form of meetings or in the
form of being in “reaction mode”.)
</p>
<p>
(An aside — It’s hard for me to stop doing things in general. One of the
downsides of habits is that we think of consistency as a virtue, even if its
consistency in something that’s value-neutral. I legitimately thought long and
hard about ending my 700-day Duolingo streak because even though I stopped
giving a shit about French, it felt bad to break the chain!)
</p>
<p>
I think the answer, then, is going to be a bit to-be-determined. But a quick
perusal of iOS’ “Screen Time” report reveals that there’s a lot of low-hanging
fruit.
</p>
20192019-12-29T00:00:00Zhttps://jmduke.com/posts/essays/2019/<p>2019 was pretty great. here are the highlights:</p>
<ol>
<li>found someone i loved</li>
<li>we moved in together</li>
<li>into a house that i bought</li>
<li>got promoted</li>
<li>at a job that i continue to love</li>
<li>worked very hard</li>
<li>hit 5k MRR across all my various projects</li>
<li>signed my first four-digit software contract</li>
<li><em>and</em> my first five-digit software contract</li>
<li>read (and listened to) a lot of books</li>
<li>stayed in relatively good shape</li>
<li>perfected my allspice old-fashioned recipe</li>
</ol>
<p>i only really accomplished one of my resolutions from last year (getting spoonbill to breakeven), which is...fine. i think resolutions work better for me as compasses rather than waypoints: i don’t need a specific GPS coordinate, i just need a handful of true norths.</p>
<p>a lot of 2019 (outside of my relationship with my partner) was about working pretty hard in lieu of more leisurely stuff. i was bad at texting people back; i was bad at playing video games. i don’t <em>regret</em> this exactly — it got me to where i was trying to go! — but i think this year i want to course correct a bit.</p>
<p>the truth is, i know i’m going to do a bunch of work stuff. it seems silly to plan that out and assign myself goals and KPIs or whatever — the work is going to be there regardless, and it’s going to get done.</p>
<p>for buttondown, that is mostly around getting it into a more scalable and sustainable place. i wrote last year about buttondown’s growth hurting my life in the way freemium apps tend to — as is the cliche, the demand (or burden, or whatever noun you care to use) of free users is pretty high, and without hiring a customer service person it sucks some joy out of my life. i’m going to move it to a purely premium model, work on improving some of the core experiences (namely the writing and editing interface), and move it in a <em>slightly</em> more legitimate direction with support for captcha, internationalization, and webhooks.</p>
<p>for spoonbill, this is mostly sales and product development stuff. i’ll be introducing “spoonbill for power users” (my friends have advised me not to actually call it that, but we’ll see), which will have support for real-time notifications, searches, and zapier support. and then add a bunch more social networks (if you know anyone at linkedin who is up for a friendly chat, let me know...). and then redo the email design because boy is it ugly.</p>
<p>and, of course, the third big side project in my life is my house. the house is a very cute craftsman with a hellish basement that just got flooded with a half-inch of water. i would like to finish the basement and redo the bathroom and figure out what we’re going to do with a guest-room-slash-walkin-closet. also the basement stairs are currently made out of plywood, which should...not be the case.</p>
<p>because i am sociopathic, i have all of these things planned out with due dates. but that’s more about limiting the analysis paralysis (buttondown has <strong>400</strong> open issues on github right now) and focusing my efforts on what I think will be a combination of highest impact and most fun to work on, rather than like, “oh I gotta hit $10k mrr this year.”)</p>
<p>i want to focus on the things that i otherwise wouldn’t do. in 2020, i want to focus on the things that i tend to be bad about making time or space for in my life but that i never regret doing:</p>
<ol>
<li>cooking and making cocktails (gonna try for one new recipe a month for each)</li>
<li>hosting friends (i am still okay with being bad at texting and communication, but i need to be better at replacing that with spending time with the people whom i care about)</li>
<li>running and lifting. i’ve been in a holding pattern with lifting recently (maintaining but not really improving my numbers) and i think hitting a two-plate bench would be fun.</li>
<li>drinking tea</li>
<li>reading poetry</li>
</ol>
<p>and if i do none of these things, that is completely fine. i have found myself the past two months aghast at my luck and privilege. this is the first time i have been so conscious (or self-conscious) of how good i have it; i think things can be even better, but if all twelve months of 2020 are like the worst month of 2019 i will consider myself prudent and blessed.</p>
20182018-12-29T00:00:00Zhttps://jmduke.com/posts/essays/2018/<p>Okay, let's skip the whole "gee, it's been a whole year, huh? this time last year I was..." and get into it.</p>
<h3>What worked</h3>
<p>So, all the good stuff first:</p>
<ol>
<li>I started writing poetry again. This is, honestly, the best: I forgot how much I needed a creative outlet, and how much sanity and satisfaction it affords me.</li>
<li>I started running seriously! I ran my first 10K (and then broke my PR a half dozen times, and am now addicted to middle-distance running.) . I am not a fast runner, nor a particularly graceful one, but crossing finish lines is consistently the happiest I've ever felt.</li>
<li>I started the best job in my life. <sup class="footnote-ref"><a href="https://jmduke.com/posts/essays/2018/#fn1" id="fnref1">[1]</a></sup> . Working at Stripe has put me in close proximity with more friendly, earnest, and talented folks than ever before, and I find myself learning and building awesome things.</li>
<li>I read the most I've read since graduating, a feat which necessitated (fortunately and unfortunately) becoming a big ol' audiobook nerd. (More about that later.)</li>
<li><a href="https://buttondown.email/">Buttondown</a> continues to grow. It's crossed the vaunted four-digit MRR mark, and it sends hundreds of thousands of emails every month. Neat! And terrifying!</li>
<li>I finally became a morning person and have some actual morning and evening routines. (It only took me being out of college for...five years.) . As I wrote in an email recently, my routine sounds like a parody of "here's what you need to do to be successful" puff pieces on Business Insider, but getting my emails and my workout and my various administrative obligations done by 8am every day has been a game-changer.</li>
<li>The blasé but earnest capper: I continue to be surrounded by loving friends and loving family, who have all stayed universally healthy.</li>
</ol>
<h3>What didn't</h3>
<ol>
<li>It is hard for me to remember the first three or so months of 2018, but it wasn't great <sup class="footnote-ref"><a href="https://jmduke.com/posts/essays/2018/#fn2" id="fnref2">[2]</a></sup>. I remember playing a lot of <em>Persona 4: Golden</em>, and lifting, and learning how to navigate a new personal space (being single for the first time in a while). I gained like twenty pounds. It was kinda rough, and I consciously knew I needed a change.</li>
<li>I still haven't really figured out the work-life balance thing, though I keep on trying. Starting in around April, Buttondown <em>took off</em> to the extent that it became a net-negative on my well-being in terms of how much stress it brought me versus how much joy it brought me. It's right around that midpoint again (thanks to an emphasis on nuking tech debt, improving operational efficiency, and just taking some time off) but I want to get it back to a positive place.</li>
<li>I didn't watch that many movies! I wanted to, but didn't. This sounds trivial, but it feels vaguely symbolic for me: I think the fact that I was consistently unable to take two hours out of my evening and go to the theatre is a failure case.</li>
</ol>
<h3>Books</h3>
<p>I read a lot of stuff I <em>loved</em> this year. My absolute favorites:</p>
<ol>
<li><strong>Emily Wilson's <em>The Odyssey</em></strong>, a striking and modern translation.</li>
<li><strong>Anne Carson's <em>Autobiography of Red</em></strong>, a verse novel about myth, pain and perspective.</li>
<li><strong>Italo Calvino's <em>If On A Winter's Night A Traveler</em></strong>, a beautiful and often hilarious postmodern treatise on the act of reading and succumbing to fiction.</li>
<li><strong>Emily Ruskovich's <em>Idaho</em></strong>, a lyric narrative about memory, loss, and absence.</li>
</ol>
<p>On my completely arbitrary reduce-all-artistic-merit-to-a-five-point-scale rubric that I've been using since ~2010, all of these are are fives. And I've only have ten fives in the past ten years. I think explaining why I find a given book life-changing is fairly arbitrary, but all four of these deal fairly closely with how we define ourselves in relation to our environment and our art which I think is neat!</p>
<p>The other stuff I read, in roughly descending order of how much I liked it (excluding poetry, of which I read a blessed forty-seven books this year):</p>
<ul>
<li><em>Invisible Cities</em></li>
<li><em>The Iliad</em></li>
<li><em>Sapiens</em></li>
<li><em>Kitchen Confidential</em></li>
<li><em>Circe</em></li>
<li><em>Hard-Boiled Wonderland</em></li>
<li><em>Sense & Sensibility</em></li>
<li><em>The Postman Always Rings Twice</em></li>
<li><em>A Gentleman in Moscow</em></li>
<li><em>London Fields</em></li>
<li><em>Little Labors</em></li>
<li><em>The Fire Next Time</em></li>
<li><em>I, Claudius</em></li>
<li><em>The Checklist Manifesto</em></li>
<li><em>Convenience Store Woman</em></li>
<li><em>Flash Boys</em></li>
<li><em>The Aeneid</em></li>
<li><em>Transit</em></li>
<li><em>Rabbit, Run</em></li>
<li><em>The Beautiful Struggle</em></li>
<li><em>The Coming Storm</em></li>
<li><em>Last Words from Montmartre</em></li>
<li><em>The Paper Menagerie and Other Stories</em></li>
<li><em>The Little Book of Hygge</em></li>
<li><em>Debt: The First 5000 Years</em></li>
<li><em>Meditations of Marcus Aurelius</em></li>
</ul>
<h3>Even More Content!</h3>
<p>I focused more on books than, uh, anything else this year, but the five non-book things that I truly loved:</p>
<ol>
<li><strong><a href="https://www.nintendo.com/en_CA/games/detail/gris-switch">Gris</a></strong>, a game for the Nintendo Switch about loss and growth. If you own a Switch, buy and play this game: it is like fifteen dollars and four hours long and very beautiful.</li>
<li><strong>Legend of Zelda: A Link To The Past</strong>. I think this is one of the few remaining games from my childhood that Adult Justin owed it to Child Justin to beat. It was lovely! (Though not as lovely as <em>Link's Awakening</em> in terms of nostalgia and childlike-sense-of-wonder.)</li>
<li><strong>Phantom Thread</strong>. This was in 2018? Wow. Still my favorite movie I saw this year, though.</li>
<li><strong><a href="https://open.spotify.com/album/07bIdDDe3I3hhWpxU6tuBp">DAYTONA</a></strong>. The greatest workout album of... all time?</li>
<li><strong><a href="https://open.spotify.com/album/3Azclf786vim7jMEXfDceG">Anne</a></strong>. My favorite album of the year.</li>
</ol>
<p>(Honorable mentions go to the new 1975 record, <em>Le Monde d'Edena</em>, and <em>What Work Is</em>.)</p>
<h3>Next Year</h3>
<p><em>I've got some resolutions.</em> I love resolutions. I never stick to them, but they're fun anyway.</p>
<ol>
<li><strong>Spend two weeks in Japan</strong>. I didn't travel outside the country in 2018 which was a huge bummer. I want to be a stereotypical white tech dude who visits (and falls in love with) Tokyo <em>so badly</em>. This is gonna be the year.</li>
<li><strong>Read <em>War and Peace</em></strong>. Twelve hundred and twenty five pages of Tsarist angst. This will probably take me.... three months, at a conservative twenty pages per day?</li>
<li><strong>Score 200 on the <a href="https://en.wikipedia.org/wiki/United_States_Army_Physical_Fitness_Test">APFT</a></strong>. This is probably my easiest resolution; I think I could handle it now if you give me an entire gram of caffeine and earphones playing <em>DAYTONA</em> at an adequately loud volume. Fifty pushups (in two minutes), sixty pushups (in two minutes), and a 15:00 two-mile time should do it.</li>
<li><strong>Run a half-marathon</strong>. This one is much more terrifying!</li>
<li><strong>Get Spoonbill breakeven</strong>. This is my only technology/career/hustle-related resolution, and it's less out of ambition and more out of, uh, financial prudence. Spoonbill has gotten me a couple nice write-ups and some lovely email exchanges but it's also been costing me around ~$150 a month for the past year, which threatens to double as the database grows. I want it merely at breakeven, which might be as simple as imploring some of the VCs who use it to donate to a Patreon or find some AWS credits or something.</li>
<li><strong>Publish poetry under my own name</strong>. AHHHH THIS IS TERRIFYING</li>
</ol>
<h3>Thanks</h3>
<p>It is easy, I think, to look at the slow whirlpool of global chaos and disparage 2018 (or perhaps all of the present time) as the year in which many bad things happened. But I am happy and better for having lived through of it, which is a privilege for which I'm thankful.</p>
<p>Salud! See y'all in 2019. (I'll try and write more, as always.)</p>
<hr class="footnotes-sep" />
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p><a href="https://stripe.com/jobs#openings">We're hiring.</a> <a href="https://jmduke.com/posts/essays/2018/#fnref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><p>I have terrible middle-term memory. Well, and short- and long-term memory, too. <a href="https://jmduke.com/posts/essays/2018/#fnref2" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
20162016-12-29T00:00:00Zhttps://jmduke.com/posts/essays/2016/<p>Welp.</p>
<p>2016 was a doozy.</p>
<p>There were lot of very good things. I started the best job of my life; I started living with my partner, and have never been happier; I'm in the best shape of my life. I listened to a lot of great music, and ate multiple salads. (This sounds boastful, because it is, but I'm trying to be more honest about victories and defeats.)</p>
<p>There's also, you know, the macro part of 2016. Where, to be brief (because I'm not capable or insightful enough to put an interesting spin on things), <strong>very bad things happened.</strong></p>
<p>2016 has made me feel good and bad. The good parts felt standard; the bad parts felt wholly new.</p>
<p>It is hard to reconcile a personal, immediate happiness and contentment with a shattered faith in institutions and basic understandings about the world.</p>
<p>So, largely, I haven't tried to!</p>
<p>After a few weeks of general existential malaise I've worked harder than usual. Part of this is coping mechanism -- work to distract from <em>actual things</em> -- but largely this is because I think this felt like the most natural way to produce something of value, to try and offset the bad things, to compartmentalize. <sup class="footnote-ref"><a href="https://jmduke.com/posts/essays/2016/#fn1" id="fnref1">[1]</a></sup></p>
<p>Anyway, every December I sit down in my grandparents' den and think about the year that's passed and think about the year ahead. So that's what I'm doing, because solipsism can be productive.</p>
<p>And this year, I don't really have any takeaways or through lines? I know what was good. I know what was bad. Further analysis seems pointless.</p>
<p>What I want to do more of next year is the same thing I tell myself every year: read more, write more, and work more meaningfully.</p>
<p>Goals are supposed to be concrete and measurable, so you can actually compare your progress against your objectives and all of that. So here are the specific things:</p>
<ul>
<li><strong>Read 24 books.</strong></li>
<li><strong>Publish 500 words a week.</strong> <sup class="footnote-ref"><a href="https://jmduke.com/posts/essays/2016/#fn2" id="fnref2">[2]</a></sup></li>
<li><strong>Hit $2000 in monthly recurring profit.</strong> <sup class="footnote-ref"><a href="https://jmduke.com/posts/essays/2016/#fn3" id="fnref3">[3]</a></sup></li>
</ul>
<p>I know these goals are cliche. I don't think I'll make them (though I hope to). It just feels necessary -- to have some sort of North Star, to try and be better.</p>
<p>I hope you are well. I hope you have a good year.</p>
<hr class="footnotes-sep" />
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>It goes without saying that there is <em>tremendous privilege</em> in the ability to compartmentalize at all. I'm an agnostic white dude in tech: none of my immediate comforts or livelihoods are in danger. <a href="https://jmduke.com/posts/essays/2016/#fnref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><p>I know a week is a cop-out, since most people do that every day. But I'm more mindful of what I write at this point, and also there's <em>no way</em> I carry through with 500 words a day. <a href="https://jmduke.com/posts/essays/2016/#fnref2" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn3" class="footnote-item"><p>I've been writing more about my side business, Village Blacksmith. You can see the monthly review I wrote in <a href="https://jmduke.com/posts/essays/2016/villageblacksmith.consulting/november-2016">November</a>, and I'm in the process of writing December's. <a href="https://jmduke.com/posts/essays/2016/#fnref3" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>