~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Thinking different about technical debt

Technical debt is typically understood as the consequence of shortcuts programmers take to meet a deadline. At some point in the future this debt is repaid by cleaning up the code, or so the basic finance analogy goes.

Technical debt, much like debt in the real world, is not all created equal. Some types of technical debt come with a high interest rate and put serious constraints on what you can do in the future. Debt like this can easily ruin your startup. Other types of technical debt just exist, harmlessly, like a 30 year mortgage bearing a 1% interest rate.

You want to distinguish between the different kind of shortcuts you can take and the ways in which the debt can bite you. In this post I expand the use of ‘technical debt’ so all design choices that create future obligations are considered debt. You can accumulate debt to the point where you spend all day every day dealing with these obligations. You have to work longer and longer hours just to avoid drowning. Any additional setbacks and you’re going under. The financial analogy here is that you don’t want to get to the point where the majority of your income is eaten by interest payments. You won’t have a good time digging yourself out of that hole.

I categorize tech debt roughly like this:

Sloppy code debt. This can be anything from gratuitous copy-pasting to a hairball of dependencies between different parts of your stack. This is what most people think of in the context of Technical Debt. Harmless technical debt in this category is inside self-contained in edges of your architecture. Self-contained parts are easy to understand and easy to rewrite if you need to. The worst kind of debt in this category is at the core of your app. If the code you have to deal with all the time is a mess that’s going to slow you down and result in bugs.

Creative user debt. Your users will use your app in ways you don’t anticipate. Suppose you introduce a ‘tags’ feature then some users will inevitably try to add 100 or 1000 tags in places where you expected people to use at most a handful. If you’re in a hurry and don’t at least assert()[1] to put a cap on the number of tags that can be associated with a thing you’ll wake up one day to an email from a user about the app being slow. After some minutes of confusion and looking at some SQL logs you figure out what’s going on. Goodbye road map, hello long days of frantic repair work.

Not making a nice error message when a user adds to many tags? Harmless tech debt. Having the UI glitch when there are a silly amount of tags? That’s fine, too. Having servers melt down because you didn’t spend 10 seconds to enforce limits? Not worth it.

Package dependency debt. Software you write today will stop working sooner or later, and often much sooner than you’d think. Sometimes people call this bit-rot, as if software suffers from biological degradation. It doesn’t, of course, but the environments we write software for change and that amounts to the same thing. Server-side packages get patched for security updates, as does the OS itself. On the client browsers change and break your code.

When your app is large and has many dependencies this kind of debt isn’t just a chore to deal with. It can actually become a real problem, especially when you’re forced into version upgrades in order to get those security patches. It gets even worse when you compile your own packages or deviate from the packages that come with your OS. Have your app depend on 250 packages? That’s easy. Monitoring 250 packages for security vulnerabilities? Practically impossible.

To mitigate this we think it’s best to roll your own solution instead of adding dependencies to your app, whenever possible. It’s more work up front, but your code will solve exactly the right problem and once it’s done it will continue to work with minimal maintenance.

3rd party integrations. All integrations with 3rd party services create debt and debt of this kind is always bad. Every integration you make with a 3rd party is going to break, and probably at the most inconvenient time. There is a time for caveats, but this isn’t one of those times. Any 3rd party you depend on is either (slowly) going out of business or they’re growing so quickly that they have growing pains and need to make breaking changes to cope. In the past we’ve built integrations with Slack, Twitter, Google Maps, Google Analytics, Google Workspace né Google Suite né Google Apps, MS Active Directory and more. They have all completely broken down, at some point or another. The ongoing cost of integration maintenance is real and not to be underestimated.

Overengineering debt. When you overengineer you make something stronger than it needs to be. When you under-engineer you make something that you expect will collapse sooner rather than later. This might sound bad, but it isn’t. You can’t predict where the bottlenecks in your architecture will be in advance, especially in the face of exponentially growing traffic. Exceptions notwithstanding, the simplest pieces of architecture are the easiest to diagnose and improve.

dis GIF

If we have to choose between having extra package dependencies (debt of itself) and a brittle 100 line Python script we’ll go with the Python script every time. Sometimes the hacky solution keeps working for years with zero issues. Using your SQL database as a message queue? It’s not pretty and it requires polling, but it’ll work and your customers won’t know the difference. Do you want to wake up a script? Polling a file’s atime or sending a SIGUSR signal is no crime. Customizations for customers? You can hardcode them. It’s fine.

Feature debt. Sometimes features turn out bad and confusing. If at all possible, you want to get rid of those features right away. When you’ve just launched you can still delete a feature without notice. Once you have paying customers removing features from your product becomes a far more delicate matter. Sometimes two features are fundamentally incompatible, and by introducing one feature you can’t introduce another. For instance, if you promise end-to-end encryption you can’t also offer server-side search functionality (the inverse is also true). Once you realize your mistake you have to fork your code and you’ll end up with customers on both forks.

