This week was split in twain.
I spent the first half in Seattle, as I tend to do, plodding away on the ListView work that I’ve spent the past two weeks chatting about.

It is… going well! The painful scope creep I mentioned last week has mostly been dealt with; I’m dealing with the long tail of various features that needed to be port over, and I’m firmly in the ‘downhill’ part of a scope creep-y project — when the punch list might be long, but I’m confident it’s finite, and the hours of “checking off one item will inevitably spawn six more” are behind me.
First, I suppose, the good news:
Email filtering, such as it is, is done. My goal with the item is complete! My DataTable component has expanded and I now have a ListView component that takes a bunch of stuff, and the Archive page at /emails exists solely as a wrapper around the ListView. (Plus I added filtering, the whole point of the exercise — and the most trivial one.) There are a couple tiny things I need to do before merging, like better type information and fixing where the dropdown menu appears, but it is Done.
The problem, of course, lies in an email from Anne I received in response to last week's weeknote:
This looks nice, but it's different than the current look and feel of the other pages, right? How do you think about rolling this out and not having it be consistent?
I started my first piece of the Q4 roadmap yesterday. It might not look like much yet, but I am very excited about the following screenshot:

Why am I excited? Three reasons:
<list-view /> component, which itself contains a <filter-widget /> and a <data-table /> (both of which are already live throughout the app, albeit in a slightly different style) and so on. The goal here is consistency: I want to get to a point this year where I have one main way to create a list of items, and to next quarter have one main way to create a detail page for an item, and so on. None of this is novel, but Buttondown's finally at the stage where it makes sense to invest here. Plus, it's just fun to program.foo" and "was premium-only then moved to free as a lead magnet", that sort of thing. It's that pill paradigm with the unsightly green — and, like I led off with, it doesn't look like much, but I'm excited at being able to collapse a fairly complex state space into a terse, readable badge.No big news, since I spent most of this past week trying to clean up the slate for the Q3 -> Q4 transition. A smattering of updates this week, then:
.new domain. Buttondown's marketing & positioning has really matured around being "just a newsletter tool" — no ecosystem, no multichannel, just newsletter-ing, and this fits that vision well.I whined last week about having a fairly unstructured Q3 despite my lofty goals, and I think Q4 is going to (hopefully?) be a bit better. You can take a look at my (unpublished/unfinalized, but mostly all there) Q4 roadmap — repricing and then a whole bunch of small follow-up work.
It feels a little bit like cheating to shift my gaze from Q3 to Q4 when I've got a handful of things that aren't quite done yet. The subscriber-facing views are technically "live" now, but I need to start actually ramping up the flag and prompting folks to update.
(What are the subscriber-facing views? This is a long-awaited reversal of the single worst technical decision I made when building Buttondown, which was tieing the subscriber-facing views [public archives, subscribe pages, that sort of thing] to the same Javascript codebase as the author-facing views [writing, subscriber management, etc]. That has made it tremendously difficult to iterate on either without breaking something.)
Still, that's less "coding" work and more "emails and feature flags" work — much less scary. A friend asked me why I didn't just YOLO it and migrate everyone over, and I had to respond with the truth — Buttondown isn't quite at the scale where I can do that and be supremely confident nothing will break (people are doing weird things with their pages these days!), plus there's all the custom CSS that would break. It is a bit of a bummer not being able to unship the legacy views quite yet, but I have confidence I'll be able to do that before the end of the year.
Where have I been for the past two months, you ask?
I have been active, I promise! I’ve been around!
I write a lot about the “good type of busy” and the “bad type of busy”. This was mostly the good type of busy: lots of new folks to onboard, lots of fires to put out.
It is funny, thinking about fires: I was reminded, cleaning up a part of the codebase from 2017, about the sheer I had the first time I had someone reliably sending out 10,000-subscriber emails on a weekly cadence. Some of the terror was, frankly, rational: one bad send with a few hours’ delay and I would have terrible blood pressure for a week. Email is tricky: it is temporal, it is irrevocable, and it is a scary thing to screw up.
Hello from the East Coast! I’ll be here for the next four weeks, which will be an interesting stress test on how productive I can be without my routines & contexts (to say nothing of the whole 34” curved monitor and desk setup.)
A week later, and I have written… well, I’ve written some content for the docs site. I have made good progress in general on the site, and I think it’s about ready to launch even if it’s not ready to call done — all of Notion’s content has been ported over, I’ve added the last bits of scaffolding such as a table of contents and stronger search metadata (as discussed last week), and it’s in a good place to just start iterating on.
I’m working on the hairiest part, technically speaking, of the documentation site — which is search functionality! This is slightly trendy at the moment — all the coolest kids are adding FTS to their docs — but is also tremendously useful (and replicates one of the really nice things about Notion hosting).
There are… a lot of options in terms of powering client-side search.
It is OKR season at my day job and so it is OKR season in Buttondown as well. I think I am actually making a pretty good go of it, this time around:

Two meaty projects (maybe a month each?), two smaller (maybe one-to-two-weeks each?) projects.
If you weren't around six months ago, I did a similar undertaking at the end of last year, with the ambit of it being a year-long plan and not simply a quarterly one:
Anyway, my goal for this week: pushing out some customer portal changes! My goal for the end of the week is to allow all premium subscribers to pause, upgrade, and downgrade their subscriptions: mostly this means a lot of transactional emails and web hook unit tests left to write.
I did it!
And, uh, not too much else. It was a week of emails (things are still fairly downbeat, but it happened to be a particularly voluminous week, the kind where the raw count is static but the “amount of thinking per email” spiked up), setting up a new laptop (I love my Mac mini to death, I really do, but the M1 MacBook Air feels almost comically performant in comparison. I bought it on a lark knowing that it was hard to justify, and yet I think I’ll be spending more time coding on it than on my Mac mini!), getting some nice press (I landed a fairly meaty mention in the , which is the kind of thing that I don’t know impacts me in a serious way besides SEO juice but certainly feels good!).
This coming week, there’s something vaguely time-sensitive on the horizon! Heroku is changing how they handle DNS registration for custom domains, which means I need to send out a bunch of relatively urgent (“take this action by July 31st…”) comms to everyone using custom domains. I’ll also use it as an opportunity to tweak how I’m handling the Heroku flow, since it relies on a couple bad assumptions (that get invalidated after this Heroku change) and is flaky in a painful way. So that (plus Roadmap V2, as aforementioned) should be on the ship list this week: though it’s going to be a long weekend and the start of a new month, which means I’ll be a little more spread thin than usual.
Hey, I did both of these things!
First, the Heroku changes. These were largely boring and good; I got to clean up some tech debt and improve the custom domain onboarding interface as I built it out, which is always nice. Exogenous event as impetus for tech debt pay down is rare, but I have to celebrate it when it happens.
That being said, it’s time for some dessert work! Maybe I’ll treat myself to cleaning up some of my dropdown CSS or messing around with the search bar UI, which has somehow already becoming stale. Either way, I’m going to grant myself a bit of a reprieve from the weekly goal: as long as I push the roadmap changes live, that’ll be good enough.
Well, it is 6.20pm on Sunday as I write this, and I have two items left on my list:
It turns out the trick to completing a daunting project is roughly threefold:
Sorry for the radio silence last week. A double-dosed Weeknotes this time around, as recompense, focusing on two fairly divergent workstreams — one that has been occupying my coding time and one that has been occupying my thinking time.
I’m desperate to onboard to Headless UI, a very useful set of components that let me unship a lot of cruft (I have home-baked popovers that are awful, I have home-baked modals that are awful, etc. etc.)
(Fool that I am, I wrote this on Sunday and checked it off of my todo list without actually scheduling the dang thing. Apologies!)
All of that is to say, I think the DNS work is a worthy goal for the week ahead. Once that lands, I think I’ll be in a good spot to spend May on dessert work: JavaScript exfiltration, support for attachments, that sort of thing.
My streak of (somewhat successfully) calling my shot continues! Here is the "after" of the DNS onboarding pane:
This coming week, though — bug fixes. For real this time: I need to ameliorate some of the incoming customer service and bug fixes are the way to go. Honest! I’m building out a Things project and everything!
Hey, I did this! I picked out the five most noxious bugs (in terms of “time I have to spend answering questions around them” and also “embarrassment that they are extant bugs”) and by Sunday I had fixed four out of them. The fifth is — perhaps, dear reader, you are familiar with this one! — the whole “cursor randomly jumps to the top of the textarea” bug, which I am still as of yet unable to reproduce. There is even an entire blog post about this genre of bug, which was both heartening (I am not the only one!) and disheartening (this may remain forever unsolved!)
I chanced upon my 2021 goals doc again. It was nice to read through — it is unfair to call it borne out of naivety, because a lot of the work was sound and sage, but the context of December 2020 does not match the context of April 2021. One of the unsolved problems I called out in the doc was DNS onboarding woes, which feels...like a thing I want to solve sooner rather than later, as it’s probably something like 40% of my onboarding effort at the moment (and also a significant pain point for folks, particularly the less technically savvy.) What “improving onboarding” looks like is a little murky, and I think it’s easier to start by iterating out a list of common hangups:
email.domain.com.domain.com.I wrote last week that my Big Goal for this week was to take care of the three most noxious bugs I deal with and to, well, stay sane. Neither of these things happened! It was a very busy week — lots of support requests to answer, lots of day job work, lots of accumulated fatigue — and I mostly did my best to keep my head above water.
When I have tough weeks, I fall back into a familiar holding pattern: working on things that seem neat rather than important or urgent. This week’s subject of neatness is at least one that I’ve been discussing and thinking about at length lately:
On the face of it, this didn’t feel like a productive week. I did get two things of note done:
checker infrastructure I mentioned. I punted on some of the trickier (and less important) architectural questions, like how to declare reactions to check failures; I realized it was more important for me to just get the infrastructure live and working and that more nuanced iterations would only come with experience. (To wit: there have been a lot of fast-follows, such as me immediately adding the ability to track how long a check has failed or being able to re-run checks through the admin interface.)Pay-what-you-want is live! There are still some fast-follows that I want to add, as alluded to in the blog post, but overall I’m satisfied with things. (In the...48 hours since public launch, I’m sitting a little shy of $5,000 being committed through PWYW, which is higher than I expected!)
It is always tricky to know when to call it a day here. I could have spent this week working on fast-follows, and the fact that so many of them have been explicitly called out in questions I’ve received post-launch is a sign that maybe I should have spent the extra day documenting upsell CTAs or adding more details to the post-upsell email. All of those tasks feel granular and snackable, though: they don’t require the dedicated level of flow and focus that actually hunkering down and shipping PWYW did.
I decided to celebrate with a bit of dessert work: rebuilding the checker architecture. My checker architecture as it stands is grim: I have a cron that runs twenty different methods simultaneously and emails/pages me if any have interesting failures. This falls over for the obvious reasons: some checks are flaky, uncaught exceptions will trump any subsequent legitimate failures, etc. So I embarked on some work to make it better: setting up the obvious architecture and data models and shifting it to something more decentralized.
One interesting bit I’m grappling with here: I’m explicitly building this as a separate app with the goal of being able to open-source it down the line. I don’t have a great grip on how to structure Django apps, but I do love that it acts as a forcing function to make you think about what your interfaces and external surfaces should be. I settled on this decorator-registry pattern that I’m quite fond of:
I got a surprising amount of work done in the past week on pay-what-you-want! This is good. This kind of feature work is not what I am particularly good at lately: the ratio of “staring and thinking” to “coding” weighs heavily in the favor of the former, and I am guilty of getting bored of staring and thinking and trundling off to some well-scoped problem that I can just crank out in a few hours.
In fact, if you wanted to, you could enable PWYW right now — it’s live in Paid Subscriptions. This is also the kind of feature where me saying “oh I’m done!” presages the majority of the work. Wiring together the billing code and the settings code is the easy part: then it’s like, oh right I need to update four FAQ pages and write a blog post and most of the subscription interface is built on the assumption that everyone is on the same price so I need to add more details to the NotesModal and also the subscription confirmation email and I’ve been to revamp the premium subscription email to look nice and have GIFs so I might as well roll that into this work and oh this means I need to start slurping up custom plans from Stripe to populate the various modals and emails so — [exhales].
There are quite a few apocryphal quotes around “overnight success taking ten years”, and that’s kind of what this past week felt like: it took me I think around a year and a half to get my first $1,000 MRR on Buttondown (still one of my proudest moments as a developer ever!) and then things happen and I get a marginal $1,500 MRR in the span of a single week. Phrasing this as “MRR went up nearly 25%” is less visceral and more directionally accurate, but also much less fun.
Mostly what that meant was randomization: I wrote last week about my humble ambitions of puttering around on some small paper cuts and tech debt tasks while I set my brain to work thinking about a revised analytics stack in the background. Neither parts of that came to pass: I spent a tremendous amount of time on the operational and onboarding side (pulling in archives is easy; matching existing Stripe subscriptions is less so) and what little time remained was on figuring out what a new revised roadmap would look like.
“Change your roadmap entirely based on an influx of one specific customer demographic” is kind of a giant warning sign, and I don’t think I did anything too drastic: the on my new list are things that I slated for this half anyway, they just seem suddenly new and more important.
Last week, I wrote:
There is some bug bashing that needs to take place, but I think finally tackling multiple-newsletter creation in a less janky way than “log out, create a new newsletter, and paste that API key in” feels like a good candidate. (And no, of course that’s not because I received two support emails about it this morning. Why would you even ask that?)
And I appear to be in the position now for the second week in a row that a chunk of work I pegged at being a solid week’s worth of work ended up being about an hour’s. When I thought about it in the abstract, “newsletter creation from the perspective of an already-registered user” felt hairy and complex and prone to lots of yak-shaving. When I actually sat down to work on it and start figuring out the data modeling, it turns out it’s quite simple: newsletters need a name and a username to be created, and that’s it. The rest can be handled just by the existing settings interfaces (which, don’t get me wrong, need some TLC, but that’s an orthogonal problem) — there’s no point recreating all of those form inputs just to have them in a multi-stage modal or whatever.
This seems to be something of a recurring theme for me. I have a good sense of things that are Definitely Big Projects (repricing; relaunching the marketing site) and things that are Definitely Small Tasks (tweak administrative panel to prioritize exact matches on string search; optimize ORM usage for BulkSendReminderView and add pinning test) but the messy in-between is where I can get into trouble. This is symbolized (and likely exacerbated) by how I keep track of tasks: I explicitly have. “projects” list and a “tasks” list, and the stuff that is in the middle is hard to scope and slot into my schedule.
Last week, I mentioned that I was gearing up for a week of frustrating (and hopefully rewarding) Heroku spelunking: dusting off the proverbial (and I guess literal — I had to update a couple dependencies) toolbox and going to town.
…
It was a one-liner, I think.
Not even a one-liner, the kind of one-line change that you arrive at with a sense of exhausted satisfaction after many hours down in the Performance Mines: a stupid, idiotic one-liner, a “oh my god of it’s this issue, why didn’t I just look at this sooner”. The in this case was a timeout set by , the web server I run around Django — it was set to kill any requests after , whereas Heroku had a hard timeout of .
The coy way to phrase how this week went is that I blew my Heroku budget out of the water. I woke up on Friday morning to discover that I had been Fireballed, something that my 20-year-old self would have been much more thrilled about but is still none-the-less very cool and very valuable. Getting a low-orbit cannon of traffic thrown at the site is useful in two dimensions:
One of the things Gruber called out in the post was that Buttondown kind of cops out a little with privacy opt-out (as opposed to tracking opt-in):
This has been quite the week!
I’m in the final stages of a pretty fun high touch sales deal. I spent twelve hours assembling IKEA furniture in my basement. I shipped Importing V2. My dog got neutered. I’m in the final stages of delivering on a very fun and philosophically well-placed partnership with an analytics provider.
(Okay, some of those aren’t related to Buttondown.)
My latest of reasons for being bad at writing is the most adorable yet: Telemachus, my ten-week-old corgi puppy. He is a very sweet dumb fluffball, with some strengths:
I was relatively sunny and awash with optimism last week. I owe a lot of this to a new strategy I was trying out to preserve focus, which was to ignore my inbox except in the mornings and evenings: rather than keep the inbox open in a tab all day and respond reactively (and get randomized) by emails, I was going to just batch-respond twice a day and ignore my email otherwise.
This was working very well until Thursday morning, on which I awoke to....72 emails, what I think is a new record. These weren’t all bad emails — some were pleasant compliments, some were error notifications, some were interview requests. But a lot of them were serious and required work, and it was all a little overwhelming.
And for those of you reading this email who might have been in the cohort of 72, I promise you: I love you, and don’t bedrudge you at all! This is a me problem, not a you problem.
Buttondown has been slow and flaky recently. I feel pretty confident that this is due to increasing volume: the ‘odd’ HN hug has become a daily occurrence, and the load coming from Mailgun/SES, while spiky, scales linearly with traffic. This is manifesting in a painful ways: spats of timeouts and dropped connections.
Previously, this has been a problem I’ve scaled my way out of: Buttondown sits on a half-dozen Heroku boxes and then I bump that up to a dozen until things stop complaining. That steady state has shifted, though: instead of going from 6 to 12, I’m finding myself going from 12 to 24, and even then there’s a lot of pain.
This is not uncommon, and there’s a playbook for this:
So, first, the exciting news. (Well, exciting news for me): I’m taking next week off!
This, of course, means I’ll be spending more time than usual on Buttondown, retreating to the figurative backyard shed.
I was poking around my roadmap finding a couple interesting tasks to work on with my time. I think I’ll be able to fully ship internationalization and the remaining marketing work, but wanted a nice meaty project for the back half of the week: revised billing? Multiple users per account? A redesigned writing interface?
For once I was true to my word, and I launched.
In no real order, some takeaways thus far:
It is so tempting:
And if we scroll up a bit, we see just how tempting (and scary, tbh) it is:
Feast upon these glorious screenshots:
Apologies for the late weeknote. This has been a low-energy, high-email week, where I would look at the end of my to-do list, see "write Weeknote" alongside three other things, and sigh to myself as I opt to do precisely none of them. Sometimes you need to have low-bandwidth weeks!
I'm reminded of one of my favorite pieces by Patrick McKenzie, my coworker:
Through sickness, health, and mind-numbing tedium, I’ve woken up every day for the last four years, checked email, gone through the day, checked email, and gone to sleep. This is the single best guarantee that I would deliver on the promised level of service to customers — almost all questions answered within 24 hours. There have been many, many weeks where this is literally all I’ve done for the business.
I've tried a vast number of tips and tricks over the years to account for the weeks when I have relatively low energy, and some of them work for a little but I don't think there is a lifehack that solves for sometimes you have more energy and sometimes you have less, and that is okay.
First: hello from my new iPad keyboard. I’ll spare you the requisite bloviating about yet another piece of Apple hardware, but I quite like this one, and it means I can be a little more efficient shifting more and more non-development work away from my desktop. Notion works well; Mail works well; Zendesk works well. And thanks to Juno, I can even do some limited diagnostics/data exploration work!
Here’s a thing that I’ve known to be true for a while but have found to be particularly true in the past few weeks, as all element of my life bleed together: two hours is not the same thing as four chunks of thirty minutes.
I spent this weekend refactoring how Buttondown handles archival imports!
First, what are archival imports?: if you bring your newsletter over from Mailchimp or Tinyletter (or, now, Substack, but we’ll get to that later) I have some janky logic to import your archives. This was, prior to this weekend, done in the span of an HTTP request, which was, uh, non-ideal: if you had a large (or even a not-so-large) archive, your import would probably time out and/or silently fail, leading to a customer email, leading to me importing your archives manually, which was pretty easy but definitely frictional.
A good rule of thumb I learned a number of years ago is to get the number of “things” in an HTTP request as low as possible: “things” are database calls, sure, and third-party requests, and file reads, and pretty much . HTTP requests should be granular, instant, and fail very rarely — all the important stuff associated with them should happen after the fact. My archival imports broke this rule with wild abandon.
Buttondown has a new Twitter handle, which also marks my first (and, if I were to bet on it, last) foray into social media account purchasing.
I had had @buttondownemail for a while and it was, you know, fine. The success or failure of an enterprise is not determined by the concision of its social media identifier, you know? But, because I am foolish, I wanted @buttondown, and had a lingering to-do item to try and track down the owner.
The account appeared to be in use, but it was hard to tell: there was a name ("D. Laackeman"), a location ("Vermont"), and a handful of followers and following behind a protected account. I did some sleuthing, found someone who matched the profile on LinkedIn, and slid into their proverbial DMs.
A short update this week, which perhaps belies the amount of work I got done last week; I think I managed to check off a roadmap item every day for the past seven days, which feels great. Some of the quarantine insularity is definitely getting to me, though — I’ve felt pretty dang tired today and yesterday, and am taking a mental health day tomorrow to noodle around in the house and do some reading.
(It is hard for me to remember how and when to rest, sometimes. Working from home exacerbates that.)
I have spent all of two weeks with a public roadmap. Some notes thus far:
I'm writing from Seattle, which you may potentially know as "the COVID-19 epicenter of the United States." I promise this will be the last mention of coronavirus in this newsletter, but I used my last "ignore social distancing at one's own peril" pass this past weekend to go to a ghost-town version of IKEA and upgrade my home office to one that fits both me and my partner:
This falls squarely into 'Ikea hack' territory, a content genre unto itself: our double desk is not so much a desk as it is three dressers and two kitchen countertops, a tactic we purloined from others. It is very pretty and very nice and I am more excited than ever to spend mornings and evenings in this lovely space.
Sorry for the, uh, [checks calendar] three month interruption in weekly Buttondown coverage.
I wrote some of this on Twitter, but it has been a period of vague and guilty restoration. I am not sure whether or not to call this writer’s block or developer’s block: I was fatigued, and busy, and could not muster the energy to do much of either.
I am back from San Francisco! It was filled with Philz and, as always when I am removed from Seattle for too long, a deep sense of longing.
Instead, this week’s sense of chaos (or, a word that has alarmingly emerged into parlance, randomization) comes from home, where I am hosting my brother and parents. Family is, like any good thing, draining and rewarding.
I integrated a new scimitar into my weapons rack of anti-spam measure: Project Honeypot, which is a crowdsourced database of malicious IP addresses. It looks like this has around a 70% crossover with my other main repository of bad actors, CleanTalk, which is nice.
Take a look at the work it's done so far:

I am doing something that I have never done before with Buttondown, which is, uh, planning. This is all very uncharted and exciting. Here is what my plans look like at the moment:

All of this is subject to change, obviously, and there’s the very large and looming possibility that I get two weeks into January and decide to ditch the entire thing.
Some people go out on Friday evenings; I was one of them once, letting off some steam after a long week with a coterie of cocktails and the effervescent Belltown nightlife.
Other people sit inside, unwinding with some tea (my partner has got me on this tea-with-honey kick, and I can honestly not tell the difference taste-wise but it’s certainly more hygge) and a blanket and a pleasant movie or RPG.
I, of course, impulsively launched a new landing page:
What have I been doing in October (besides neglecting to update this newsletter?) Well, marketing things. Or things that, if I squint at them, I can call them marketing things.
Let me back up a bit. I’m in a new house, and one of my dreams for my starter home was to get a personal home gym. I don’t need much — a power rack, a bench, some dumbbells — but my basement is kinda janky in terms of layout and ceiling height. I found a compact rack by Rogue (costs around $3,000 and probably a handful more in terms of finishing the basement so I can do things like drop 315lbs without being terrified I am scarring my estate forever) — right at the same time I found a YMCA (costs around $50/mo) five blocks away from my place.
Hello after a few weeks away! I spent two weeks in Japan, which was a trip of two fronts:
Sorry for the hiatus last week. I bought a house. And moved into the house. This was delightful, and stressful, and I learned that I own entirely too many Magic Keyboards and 30-pin charges. (And entirely the right amount of books, which happens to be a quantity infeasible for moving.)
Being offline most of the week and weekend was tough, to be honest; it is not the most exciting thing to open a weeknote on, but I had two churns from fairly excited/well-fitting users that amounted to “you’ve been unresponsive for two days while I’m having a serious issue, boy bye”. This is fair! And it’s a bummer, and a systemic flaw — one that I know I harp on.
I wrote a couple weeks ago about Project Eyeglass, my attempt to build out an administrative metrics dashboard for Buttondown. This was going to give me a more data-driven view into things I should be caring about at this stage, like growth and user activity over time.
I built what I like to call a dessert feature on Friday — Instagram embedding.
I’m in the process of working out a two new business models for two new customers. Neither may come to pass, but they’re both fairly interesting in their own right: