GeistHaus
log in · sign up

https://karlsutt.com/rss

rss
15 posts
Polling state
Status active
Last polled May 19, 2026 00:36 UTC
Next poll May 19, 2026 23:54 UTC
Poll interval 86400s
ETag W/"1048d-ylsznjSx9UUFePG7RN1mZW0XHR8"

Posts

Are you too focused on outcomes?
Mindset
What happens when we focus too heavily on the fruits of our labour and not growing the tree that provides the fruit?
Show full content

Are you too focused on outcomes?

What connects these three seemingly separate stories, besides tremendous damage to the involved individuals?

In 2016, Wells Fargo was found to have created millions of fraudulent savings and checking accounts without their clients' consent, resulting in fines of $185 million. The scandal has had ongoing legal, financial, and reputational consequences for the company and former executives, with additional suits totaling an estimated $2.7 billion.
In March 2019, the grounding of Boeing 737 MAX 8 and MAX 9 aircraft was instituted in over 41 countries, including the United States, Canada, and China, following a deadly crash involving an Ethiopian Airlines plane. This was the second crash of its kind involving a Boeing 737 MAX, resulting in the deaths of all passengers.
In November 2022, the crypto exchange FTX collapsed in a dramatic fashion after Binance's CEO Changpeng Zhao tweeted that he was planning to sell off Binance's holdings of FTT (FTX's token), which caused FTT to fall rapidly, triggering a liquidity crunch, suspended withdrawals, investigations, realisations and, eventually, bankruptcy.
Are you too focused on outcomes?
Wells Fargo measured and rewarded a lagging indicator of success — the number of products held by a customer — thus incentivising fraudulent behaviour. Source
Are you too focused on outcomes?
The Boeing 737 MAX software issues were driven by an external force, wanting to be "better" than Airbus, which is a lagging indicator of success. Source
Are you too focused on outcomes?
SBF conducted extensive PR campaigns to paint himself and FTX as honest, when in reality they were anything but. Being seen as "honest stewards of crypto" is a lagging indicator of success resulting from putting the customer first and doing the right thing. Source

One common thread in the above stories, and in many stories similar to these, is the main characters' fixation on outcomes — lagging indicators of success — rather than inputs — the leading indicators of success.

What about some positive examples?

Amazon is one of the largest and most well-known retail brands in existence, not to mention their cloud business. Regardless of what you may think about Amazon and its business practises, it's worth asking "How does an online bookstore startup end up as the foremost online retailer in the world?".

Is it possible that they have been tricking people into spending more money on stuff for nearly thirty years? Did they "succeed" by secretly charging customers' credit cards and shipping things to them?

No. Amazon got to where they are by relentlessly focussing on the customer's needs — finding out what customers care about and delivering it day in, day out, for decades.

In the investment world, where the majority of funds underperform the SP500, Warren Buffett focuses on the fundamentals and finding value, not making a quick buck or living off clients' fees. He does not care about looking foolish in the next quarter or year, because he knows that only by buying great businesses at good prices, over the long run, is the key to outperformance. He cares about beating the SP500. But he cares about buying great businesses more.

Would it have been possible for Warren Buffett to get rich — a lagging indicator of success — by prioritising extracting fees from clients? Sure. Nick Maggiulli argues that many hedge funds do, in fact, get rich that way. But to achieve the outsized success that Buffett has achieved with Berkshire Hathaway requires more than gouging fees until the next economic downturn or market crash. It requires prioritising not losing money for his clients.

What's this got to do with you?

The examples above may seem like far-fetched scenarios that don't apply to us individually, but in each of the above instances there was an individual making decisions about what to measure and what to optimise for.

The concept of leading vs lagging success indicators boils down to inputs vs outputs and figuring out which inputs are effective for positively influencing the outputs.

Folks often take shortcuts by trying to improve the output directly — without investigating and testing which inputs give rise to them in a sustainable way. In the FTX example, SBF invested heavily in making it seem like FTX was a reputable crypto exchange, rather than making sure that it actually was one. These "shallow" shortcuts might work for a while in the short term, but reality has a way of erasing them — often quite painfully.

So what should you do?

Understand the difference between a goal and an action towards that goal

"When a measure becomes the goal, it stops being a good measure", cautions Goodhart's Law.

We often get confused over what's an input and what's an output. Money, expensive cars, watches, exotic vacations, Instagram-lifestyle, and so on, are all proxies for "a good outcome". They're very easy to measure. They're also very easy to compare. And unfortunately, they have a habit of becoming goals.

Attaining these "good outcomes" is great when they're the byproduct of something deeper, but as soon as we start to chase them as goals they stop measuring our lives accurately.

Maybe you set a goal of being your own boss, having freedom and making money on your own terms. Maybe you begin to do well and in the process you start raking in a ton of money.

Money is much easier to measure than "having freedom" or "being your own boss". And at the end of the day, if you make more money than your friends, you're doing better than them, right?

So, slowly over time, your goal of "be your own boss, have freedom" morphs into "make more money". You spend every waking hour obsessing over how to scale your business or start more businesses. You think leaving money on the table would be failing your goal.

Except... that wasn't your goal to begin with. Your goal was to be your own boss and have freedom, not slave away trying to "make number go up".

Be mindful of your own measures of success — don't let them become the goal. Periodically re-evaluate what you actually want and why.

Ask 'How?'

There is a popular concept in root cause analysis called Five Whys. It's used to diagnose the root cause for a defect. Essentially, you just keep asking "ok, why?" until you have arrived at a good answer for what caused the problem.

You can apply a similar pattern for finding plausible inputs for positive outputs. Just like with Five Whys, you start from the outcome, but instead of asking "why?", you keep asking "how?" until you arrive at persuasive inputs.

But remember — don't fool yourself. "The first rule to remember is to not fool yourself, and you're the easiest person to fool", said Richard Feynman.

If you want to run a personal best in a marathon, don't ask "how?" and answer "get an Uber at the 10th kilometer". That would defeat the purpose. Your answers require humility and honesty.

Here's a preventable mistake from my own life where I failed to apply the (Five) Hows. I recently started playing squash more regularly. I really like the game and it's a great workout. The increased frequency of playing lead to a hip injury which has put me out of action for at least two months (and counting).

Given I wanted to continue playing squash, I should've said

  • I want to keep playing squash; how?
  • by regulating the intensity of each match; how?
  • by not diving after every loose ball in order to win; how?
  • by hiring an instructor to learn better technique; OK, how else?
  • by warming up properly before the matches get intense; how?
  • by stretching and doing some light exercises before getting on the court; how?
  • by arriving 15 minutes earlier than our scheduled slot; OK, how else?
  • etc etc

The difference between Five Whys and Five Hows (yeah, it's called Five Hows now) is that Five Whys is convergent — it tries to get to a single explanation for the outcome. Five Hows, on the other hand, is divergent by nature — there may be multiple different inputs that lead to a favourable outcome. Sometimes it's useful to modify the "how?" to "how else?" to generate more alternative paths.

Focus on what you can control

One of the pillars of Stoicism is the dichotomy of control, which is the simple idea that some things are within our control and some are not.

Among other things, your beliefs, your actions, your goals, your attitude and your outlook on life are firmly under your control. You can choose your own goals and your own metrics for success. You can choose how you behave and how you treat people. You have choice.

Not under control are other people's opinions, their actions, whether they like you or not, and so on. You could try your best, but it doesn't mean everyone will like what you're doing. In fact, there will be people who won't like you no matter what you do.

There are vastly more things not under your control than there are things that are under your control. If we get too hung up on how the world should be, instead of how it really is, we set ourselves up for failure and misery.

"The best way to achieve success is to deserve it", says Charlie Munger. Focusing on your actions and trying to be better today than you were yesterday — trying to deserve success — is the longest lever you have in your search for success.

🙌🏻Thanks for reading.
If you have any comments or feedback on this article, I’d love to hear from you. Come say hi on Twitter or on LinkedIn.
636a29f8891a9f004d804f18
Extensions
How to communicate effectively as a developer
Writing
Some tactical and strategic tips for writing effectively as a software developer.
Show full content
How to communicate effectively as a developer

Writing effectively is a superpower, there is no denying it. As a software engineer, you write a lot. Most of the writing you do is for computers. Businesses, however, consist of people.

Just spoke to the CEO of a $350 million company, who said: "I won't promote somebody to a strategic leadership position unless they're a good writer."

— David Perell (@david_perell) October 20, 2022

Many of us work remotely which puts higher demands on the quality of our writing. If a few years ago we were able to scoot a couple of meters in our office chair and talk to a co-worker about something, we now usually write to them. Sometimes we won't hear back until several hours later, maybe even days. When the round-trip time is measured in hours, you'll want to consider what and how you write to avoid the gut-punching "What do you mean?" a day later.

Communicating effectively as an engineer means empathically increasing the resolution of your writing.

Short-form writing

The first major category of writing ubiquitous in every developer’s life is chat — Slack, Discord, Telegram, WhatsApp, etc. Whatever the platform may be, the messages are exchanged — written and read — quickly.

Writing messages on Slack isn’t what engineers get paid for, though. Writing code to solve problems is. Instant messaging tools are seen more as a necessary evil to get work done in a team than anything else. Because of this dynamic, there is a tendency to elide details or cut messages short, sometimes at the expense of legibility.

Let’s look at a couple of tactical examples 👇🏻

How to communicate effectively as a developer
Chatting in DMs or in a private conversation.
How to communicate effectively as a developer
Chatting in a public or team channel with many people.

In both cases, the example on the left is what I call “low-resolution writing”. There is very little context, too much reliance on pronouns and unclear references. The writing is not empathic —the reader has to spend extra energy to work out what is being said:

  • what is “it”?
  • who is “her”?
  • “not working properly” how?
  • what exactly does “bad time” mean?

The examples on the right, on the other hand, try to make the reader do less work, even though it is more effort for the writer. The writer clearly has thought about how the message will be read.

Especially in the “team channel” example on the left, it is unclear what the writer is even trying to convey. It is a statement, do I have to acknowledge it? It is ambiguous, should I follow up with questions? Is the writer looking for help or simply quipping that there is a problem?

On the right, however, the writer has gone through the trouble of telling the reader:

  • what symptom they’re seeing (assuming they pasted the exception afterwards);
  • what they have tried in order to resolve the problem; and
  • that they need help.

Someone familiar with the problem will be able to help immediately instead of having to start solving the mystery of what the message author meant.

Longer-form writing

The second category of writing many engineers carry out daily is producing notes of all sorts — comments on pull requests, feedback on design documents, proposals and RFCs, code comments, and so on. Usually, this type of writing is to be read “offline”, at a slower pace. The rules of high resolution and empathy apply here, too.

Longer-form writing gives you an opportunity to dive deeper into why you are saying what you are saying. It is a chance to educate, to teach, to help understand and to level up.

How to communicate effectively as a developer

In the above example on the left, the comment I wrote does its job — it states that the introduced endpoint isn’t RESTful and that there is a bug in the implementation. There is nothing inherently wrong with this review comment, although it’s a little cold.

On the right is the comment I actually wrote. Because I knew the context and the recipient, I took some time to explain how the buggy implementation could crash and elaborated on how to fix it “properly” by making it more RESTful. It may seem like an overkill. And sure, if you’re providing feedback like this to an industry veteran, you are going to get some weird looks. That’s why it’s important to understand the context and write for the reader, empathically.

Now, I’m not saying you should be producing essays every time you sit down to do a PR review or provide feedback on a design document. Often it is totally fine to leave a few short, clear notes. But keep an eye out for situations where the reader might disproportionally benefit from you spending 10 minutes vs 2 minutes writing a response.

Long-form writing

This category of writing is very difficult to give tactical advice for. The length of documents produced can vary from a single page to several thousand words of prose and it's usually where structure and flow really start to matter.

Many organisations cannot afford (or simply do not see the point of) hiring a technical writer, so the task of producing clear, helpful, accurate prose falls on you, the developer. This, in turn, requires you to be your own editor.

Although this is true for shorter-form writing as well, I would recommend thinking about long-form writing, especially, as a lever. The clearer and easier-to-understand you make your writing, the longer the lever gets.

How to communicate effectively as a developer
Take the guesswork out.

Here's a concrete example:

Imagine you run a web service. It has a bunch of users. The service is powered by an API which you decide to open up to your users so they’ll be able to build their own integrations and interact with your service programmatically.

The quality of the API documentation will carry an astronomical amount of leverage. This leverage will work in both directions.

Genuinely helpful documentation is the difference between being swamped by support requests from frustrated API users and significantly increasing the usage of your service. Happy users beget more happy users.

API docs is just one example, the same goes for other texts like specifications, design documents, and feature and project pitches.

Why writing matters

We all write things. Most of the time what we write isn’t very impactful — a reply to a random email, quick banter with friends on Discord, a birthday card to a distant relative.

It starts to matter, and matter a lot, in a professional setting. When what you write is read by your co-workers, your manager or your manager's manager, it helps everyone if your writing is clear, concise and empathic.

The more our company grows, the more I believe in quality writing.

Spoken words get forgotten. Written words are shared, preserved, and become the basis of a company's culture.

— David Perell (@david_perell) October 25, 2022
Low resolution, shallow writing is first-order positive, second-order negative

First-order positive — You get your writing done quickly and can move on to the next task. It is more efficient.. for you.

Second-order negative — The reader will have to spend more energy to try to figure out what you wanted to say. Depending on the audience, the outcomes may range from frustration to loss of business.

How to communicate effectively as a developer
Three slides on empathy from a presentation about writing, thinking and learning by Michael A. Covington.
High resolution, empathic writing is first-order negative, second-order positive

First-order negative — You will have to spend more energy to make your writing easy to follow. You will have to grapple with your own confusion and holes in your understanding. You will have to figure out what the appropriate density for your writing is.

It's not about you, though.. It's about them.

Second-order positive — Not only does a single recipient benefit from your extra effort, what if ten people read your good work? A hundred? A thousand? What if the company CEO reads it? Taking writing seriously at work or in your organisation and putting in the effort to delight the reader will, over time, compound into a massive body of quality writing that benefits everyone. It is a literal win-win-win:

  • your readers gain a better understanding and level up,
  • your organisation benefits from better sharing of knowledge and increased productivity,
  • you get better at conveying your ideas and improve your career trajectory.
Produce writing you would read with delight if you were on the other end.

🙌🏻Thanks for reading.
If you have any comments or feedback on this article, I’d love to hear from you. Come say hi on Twitter or on LinkedIn.
634fabf71ad328003dc23c3e
Extensions
You should not be using AWS. Probably.
EngineeringAWSDevOpsOperations
Why choosing a popular cloud service, like AWS or Google Cloud Platform, may be the wrong choice for you and what to do instead.
Show full content
You should not be using AWS. Probably.

If you're an indie hacker, a boostrapper, a startup, an agency or a consultancy, or just a small team building a product, chances are you are not going to need the cost and complexity that comes with modern cloud platforms.

It costs how much?!

Unless you hire a good (read: expensive) devops person to look after your infrastructure, clickops-ing your way through the AWS console will likely leave behind a pile of unused instances and components that will eat into your budget.

On top of the financial cost, there is productivity and opportunity cost from having to understand, deal with and work around the idiosyncracies of complex cloud platforms. This effort is much better spent on delivering value to users.

Writing a highly available & scalable service that can handle 100K events per second-

- API that works on localhost: 4 days

- Terraform to create the API gateway, database, lambdas, queues, Route 53 records: 1 week

- Terraform to create the IAM policies: 4 weeks

— Vic 🌮 (@VicVijayakumar) September 15, 2022
There is a grain of truth in every joke.

Let's face it, choosing AWS is the cloud computing version of "nobody ever got fired for buying IBM". There is perceived safety in choosing a popular offering — it's what everybody else does. Many big players that product teams look up to, be it Netflix or Twitter or some other household name, have built all or some of their infrastructure on top of public cloud services. This creates scale-envy and blinds teams to the actual requirements that exist here and now.

The world is not driven by greed, it's driven by envy.
— Charlie Munger, vice chairman of Berkshire Hathaway

Just because Netflix, which accounts for about 15% of the global internet traffic, has built a complex architecture of custom software and microservices on top of a popular public cloud provider does not mean you absolutely, positively have to replicate their setup.

The all-you-can-eat buffet problem

Finally, there is the ecosystem. Having a great ecosystem is a huge benefit. It's like an all-you-can-eat buffet. And what happens in an all-you-can-eat buffet? You tend to overeat.

Public cloud platforms such as AWS have hundreds of services to choose from. It can be easy to convince yourself that you need to go serverless or run on ECS or set up your own Kubernetes cluster instead of using something like Heroku[1] or Render, when the former are at "arm's reach". And often it is so alluring that actual requirements and costs fly out the window. Need to scale... infintely? Just use Lambda Functions. Easy. A globally distributed datastore. OMG yes, we need it.

The problem is that every choice you make is not just the choice on its own, there are always second and third order implications down the line.

You should not be using AWS. Probably.

In a vacuum, each exotic technology only has benefits. It is like the old quote about LISP programmers: "(LISP) programmers know the value of everything and the cost of nothing". A specific technology product never exists in a vacuum — it has to communicate and co-exist with other components in the system. There are costs associated with every choice, often hidden costs.

A wealth of information creates a poverty of attention.
Herbert Simon, political scientist

Herbert Simon was right. The abundance of options pulls focus away from what really matters — your users and customers.

You should not be using AWS. Probably.
A chart to be read to the sound of your users tapping their collective foot.
Culture of simplicity eats strategy of complexity for breakfast

Using the shiniest new technologies is rarely the cause for success, it's usually the result. Resist the urge to buy an excavator when a shovel will do. Cultivate a culture of ruthlessly fighting complexity and focusing on what your users want.

1. Optimise for operational simplicity

We all want to learn and use new technologies, play around with new languages and frameworks, and add the latest tool to our resume. Cloud services give you plenty of rope to hang yourself with and Docker enables practically every team, regardless of size, to use their programming language or framework of choice.

This sort of complexity is extremely hard to operationalise. Google has roughly 28,000 engineers and only 7 official programming languages. Ask yourself, does your team of ten really need three different tech stacks?

It is much easier to go from simple to complex than the reverse. Instead of building directly on top of public cloud services like AWS or GCP, go with Heroku[1], Render or Fly. Start with a simple data model and architecture, and any one of those three will take you very far. Choose simple, "boring" technologies.

2. Optimise for internal simplicity

Pretty much every modern software project that touches the web is a complex system. You cannot build a correctly functioning complex system from scratch. This is why big-bang releases and sweeping re-writes hardly ever go well.

Invariably, all complex systems that work evolved from simpler systems that worked.
— John Gall, complex systems theorist

Although the marketing copy on public cloud websites can make it seem like choosing $TECHNOLOGY will enable your app to scale infinitely, the harsh truth is that neither Lambda Functions, nor Kubernetes, nor Kafka on their own will magically make your app work correctly, be performant and deliver value.

Most of this stuff is learned the hard way, earned as experience in the trenches. It comes down to good software engineering skills. It comes down to understanding databases, caching and queues. It means developing deep expertise in one or two programming languages to build the product with. It means thinking through what data access patterns will look like in production and how to design for read-heavy vs write-heavy services and endpoints.

None of this is easy. If it was, we wouldn't have any re-writes, over time and over budget projects, and products that collapse under the weight of their own technical debt.

3. Focus on delivering value to your users

A useful lens to look things through is "is what we're doing providing the most amount of value to our users, relative to what we could be doing?". It starts with technology, the data model and architecture choices, and ends with new features, products and services. I would argue that choosing to eliminate technical debt can be the highest value work if it improves developer workflow significantly and enables providing value to customers even more quickly/robustly/cheaply as a result. The best teams think like investors.

There are many examples of folks relentlessly focusing on delivering value. And there numerous examples of dead startups that burned through an astronomical amount of cash without ever making a dollar.

I am not saying you should be writing cowboy code and having no processes — quite the opposite. I am saying, though, that spending tens or hundreds of thousands on infrastructure does not automatically make you useful to customers. It just means you've over-engineered your product.

Calibrate your choice of programming languages, external software components, infrastructure and other technology against the litmus question:

"Why do we think this choice will provide the most value for users compared to the alternatives?"

And try to answer honestly.


[1]: I cannot recommend Heroku after tweets like this https://twitter.com/dannypostmaa/status/1625098298465087488


🙌🏻Thanks for reading.
If you have any comments or feedback on this article, I’d love to hear from you. Come say hi on Twitter or on LinkedIn.
632d9fc5b9d01d003da575a1
Extensions
GDPR compliance for 0.5 USD per month. Plus VAT.
ServerlessPythonAWS
The art and science of using modern cloud technologies to solve boring business problems at virtually no cost.
Show full content
GDPR compliance for 0.5 USD per month. Plus VAT.

Last year I was approached by a local property brokerage and development company with a problem — they had tens of thousands of contacts in their CRM, but unable to contact them without violating GDPR regulation.

The company ain't no spring chicken, most of their contacts had been in the database for years, added long before GDPR was "a thing". The company wasn't doing anything illegal, none of the contacts in the database had been added without their consent. Quite the opposite. The contacts in the CRM database — the people interested in buying property — had been added at their request. Ditto for any new contact added to the CRM database.

And there lies the crux of the problem. The CRM is an essential tool for the company staff. It's where work happens. But there is no digital record of the company requesting the prospective buyer to opt into receiving offers. And no way to record the opt-in consent in the CRM. Not easily, anyway.

There had been a few instances where a recipient of an offer shot back a semi-angry email asking to see proof that they had opted into the company's offers as a prospective buyer. With stories of lengthy legal battles and exorbitant fines around GDPR, the company leadership was eager to solve the problem once and for all.

Initial steps

The first couple of meetings with the client revealed several nuances around the details. What do we do about old buyers in the CRM database? Under what conditions should the opt-in email be sent? How and where do we record the information about the contact having received an opt-in email and having consented (or not) to receiving offer emails from the company?

The initial approach we explored was figuring out whether or not it's possible to do this with the CRM's built-in functionality. Scoro seems pretty powerful, but it didn't look like it was possible to craft a custom e-mail template with an "Opt in" button and hook that up so that some custom field gets filled automatically when the recipient clicks on the button. I'm surprised that the opt-in flow is not taken care of by the CRM software.

So, unfortunately, the CRM-native e-mail flow proved to be a dead-end. Upon reflection, I think this would've been a fairly naive implementation of what we were trying to do — we wanted to handle the contact's roles changing as well, for example.

Second attempt

Back at the drawing board, we realised that we'd probably have to build a custom piece of software to handle the entire opt-in flow. It would take care of sending the opt-in e-mail, handling the contact opt-in form, listening for contact role changes (from Seller to Buyer, for instance), and syncing information to Scoro to be seen and filtered on by the company's brokers.

The initial dead-end approach did have a silver lining — it revealed the ability to attach custom fields to each contact. We could use custom fields to record information from outside the system — like the custom opt-in widget! Luckily, the Scoro API exposes custom fields and allows the API client to interact with them almost as transparently as any other field. I went to work devising a system that could listen for new and updated contact events, send opt-in e-mail according to agreed-upon logic, present a form with opt-in fields, and synchronise data to and from Scoro.

The solution

One of the things the client prioritised was ongoing cost. They didn't want to pay a ton of money every month for keeping the widget going. This aligned quite nicely with my technical goals for this project, which I wanted to keep as simple as possible. To that end, I wanted to rely on as few external components as possible. I didn't want to use a database, either. I didn't want to keep any state outside Scoro whatsoever. I ended up devising a system built on a couple of custom fields in Scoro:

  • a custom field for storing the actual opt-in for receiving offers;
  • a custom field for storing the opt-in for receiving the company's newsletter (not shown in the below screenshot); and
  • a custom field for storing the.. err.. database, which is just a JWT that encodes the contact's ID and their roles — are they a buyer, a seller, an old client, etc.
GDPR compliance for 0.5 USD per month. Plus VAT.
My contact profile in Scoro

The database.. I mean the JWT is used to keep a minimum amount of state to be used by the external opt-in software. It needs to know how, if at all, the contact's roles change. Every time a contact is updated in Scoro the opt-in widget receives a webhook. Sadly, the webhook doesn't contain any information about what changed, only the new version of the entity. Since the JWT stores the contact's roles, it acts as a tiny bit of short-term memory allowing the widget to work out how the contact's roles changed and if it needs to send the opt-in e-mail.

Technology

Being a self-appointed member of the Boring Technology Club I chose Python and Flask as the core of the system. I've worked with both for years and know them inside out. Other boring technology used includes Pydantic for data (de-)serialisation, Flask-Mailman for sending e-mail through the company's SMTP servers, Flask-WTF for form handling and Tailwind CSS for making the form look pretty.

I initially deployed the app to render.com for testing with real data and presenting to the client for feedback. At the time, Render didn't have a free tier. The cheapest plan was $7/mo. The widget was just sitting there doing nothing most of the time, it seemed wasteful paying that. And of course, the client was interested in keeping the costs low. You could argue that $7/mo isn't the most expensive thing in the world, but paying $7/mo for doing essentially nothing is quite expensive.

We had just started talking about serverless at $WORK and even though I am sceptical of the benefits for large complex applications, it seemed like a great opportunity to learn more about the technology. I hadn't used any innovation tokens on building the app itself, so I felt that paying one or two tokens for exploring serverless could be a pretty good bet. The extremely competitive per-ms pricing coupled with the reduced stress of not having to manage servers made serverless seem like a great fit for this project.

GDPR compliance for 0.5 USD per month. Plus VAT.
http://boringtechnology.club/#17

The first course of action was to explore Chalice as a potential vehicle for deploying the opt-in widget as a serverless stack. Given that the Chalice Github project lives under the AWS Github account, it seemed like the way to go — an official AWS-approved way to deploy an app to AWS Lambda. But soon after diving in I realised I'd have to re-build most of the app I'd already built in Flask, in Chalice. Bummer.

A bit of Googling surfaced another library/framework called Zappa. The README immediately looked promising:

Zappa makes it super easy to build and deploy server-less, event-driven Python applications (including, but not limited to, WSGI web apps) on AWS Lambda + API Gateway. Think of it as "serverless" web hosting for your Python apps.
[...]
It's great for deploying serverless microservices with frameworks like Flask and Bottle, and for hosting larger web apps and CMSes with Django. Or, you can use any WSGI-compatible app you like!
GDPR compliance for 0.5 USD per month. Plus VAT.
Sign me up!

The setup was very quick indeed — I created a zappa_settings.json file with the appropriate AWS resource ARNs, ran zappa deploy production and sure enough, the moments-ago-boring WSGI app was now a fancy server-less wonder deployed on AWS Lambda and API Gateway. Pretty cool!

I spent a couple more hours getting the subdomain for the widget set up correctly with Route 53 and ACM, and wiring that up to the API Gateway custom domain configuration.

The payoff

To put it all into perspective, the EU data protection authorities can impose fines of up to 4% of annual turnover in very severe cases. The worst case scenario for the client would be getting fined north of a hundred thousand euros. It's unlikely that it would ever come to that, but the risk is still there.

GDPR is a good thing in general, but I think it's much easier for larger companies to deal with it than it is for smaller companies. The regulation does not differentiate based on company size. A Google-sized company can throw millions of euros behind getting it right (and still get it wrong). What hope is there for small companies who have to be as compliant as Google on the face of it, but have microscopic budgets compared to Google?

But.. the innovation tokens paid off! At the current traffic levels and with a whole lot of headroom, the client is paying $0.5 per month + VAT — that's ~12x cheaper than the initial spike with Render. Not bad for avoiding tens, if not hundreds of thousands of euros in fines.

(Edit: As David Chisnall pointed out on lobste.rs — the described solution is incomplete. And I agree. The opt-out / right-to-be-forgotten flow isn't very polished at the moment. We're still working on that.)

GDPR compliance for 0.5 USD per month. Plus VAT.
AWS bill for January 2022
632d9fc5b9d01d003da5759b
Extensions
3-step process to deploying SPA and API monorepo using Heroku postbuild
Heroku
Build and serve a SPA + API monorepo on Heroku using Heroku postbuild. Includes a one-click deploy reference implementation.
Show full content
3-step process to deploying SPA and API monorepo using Heroku postbuild

Someone on Stackoverflow asked a question about how to run npm run build and have the backend serve the frontend app as part of the Heroku deploy pipeline. In other words, how to deploy a frontend-backend monorepo on Heroku. As I had pieced the solution together when I was porting Lindy to Heroku, I wrote a brief answer. This post is a more detailed guide to setting everything up.

karls/spa-with-herokuHow to build and serve a frontend app with a backend app on Heroku - karls/spa-with-heroku3-step process to deploying SPA and API monorepo using Heroku postbuildGitHubkarls3-step process to deploying SPA and API monorepo using Heroku postbuild
👆 Reference implementation on Github

The basic principles behind building the frontend app as part of your deploy and subsequently serving it via the backend app boil down to:

  1. using correct buildpacks on Heroku;
  2. instructing Heroku's build process to compile the frontend app before compiling the app slug and caching dependencies for subsequent builds
  3. ensuring that the compiled frontend assets end up in a directory where the backend app expects them.
1. Using correct buildpacks

Heroku will automatically detect the buildpack to use using certain heuristics. E.g if your app is a vanilla Python app, Heroku will automatically detect the correct buildpack (Python) for your app.

However, since we're also trying to compile the frontend assets, we are going to have to tell Heroku to use the JavaScript buildpack in addition to the buildpack for your backend language.

Adding a buildpack is pretty straightfoward, you can do it either via the Heroku CLI or via the Heroku Dashboard. Here's what the buildpacks for Lindy look like. Note: the ffmpeg buildpack is specific to Lindy, you likely don't need it.

3-step process to deploying SPA and API monorepo using Heroku postbuild
Buildpacks in app settings after adding the NodeJS and ffmpeg buildpacks
2. Use Heroku postbuild to compile the JavaScript app and cache dependencies

The next step is to compile the frontend app as part of the build process on Heroku. Add a package.json to the root of your project if you don't have one already. The two key things needed in package.json are the instructions for building your frontend app and caching the node_modules directory.

For instance, if your frontend app is located in client/, here's what package.json in the project root should look like (other keys omitted).

{
  ..
  "scripts": {
    "heroku-postbuild": "cd client && yarn install && yarn run build"
  },
  "cacheDirectories": [
    "client/node_modules"
  ]
  ..
}
package.json in the project root

Heroku will automatically run the specified command(s) in heroku-postbuild — in this instance yarn install and yarn run build. Swap those out as needed for npm. It'll also cache the node_modules in the client/ directory as per cacheDirectories directive.

3. Ensuring compiled files end up in the right place

It's good practice to .gitignore compiled files. Your build step should output the compiled-for-production files (i.e main.{js,css}) into a directory where your backend will know where to expect them, both in development and in production.

For instance, with Lindy, in development, main.{js,css} are being continuously compiled by Rollup as the source files are updated. In production, Rollup will compile, tree-shake and uglify main.js, and purge and minify main.css into the same directory as in development. In other words, the production app will find the relevant files in the same directory as in development, except in production they're compiled for production by the Heroku build process.

This is what a typical directory structure in such a scenario might look like. Roughly speaking, the build step in package.json compiles files from client/src into server/static, which is where the server will know to look for them.

⌞ package.json
  ⌞ client
    ⌞ src
    ⌞ node_modules
    ⌞ ...
  ⌞ server
    ⌞ static
    ⌞ src
    ⌞ ...
Reference implementation

An example repository implementing the pointers in this guide, with plain JavaScript bundled with Rollup, served by a Flask app, is available on Github.

632d9fc5b9d01d003da57596
Extensions