Make this mistake 5 times and you’ll struggle to keep the different versions of your product apart. This will slow your development efforts down to a crawl. Every feature has a recurring cost to it, in maintenance and in the space it takes up in the user interface, so you can’t afford to add features haphazardly.

Customer support debt: when you build a feature in a rush and don’t spend enough time making the interface intuitive you’ll also pay a price in customer support. This debt isn’t so bad because the cost of the fix doesn’t increase over time. However, if you find yourself repeatedly answering the same questions that’s a problem. Either make the feature good and self-explanatory or only enable it for a handful of users. If you allow poorly conceived features to accumulate you’re not just creating a lot of support work for yourself, you also alienate your users. A backlog of support tickets can also be considered a type of debt.


Every startup accumulates some technical debt. This is inevitable, and it’s not even a bad thing. When you start out you don’t know exactly what you’re making. By writing and rewriting your app your figure out what works. No point in creating beautiful code if you’re likely to rip it out in a week or two anyway.

Besides, before you have any users technical debt isn’t real. Anything can be fixed with the delete key at this early stage. The more users (or worse, customers!) you have the harder it gets to keep debt at a manageable level. Database migrations must now be planned carefully. Removing features becomes difficult or near impossible. 3rd party integrations break and will hijack your development schedule. Security updates and other maintenance work will slowly eat up a larger and larger percentage of your week. You can’t let these factors spiral out of control.

If at all possible, make the core of your app rock solid before you start charging people money. That way you can continue building on a solid foundation with little tech debt. If this means building a much smaller app, so be it. Smaller is better. You want few dependencies and few moving parts. Knowing exactly which corners to cut and which parts to rewrite until they’re just right is very, very hard. We still get it wrong all the time.


[1] Asserts abort a program when a condition isn’t met. The user gets a “server error” message and the programmer gets an automated email that a specific edge case has been hit. By adding many asserts throughout your code that check whether your data and inputs look correct you’ll save yourself days and weeks of debugging time.

You need a moat

With Thymer, our planning IDE for makers, we’re entering an enormous and established market of productivity software. We’re fully aware it’s a crowded space where most newcomers fail. Consequently, we need to make a good product and we need a moat. This post is about the second part, building a moat.

Before we get to that, let’s see why the conventional wisdom of building a really small MVP works for other startups but not for us. The conventional wisdom is straightforward: You have an idea, but you don’t know if the idea is any good. What do you do? You contact a bunch of people who you imagine to be prospective customers and you talk to them and ask them if your idea makes sense to them. If they seem receptive you slap together a minimal product with various nocode tools. A form builder puts rows into a google sheet, which in turn sends an email to a remote assistant that executes steps to be automated later. Use a site builder to pitch your idea. Collect some credit card numbers and you’re off to the races.

The assumptions here are (1) you can figure out what the market wants by talking to people and (2) you can demonstrate viability by duct-taping some tools together. If customers aren’t biting then no harm done. Fail fast; try again.

There are three major downsides to this approach from my perspective. First, if you simply build what people ask for then you can never create a revolutionary product. If Henry Ford had asked what people wanted they would answer “faster horses”.

Silent Sunday Nights GIF by Turner Classic Movies

The second downside is that the only products you can build are those that lend themselves to quick prototyping. You can build a MVP for, let’s say, an Invoice Scanning service. You advertise it as powered by an AI-based Deep Learning Neural Network. In reality you just outsource the data entry work. You focus on growing your customer base and when you’re confident you’ve got that part figured out you start on your AI wizardry to make the product real. Aside from the ethics of this approach, the strategy works[1].

Now consider a product that can’t be faked as easily. For instance, a new high-performance database. You could talk to tech companies and ask them if they need a better database (they’ll say yes) and if they’re willing to pay (also yes) but this doesn’t tell you anything you don’t already know. This is a product-first startup and if you don’t have a product to show people you don’t have anything.

The third downside to the nocode approach is that even if you can get your startup off the ground this way, what you end up with isn’t going to be a tech startup. And if there is no tech, where does your moat come from? If your product can get cloned easily it will get cloned the moment other people figure out you’re doing well. Sure, competition is inevitable for any startup and you can try to out-execute everybody. Still, there are always people who are as smart as you are but more ruthless and willing to work harder. If at all possible, you want to avoid an attack by clones.

roger roger GIF by Star Wars

If you have to solve some schleppy technical problem in order to make your product stand out the equation changes. The downside is that it requires a much bigger upfront investment to get a first version out. This implies a much bigger opportunity cost in case you build the wrong thing. In addition there is execution risk of having to do substantial rework as you learn more about the market. Small prototypes are much easier to change.

