May 13, 2019
Money, part two.
I wrote last week about some basic cost-cutting measures:
If this works (the main risk is that there’s some large gap in how I’m doing email address validation), this should drastically change my margins, which would be neat. Buttondown is profitable already, and now it would be, uh, more so — specifically, it would give me a decent cushion to play around with some paid acquisition channels (read: burn a hundred bucks or so on AdWords).
One week in, and this has gone well! I switched one more person over to pass-through payments for Clearbit (the abstraction for which is rough, but functional), and a rudimentary analysis of incoming subscribers shows that my bespoke series of validation hoops is only allowing ~.7% false positives. (There are a lot of compromised hotmail.com
addresses.)
Something I am idly thinking about is how to pour some of this money into open source. Buttondown would not exist without open source technologies: Django and Vue, sure, but also a long tail of duct-tape packages like things that make it easier for me to test network calls or just to run cron jobs easier.
I think my ideal way this would work would be if I could set a percentage of profit to go to a general jackpot of software I use, and then it’s dealt out automatically; the less I think about it, the better, and then I could materialize the costs as what they really are: software expenses for the upkeep of the tool. But I need to think about this more.
In more vain news: eight new Weeknotes subscribers, two new Buttondown for Professionals signups, and a new ~$45/mo in newsletters who have new accounts thresholds. I need to figure out how to track and visualize this in a more efficient way than “logging into my various accounts and being pleasantly surprised at the green numbers.”
Migrations are tough!
I wrote that, amongst other things, I struggle with migrations. (A migration, if you’re unfamiliar, is a large-scale change to a database.) I think that I blame this struggle on Buttondown’s odd size: it is large enough that I can’t just run arbitrary database queries in a matter of seconds but not so large that I need to treat every migration as sacrosanct (at a Stripe-scale company, say, you’re using special software for migrations, which take days and have very important and reasonable restraints and checks to make sure you don’t destroy anything.)
Take this migration, that I executed last night to migrate the concept of “subscriber” from something that belongs to a user to something that belongs to a newsletter (this, as you might guess, is necessary for my multiple newsletters-per-user work):
@transaction.atomic def backfill_newsletter(apps, schema_editor): Subscriber = apps.get_model("emails", "Subscriber") SubscriberTag = apps.get_model("emails", "SubscriberTag") models = [Subscriber, SubscriberTag] for model in models: for i in model.objects.all(): i.newsletter = i.user.newsletter i.save()
This is, of course, a terrible migration. I have millions of Subscribers: iterating over each one is going to take a comically large amount of time, which is something I didn’t properly internalize until I watched it spinning for fifteen minutes.
I ended up going with a much more performant solution, which does the exact same thing in a matter of seconds, since you’re operating on a number of users rather than on a number of newsletters:
@transaction.atomic def backfill_newsletter(apps, schema_editor): User = apps.get_model("auth", "User") for user in User.objects.all(): user.subscribers.all().update(newsletter=user.newsletter.id) user.subscribertag_set.all().update(newsletter=user.newsletter.id)
This isn’t rocket science. This isn’t even… I don’t know, introduction to chemistry. It’s just not something that I’ve built out the muscle memory in thinking about, and yet I dread running migrations because they go poorly so often.
Comms.
While I am on the topic of things I do poorly — this is the first week since January’s spike in growth that I’ve felt really on top of communications + customer service, and even then I missed a half-dozen questions or requests. I blame some aggressive Gmail filters set up, but the real blame lies in the fact that I’m using Gmail as my customer support tool still; I need to figure out something a little more sustainable, but I’m hesitant to invest in a solution like Intercom which has a weight and a cost associated with it.
(I don’t think there’s anything I take more seriously than this — I’ve had a number of upsells convert specifically because they had a good customer service experience, and the one churn I had in February due to bad service was probably the worst I’ve felt about Buttondown in a year. Plus, responsiveness is just an innate good.)
The to-do list.
Last week, I opted for volume over focus:
This coming week, I’m gonna go with a little bit different of a goal for myself: a seven-day streak of two hours on serious development. It’s still the early part of the month, where I’m less worried about getting some nice juicy deliverables out of the door, so this seems like a good opportunity to be focused less on outcome and more on process.
This actually worked well!
I’m pretty bad at measuring my own velocity, but it felt good: a couple dozen commits and I managed to keep on top of customer service (which, aforementioned, has grown increasingly tricky.). Most people have read the Seinfeldian theory of productivity at this point, so this comes as no shock, but process builds outcome which is nice to see vividly realized, even if it’s only a week.
I’m gonna aim for the same cadence as last week, but with a couple specific deliverables this time:
- I’d like to ship the last backend piece necessary for multiple newsletters, which is migrating the User → Newsletter relationship from a one-to-one to a one-to-many. (This is actually trivial, and doesn’t involve any database changes, due to how Django handles one-to-ones; just means a lot of rewriting
user.newsletter
asuser.newsletters.first()
. - Complete a short sprint on email addresses and email domain validations. Self-serve SPF/DKIM records have been incredible in terms of lightening my load, but there are a number of edge cases and quibbles in DNS that I’m handling poorly.
- Ship a marketing page. I have not done this in....literally ever? And I have no idea what effort this will entail, but I need to start building a few muscles here, so some sort of basic thing here will be a good jumping-off point.