The upside is that having to do a lot of work up front discourages the competition. Anybody who successfully clones Figma will do very well. Daunted by the work it would take, the aspiring cloner will look for easier opportunities. Figma’s technical excellence makes their users happy and keeps their competitors at bay. Win-win. If your product requires hard technical work you eliminate a large chunk of the competition. Most startups are started by business-minded people who have only a secondary interest in tech. They’ll avoid hairy tech problems because that’s not where their strengths are. Good software engineers want to work on their own ideas and don’t want to lazily copy somebody else’s. If you create a product that requires some real technical skill to pull off you take a slightly bigger risk but you get a moat in exchange. That’s a good deal, if you ask me!

All good businesses have a moat. Without a moat market participants are forced to compete on cost which results in a race to the bottom. In this scenario everybody loses, including the customer. You can’t offer decent customer support or deliver a good product when you’re forced to cut every corner just to survive. You don’t want to struggle to survive. Where is the fun in that?

When you’re good at tech but not great at marketing or sales it makes sense to lean as much on your tech skills as possible to give your startup an unfair advantage. It’s never a big mistake to lean into your strengths. In some circles the approach where you do months of coding before talking to any customers is considered lunacy. I don’t think this is justified.

Sometimes building a better product requires solving some hard technical problems. You risk building the wrong product by solving problems nobody cares about. That’s just the price you pay for entering a market with something new.

[1] One of the poorly kept secrets in the valley is that many of the AI startups fake their product like this.

Good customer support means saying you’re sorry

Your startup will fall short and will disappoint some of your users. This is inevitable.

  • Essential functionality is missing. When you launch you have to say no to all these features that you really want to include, but there’s no time. You want the core experience of the app to be good, and everything else will barely work or be missing.
  • Your documentation is lacking. Your app is still figuring out what it’s going to be. Documentation comes later.
  • Your app is slower than it should be. You want simple code that you can change easily for experimentation. Optimization gets in the way of that, which means your app isn’t as slick and smooth as it deserves to be.
  • Bugs and other breakage. When you’re pushing multiple updates per day to your app some quality issues are inevitable. You’ll break things. Oops.

Even if you have a bit of a perfectionist streak and you delay launching multiple times because you can’t bring yourself to have your first MVP be a buggy mess your product will still fall short. It has to because good software takes years to build. You’re launching way before you’re ready because you need to figure out if your product concept is good.

Therefore, customers are going to run into all sorts of issues. When a customer contacts you and they’re clearly unhappy you should go out of your way to:

  • Thank them for contacting you with whatever issue they have,
  • apologize for the problem (no nonpologies),
  • talk to them frankly and respectfully about the issue without any corporate euphemisms,
  • then fix the actual problem as quickly as you can,
  • and thank them + apologize again if necessary.

This isn’t rocket science, but practically every business gets this wrong. For every bug report you get another 10 (or 100 or 1000) people are equally annoyed by the bug but don’t say anything because they expect customer support to be useless. If you want to make a product that delights users you have to fix all the little things. If people report a bug and you don’t fix it promptly they’re not going to report another bug, and they’re less likely to recommend your product to others.

You want to fix every single issue people complain about. Bugs are tremendously frustrating for your customers, and most people have bad experiences with computers and with customer support in general. Empathize with your customers. After you’ve fixed the customer’s issue, try to fix the entire class of that issue. If a customer is confused about a dialog in your UI rework the dialog. If you don’t have time to fix the dialog, at least add a little ❓ to the dialog with a popup explaining that which isn’t obvious. Or maybe add a better error message. You can usually do something.

There are occasions where you really can’t fix the problem. Perhaps fixing a bug would take weeks or months, because it requires rebuilding a large part of your app. In that case, explain to the customer why you can’t fix the bug. Not just with a lazy one-liner, but go out of your way to explain why fixing this specific issue is way harder than it might seem. Try to avoid technobabble, but never condescend to your customers. Yes, they can tell and no they don’t like it when they’re condescended to.

Angry Office GIF

Many customer support requests should result in a git commit. If you’re not going to fix the problems with your product you’re basically telling customers that you don’t care. And if you don’t care about your own startup and the customers that make your startup possible, why even bother?

I’ll illustrate what I mean by good customer support.

Customer: Hi there. We can't find the invoices for February and March. Please send them to [email protected]?

Easy enough, right? This is an example of a poor response:

Sh*tty support: Invoices are sent monthly to your billing address. Search your inbox.

This response presumes the customer is incompetent or lazy, which is insulting. Also, it does nothing to fix the issue.

Sh*tty support: [automated email with FAQ responses that may be relevant and some ticketing system]

So rude. You can show customers FAQ responses or documentation before they’ve gone through the trouble to write you an email, if you have to. Lazy autoresponders send a clear signal that you just don’t care and that you view customer support as a cost center.

Sh*tty support: Thank you for contacting AmazingSoft customer support. We want to deliver excellence in customer support and we thank you for your business.

You can download prior invoices at /billing/history.

Thank you for contacting AmazingSoft customer support. We closed the support ticket and no further responses are possible.

This is still a pretty bad response. It’s mostly filler, and the customer can tell only the middle line was written by a human. Although this response indirectly addresses the problem, it doesn’t really. Perhaps the user can’t log in anymore (maybe they closed their account). Perhaps you’re emailing somebody from accounting who has no idea what your software does or how to log in. In addition, the request clearly asked for the actual invoices, which weren’t included in the response. As a whole, this kind of customer support is totally inadequate.

Acceptable support: Hi, I've attached the two invoices below. Thanks for using [product].

Now we’re getting somewhere. The first response that actually helps. But could the response be better? Well, yes! Remember what I said before about a good response involving a git commit? You can create a self-service area where customers can download prior invoices. You can let your customers configure one (or more) email addresses where invoice copies get sent to. You can add a link to the invoice history to every invoice email so customers will automatically see it. And you can add a authkey to the invoice history link so when the invoice email is forwarded to a bookkeeper they can find the missing invoices themselves without having to ask. If you do all this you will only get requests for invoice copies once in a blue moon.

Of course when you just launch a new product you won’t have the time to do any of the above. But realize that the root cause of this customer support request is that your billing system sucks. You cut corners out of necessity and you waste your customers’ time because of it. The same applies for most customer support emails you’ll get. It’s embarrassing. Unavoidable, but embarrassing and you should feel bad about it. And you should tell your customers you’re sorry.

Footnote: As we’re deep into code to get a workable prototype going we’re changing our blogging schedule a bit. There will be fewer articles and more updates about what we’re building.

Your app still has to be good

In this post I want to stress the importance of having an app that is really good. As good as you can possibly make it. Ideally your app blows the competition out of the water in a side-by-side comparison. This isn’t just about UI/UX design and features. It’s about whether your app solves a key problem much better than anybody else. Your prototypes and MVP won’t be that great, and that’s to be expected. You make those first versions to gauge potential. You have to work towards making a truly great app though, and in this post I’ll explain why I believe this is so important.

There are some businesses that do well without having great software, just by being great at marketing and sales. So why not just do that? You can hustle. You can buy ads, build a big following on social media, do cold outreach and email 10.000 people, write SEO pages, plug your app on youtube and do podcasts. That all works[1]. You can get many people to try your app with persistent marketing. Despite all that, if your app doesn’t provide enough value to people your startup will struggle and not really take off, no matter how hard you hustle. This is because the world is huge and the fast way to reach many people is by word-of-mouth. Your users have to be so thrilled about your app that they’ll tell their friends about it. Your app should have an R0 above 1, if you catch my drift :). That’s how you get explosive growth.

You can ignore what the market is telling you. You can just power through and hustle. Even with a mediocre app you’ll still find customers with persistence. You can even create a profitable business that does well year after year. This works best in markets where competition is weak and nobody has made a great product that users love. Those are markets just waiting to be disrupted. Just a matter of time until that happens.

For B2B startups having a great product is less important than for B2C. There are two reasons for this: 1) business software is more expensive which means you can just hire sales staff and 2) people don’t get as enthusiastic about business software which means your competitors are likely to struggle with word-of-mouth growth just like you. But even B2B markets get turned upside down by upstarts with wildly passionate users. Companies like Stripe and Zoom are unstoppable because of it. Business chat apps like HipChat and Campfire stood no chance against Slack. I don’t even remember the names of the businesses Zoom bulldozed. Webex meeting, maybe?

The necessity of making a great app applies to us as well. Every day we write code, design and redesign the UI. We try to figure out which features work and which ones just don’t. But we don’t know yet if our app will turn out good enough. Sometimes you swing and you miss. Occasionally you can pivot an app and turn it into something great, but that’s the exception to the rule. If reception to your app is lackluster it means your app is bad and you should go back to the drawing board. It could happen to us. Fingers crossed.

[1] To be clear, every business needs to do marketing. My point isn’t that marketing is unnecessary, but that it’s not wise to compensate for bad product/market fit by doubling down on marketing.

Thymer – a rough feature list

As we narrow down what our productivity/planning app Thymer will be like, we move more and more to the “no” column. Or to the “not yet” column, which is exactly the same as “no” but easier to swallow.

Some functionality we consider essential. Those are the core features that people will be using all the time. Long tail features are features that make the product better, but that don’t excite people and don’t make your product stand out. For an MVP you want to focus on core features and BS features exclusively.

As a reminder to myself that saying no should feel painful, here is a clip of Jony Ive talking about it:

Okay, here we go. A shocking amount of work to do. It’s still a pretty rough list because it’s still early and we try to keep an open mind about what’s important and what’s not. It’s pretty likely that the MVP will deviate a ton from what we describe here. But that’s OK.

Core features

  • The editor. This includes multi columns, line wrapping, virtual scrolling, indentation, sorting, searching, and everything else you’d expect an editor to do.
  • Command menu. Popularized by sublime text, but now also used by VSCode and many other apps. Basically instead of littering the interface with buttons and dropdowns you can have a list of commands that the user can search through with auto-completion. Shortcuts for the popular commands.
  • Planning layout, where tasks can be assigned to one or more users. This is where you get a sense of what has to get done.
  • Zoom in/zoom out. So you can focus on one part of the document.
  • Offline first. For the app to feel responsive keep track of all changes and send those to the server later. No spinners every time you do something.
  • Flawlessly sync changes made by multiple users, and resolve conflicts where necessary.
  • Conventional task features. Mark complete, in progress, done, important, waiting for something else, due dates, etc.
  • Onboarding tour. We want people to try our app without getting overwhelmed.
  • Very basic settings/preferences. Close account.
  • Splash/marketing website. Has to explain what Thymer does and why it’s worth trying.
  • Group management. Add/remove users from your team.

Things we really want (time permitting)

  • Real time multiuser support. When two people work on the same document each should see the other person type. This is super hard to add after launching, so we want to have it working by the time we launch.
  • Versioning. One of the great benefits of using pen & paper for note taking is that you can so easily go back to your previous notes. Most software products are terrible in this regard, and we think we can do better. For this we need to make some kind of system that allows the user to go back in time, see changes, and so on.
  • Billing/invoicing. Would be nice to get this done before the launch. Otherwise we just have to scramble after the launch to get it done.
  • Client side API with a console/shell. We believe apps should be programmable by the user. We can’t put buttons for everything in the user interface, but we can expose many of the internals to the user so they can go wild.
  • Bulk actions, of all sorts.

What we probably won’t have time for

  • Calendar syncing with Google/MS Office/Apple calendar. Having to use a planning app and a calendar side-by-side isn’t great. The problem with calendar syncing is that it’s just another feature, except it this one takes a disproportionate amount of time to get to work right. Once we have users I have no doubt many will start demanding calendar integration. But it’s something we can postpone for now, so we do.
  • Email notifications when team members make changes. Email summaries for changes in a day/week/etc. Nice to have, of course, but it’s something that can be cut, so it gets cut.
  • End-to-end encryption. Because almost all logic is client-side already, end-to-end encryption is doable and certainly a nice feature to have. We are pretty paranoid about security ourselves, and we think it’s a good thing when e2e encryption becomes a standard practice. It’s not without downsides, though.
  • Super advanced editor features, like block selection or multiple cursors. Copy-pasting rich content between browser tabs.
  • User documentation or manual.
  • Theme support. Dark/light mode.
  • Extensive customizability.
  • JSON API

BS features

  • Cool animations
  • Tips & tricks
  • We’ve got many wacky ideas. But it’s a distraction for now. BS features we’ll add in the final stretch.

Definite no

  • Mobile app of any kind. Definitely no iPhone or Android app. Making a good mobile app for anything is a big project. We think in most situations an MVP should be either for desktop use or for mobile devices. It’s pretty unusual for apps to have a 50/50 split in desktop/mobile usage, and for light apps a mobile-first design is still usable on the desktop.
  • Extensive browser support. If it works on Chrome (and maybe Safari/Edge) we’re happy.
  • Localization. English it is.
  • File attachments. It’s just another long tail feature.
  • Desktop app. Some day, I’m sure. Do we want to mess with electron on some kind of cross platform UI framework right now? Nope. No time.

As you can tell, our ideas at this point are pretty vague. As Thymer starts to take shape we’ll get a better sense of what is really critical. With only 60 days or so to go I expect we’ll be forced to cut even more from our “must have” list.

Setting up a very basic git server

Just yesterday gitlab was down. Github has network issues on a pretty regular basis. Imagine not being able to push an update to your product because some 3rd party service is down. No thanks! We’ll set up our own git server. Shouldn’t be difficult.

Our general philosophy is to do as much as possible ourselves, for 3 mains reasons. One, we learn a bunch and this will help us troubleshoot when something goes wrong down the road. Two, we enjoy not being dependent on 3rd parties. Three, having your own stuff that never breaks with APIs that never change makes life way better. Yes, sometimes we reinvent the wheel, but that’s alright.

We’re two people and we build small scale apps (couple million users max). This means we don’t need much in terms of infrastructure. We don’t need even 5% of the functionality github has to offer. When we have specific needs we can often duck-tape a handful of Linux command line utilities together. In that spirit we’re setting up our own git server.

Goals: (a) create new repositories easily. (b) push/pull from VSCode. (c) push/pull to release server. (d) email hook on push.

I’m just going to follow the guide on git-scm.com. I’ll create a user called ‘git’ on our server, and set up public key authentication, with blockers for port forwarding. We have a whitelist for ssh logins, so I’m also updating AllowUsers in /etc/ssh/sshd_config. With chsh I’m removing shell access for the git user as well.

If you lock everything down as aggressively as possible then you’re never one configuration file typo away from disaster. We have a firewall that whitelists IPs, a secondary firewall on a switch in the data center, we block users in sshd, disallow password authentication, we disable shells, we use fail2ban to ban/alert on suspicious activity, all sorts of monitoring and we probably have additional security measures I can’t think of right now. We’re big believers in this kind of layered security and I’m sure it will be the subject of future posts.

Now I’m going to deviate a little bit from the git-scm instructions. One, I want to rename the main branch to ‘main’. We can do that with git symbolic-ref HEAD refs/heads/main. Future versions of git will make renaming the main branch easier, but this works.

I’ll also add a simple ‘post-receive’ hook so when any commits are pushed to the git server it’s posted to our wiki and we’ll get a nice email about it.

Basic bash script gets the job done. This is not a robust script that is intended to stand the test of time. We think of it as a type of interactive documentation. When we want to create a new git repository a year or two from now and we forget the steps we can just read the script, ask ourselves if it still looks reasonable and then run it.

It’s a good habit to sanity check your inputs, even on throwaway scripts. It’s easy to shoot yourself in the foot with bash shell expansion, after all.


#!/usr/bin/bash

# usage: ./make-repo myrepo
#
# for git commands see
# https://git-scm.com/book/en/v2/Git-on-the-Server-Setting-Up-the-Server

# http://redsymbol.net/articles/unofficial-bash-strict-mode/
set -euo pipefail
IFS=$'\n\t'

if [[ "$1" =~ [^a-zA-Z0-9_-] ]]; then
        echo "use alphanum git repo name '$1' (exit)"
        exit
fi

sudo -u git mkdir /home/git/$1.git
cd /home/git/$1.git
sudo -u git git config --global init.defaultBranch main
# only repo, not also a checkout
sudo -u git git init --bare
# debian git doesn't have rename head yet
sudo -u git git symbolic-ref HEAD refs/heads/main
# papyrs wiki hook
sudo -u git ln -s /home/utils/git_receive_hook.py hooks/post-receive

Now it’s just a matter of adding the remote to my local git repository and we’re off to the races:

git remote add origin ssh://80daysgit:testproj

And I’ll add an entry to my ~/.ssh/config:

Host 80daysgit
  HostName [redacted]
  User git
  ForwardAgent yes
  IdentityFile ~/.ssh/80daysgit_id_rsa

That’s it. The remote shows up in VSCode automatically and I can push/pull with a click of a button.

State of the Startup (20%)

Yesterday was day 16, which means that we’re at 20% of our our 80 day journey. Time flies. We’ve made some good progress, but building traffic takes a lot of effort and patience. Let’s get to the stats, and I’ll then add some observations.

The stats

Traffic for the blog is at about 150 visitors/day, up from 50/day at the last update. We had a big spike in traffic on the 24th when one of our blog posts hit hackernews. This was probably the blogpost I spent the least time on. I wrote it in a hurry and didn’t make my case very carefully. No predicting which post gets popular out of nowhere, sometimes this is just how it works out. Still, part of me wishes one of my better posts got all the traffic. Oh, well.

On twitter our following count is ticking up, slowly but steadily. Wim is at 85 (was 38), Diederik at 91 (was 19). I also discovered that on analytics.twitter.com you can see some charts.

We’ve written 38 blogposts in total now (up from 20). Blogging still takes an immense amount of time, although I’m already getting a bit faster. It will take a long time for us to get actually good at writing, but we have we have some good insights to share. I think that there are many people interested in what we have to say, it’s just that practically nobody knows this blog and our 80daystartup project exist. Our goal is still to write the blog we wanted to read when we started doing software startups.

Our mailing list is now at 95 subscribers, up from 29. We email a weekly summary of our blog to the mailing list, and hopefully that will keep people engaged with our project even when they don’t use Twitter or RSS.

Stray observations

I don’t know if the kind of traction we’re getting on social media is good, ok, or bad. I don’t know if we should be writing fewer super high quality blog posts or stick to our daily blogging schedule that is more hit-or-miss.

Combining focused programming sessions with social media presence is difficult. Social media is a big and constant distraction, which is why I’ve stubbornly avoided social media for my entire life. Maybe I’ll get the hang of it and find a good balance, but I’m not sure.

Thank you!

Thanks for reading and for following us in our 80 day project. We’re always excited to hear from people, and we’ve gotten some super nice messages! Just shoot us an email at [email protected] or contact us on twitter for any reason. We reply to everybody, usually within hours.

Next update at 30%!

A paradox of course correction

The perfectionist needs to be told to ship faster. Because not shipping or having exhausted yourself by the time you launch is fatal. There is a critical difference between polish and wasteful perfectionism. Polished is an app that looks good and works well. Perfectionism is refusing to launch until every single bug is fixed, rewriting and refactoring backend and frontend code just to make it look nicer or cleaner. It’s wasteful work because large parts of your app and infrastructure will get rewritten anyway. Why seek perfection in something you’ll inevitably tear down?

The hustler needs to be told that making a really good product takes time so just sit down and do the work. Because you can’t grow a product startup if you don’t have a good product[1]. The engineer needs to accept that if nobody knows your product exists you won’t have any customers. If nobody knows what you’re building you won’t get any feedback. Having to face zero signups after spending a year on your beta is so demoralizing you’ll likely give up.

The spendthrift needs to stop squandering money. It’s the runway[2] you spend. Everything takes longer than you think and many startups fail simply because they run out of money. The miser should try to be less cheap. Sometimes you have to spend money to push your startup forward[3].

These are just some examples, but I think you get the idea.

In order to survive you want to embrace conventional and boring choices in some areas, because conventional choices aren’t terminal mistakes by definition. That leaves you with unconventional bold choices in those areas where you want your startup to stand out. Founders tend to be unconventional people, but it’s hard to be only moderately and selectively contrarian. That’s a problem because being contrarian and wrong can easily kill your startup.

People who are at one extreme need to shoot for the other extreme in order adjust their behavior enough and end up somewhere in the middle. 10% adjustments don’t cut it when your initial position is completely wrong. Your position is wrong because you are predisposed to believe facts that reaffirm your beliefs. To counteract your blind spots try to overshoot your goal and you’ll end up closer to the middle, where you want to be. This is counter-intuitive!

When you already have a reasonable middle position on any of these issues the advice you read and act on doesn’t affect you much. After all, it’s the big mistakes in startups that kill you. If you can survive until you reach product/market fit (= awesome product and accelerating growth) you’ll be fine even if you get all the small stuff wrong. You just need to avoid the terminal mistakes. Tautological, I know, but still easy to forget.

That’s the paradox of course correction[4]: when you at the one extreme aim for the opposite extreme to end up in a moderate position. If you’re an engineer with an inclination to do zero marketing, try to overdo your marketing and maybe you’ll do barely enough.

[1] You can build a different kind of business, e.g. where you provide some kind of B2B service manually that you’ll automate later, after establishing a market need.

[2] Your runway is the time you have before you run out of money. The sooner you can cover your living expenses the better.

[3] We’re pretty bad at this, admittedly, and have a tendency to reinvent the wheel..

[4] If this is a known concept I don’t what it’s called.

Bootstrapping B2B vs B2C

One of the most important decisions you have to make early on is whether your product is going to be for businesses or for the general public. There are significant advantages and disadvantages to both markets, and in this post I’ll touch on the major differences. You can easily see that the B2C column has more check marks, but that’s deceptive!

Advantages of consumer apps

With a consumer app you can get to market more quickly because you need less functionality. A consumer app that just does one or two things well can be a hit. Business users demand all sorts of long tail features. Think of single sign-on, permissions, audit features, integrations with other products. These are not features that make your product stand out in the market, these are just features you have to build to get and keep customers. This clearly favors the B2C app.

Consumer apps also sell easily. When you sell to other businesses it can take forever. Accounts payable. Requests for quote. Oh wait, you first need approval from the boss. Phone support. Product demos. Sales is a process and it’s real work.

If you make a cool consumer app everybody wants to try it. Consumer apps become viral hits in a way CRM software never does. Making software that is used by millions of people around the globe is a dream of many software developers, myself included. Happy customers tell their friends. That means that you can have explosive growth manifest from nothing. If you sell business software growth means hiring sales staff.

Consumer apps that look and work better tend to win. That means if you enjoy creating software that works really well you get rewarded for that in the consumer space. Business customers don’t care that much. Either your product saves or makes them money but they won’t care much beyond that. Tragic, but true.

Customer support for consumer apps is also a lot simpler. You can have some kind of self-service forum and some FAQ pages. You can automate almost everything for consumer apps.

That’s a long list of things that favor consumer apps. So what do B2B apps have going for them?

Advantages of business apps

Business software has two major advantages, and those two make up for all the downsides. You can easily charge money for B2B software. This difference is enormous. Businesses have effectively unlimited budgets if you can demonstrate your software is worth it. One B2B sale can bring in the same revenue as 100 or 1000 consumer sales. Would you rather provide customer support for one customer or for 1000? Easy choice.

In addition, your business customers will keep using your software for 5 or 10 years. More if your product is really sticky. When you sell consumer software you are constantly fighting against churn and new developments in the market. For B2B software boring is good. I put the UI/UX checkbox in the B2C column because B2C software is more meritocratic, and that’s good if you want to break into a new market. However, once you’re an established business this benefit turns into a disadvantage for you and an advantage for new up-and-comers.

The reality is that business software is where the real money is. Consumer software is all chasing after the same consumer dollars. It’s hard to compete with the free or nearly free offerings provided by the megacompanies we all know. In addition you’re going to compete with venture backed startups that will happily give away their products for free to get a dominant position in the market. Despite those challenges your consumer app needs to find a massive audience, think millions of users. That requires a small miracle.

The bottom line is that when you write B2B software you enter a far more forgiving market where there are plenty of customers eager to pay real money. If you decide to write consumer software regardless, you better have a few aces up your sleeve.

Beware wanton originality

Let’s say your startup website gets some traffic. Maybe through social media, ads, a podcast, or from an article you’ve written. Some of those visitors won’t understand your product or just aren’t interested. A percentage will sign up for the free trial. Of those people some mean to “look at it tomorrow” but forget, others open the trial but get confused and quit. Some will actually try your product for real, those are your users. And of those people some like the product enough that they want to buy it. Yay.

The percentages used here are pretty arbitrary. If you have an SEO article that directs people with a specific problem to your website you get high quality traffic that will flow through your funnel nicely. On the other hand, if your product is covered in some (online) magazine your traffic will consist of people who are curious but who are not actually prospective customers. Trial signups from that audience are just noise.

At some point you want to optimize your funnel and figure out where you’re losing people. When you’re just starting out your splash page[1] won’t have much traffic (not enough for A/B testing) and you won’t have the time to get everything perfect. Your funnel will leak, but that’s fine. Your initial goal is to figure out if your product is good. You can always fix the website later.

Ultimately your app will only sell well if it’s really good. Ideally, your app is also original and stands out from the rest. A great app will get you passionate users that will spread the good word to their friends, after all. This is especially true if you’re making an app for a horizontal[2] market. Originality is not without downside: different is scary and alienates people. If your app is unique in a good way it might become a big hit. If your app is unique in a bad way you won’t make it. Back to the drawing board.

If your app is too conventional it’s hard to stand out. If you have excellent marketing you can do well, but you risk becoming a zombie startup[3], where everything seems promising enough that you don’t want to give up yet, but you’re not getting the kind of traction and growth you really want.

Let’s compare this with an “innovative” splash page. Maybe you’ve done something whacky where people have to solve a puzzle in order to read what your product does. Original? For sure. But good luck getting anybody through that funnel. Look at the risk/reward here, assuming you have an app that is original and good (good idea + good execution):

A great splash page can’t save a bad app, but a bad splash page can destroy your chances of success even when your app is good. This brings us to the title of today’s post: wanton (unprovoked/unrestrained) originality.

Limited upside + unlimited downside → conventional choices are good.

This is why the websites of so many startups look the same. Your website only needs to explain what your product does and what it costs. If you do more you’ll risk punishment for wanton originality. Yes, ideally you want a high percentage of people who visit your website to try your app, but a reasonable website will do a pretty good job at that. It’s way easier to 10x your website traffic (top of the funnel) than to 10x the conversion rate of people who visit your site. Traffic matters. A good app matters. A great website is just nice to have.

Don’t get me wrong. If you can make your startup website look great by all means go for it. Just bear in mind that you can’t save a bad product with a good website[4], but you can ruin your chances with an incomprehensible website even when your product is excellent. You can have a conventional billing process. Conventional, but excellent, customer support. You don’t need to reinvent the wheel here.

Be original where it matters and don’t be different for the sake of being different.

[1] Splash page. The website for your app that shows people what your app does to encourage visitors to sign up for a free trial.

[2] Horizontal apps are for a broad group of people and businesses. Think spreadsheet software or website builders. Vertical apps are for a specific niche or industry. Think software specifically made for tennis fans or architecture firms.

[3] Zombie startup. Not quite dead but also not really thriving.

[4] Enterprise software businesses excepted. If the people who make the purchase decision won’t be the ones actually using your product (“solution”) then everything changes. Enterprise software businesses are all about the sales process. It’s a totally different world.