Back to all posts
October 12, 2026

What Is Abstraction? (And Why It Used to Be Too Expensive to Bother)

What Is Abstraction? (And Why It Used to Be Too Expensive to Bother)
Listen to this post
0:00 / 7:03

Your app sends emails. You wired it straight into one provider. That provider gets suspended on a Tuesday and now your app doesn't send email, and your clients are calling. Happened to me. Abstraction is the fix: one layer in the middle, three providers behind it, cheapest first. It used to be too much work to justify at the MVP stage. That excuse just expired.

I've written before about single points of failure: one piece of your system where, if it breaks, the whole thing comes down with it. No backup, no fallback, no second option. Just a hole where your product used to be.

This article is about the main tool for fixing that, and about why the usual excuse for skipping it doesn't really work anymore.

The setup

Say you're building a product. Among other things, it sends emails and text messages. Normal stuff. Password resets, appointment reminders, receipts.

To actually send an email you use a provider. SendGrid, Postmark, Resend, whoever. Same for texts, Twilio or one of its cheaper competitors. You don't go build a relationship with every carrier and mail server on earth, you pay somebody who already did and you talk to their API.

There are two ways to wire that in. The difference between them is the whole article.

Way one: hard code it

Architecture diagram showing a box labeled your app wired directly into a single box labeled email provider, with a red X over the provider and the app going dark, labeled hard coded
One wire, one provider. Pull the wire, the lights go out.

"Hard coded" just means it doesn't change. The provider is baked directly into your code, everywhere you send an email. Their specific function calls, their specific way of formatting things, their key, all of it, scattered through your app wherever an email goes out.

And it works! It works great, right up until it doesn't.

Because here's what actually happens in the real world. Your provider suspends your account over a deliverability flag. Or they have an outage. Or they change their pricing. Or they decide your industry is now against their terms. Any one of those, and your product cannot send email. Not "sends slower." Cannot.

Fun fact, that happened to me. I was fast enough to get it back up before an angry phone call came in, but I was sweating, and the only reason it ended fine is that I happened to be at my desk. That's not architecture. That's luck with good timing.

Way two: put a layer in front of it

Now the same product, but instead of wiring the provider in directly, you add one layer in between. That layer is the abstraction.

In the simplest version, you write one function. Call it sendEmail. Everywhere in your app that needs to send an email calls sendEmail and nothing else. Your app does not know, and does not care, who actually delivers the thing.

Behind that function, you put your providers in order.

Architecture diagram showing your app connecting to a single middle box labeled sendEmail which fans out to provider one, provider two and provider three with fallback arrows, provider one marked with a red X and provider two lit green
The app only ever talks to the middle box. The middle box decides who actually sends it.

Provider one is the cheapest, so it gets tried first. It works maybe 90% of the time, which is fine, because that's 90% of your volume going out at the lowest cost.

If provider one fails, the layer catches that and quietly tries provider two. Costs more. Still sends.

If two fails, three. Most expensive, last resort, but your email goes out and your customer never knows anything happened.

Three stair steps rising with increasingly large price tags labeled cheapest, more, most, and an envelope icon stopping on the first step
Order them by cost and the expensive ones only get used when they have to.

Now go back to the scenario. Provider one gets suspended on a Tuesday afternoon while you're at lunch.

In the hard-coded version, your product is broken and you find out from a client.

In the abstracted version, your bill goes up slightly for a few hours and nobody notices. You fix it when you get back.

I want to be honest: it's still not pleasant. Account suspensions are their own flavor of nightmare, and you'll still be on the phone with somebody's support team explaining yourself. But the difference between "annoying thing I have to deal with" and "my product is down and my clients are angry" is enormous, and that difference is one layer of code.

Why anybody ever skipped this

Here's the part I actually want to make, because the concept above is not new. Every decent engineer knows what abstraction is. Plenty of real systems still don't have it. Why?

Because it used to be genuinely expensive to build.

An abstraction layer is a whole extra piece of architecture. You have to define the interface, handle the differences between providers (they all format things differently, they all fail differently), handle retries, decide what counts as a failure worth falling back on, and then go refactor every single place in your codebase that was calling the provider directly. That's not an afternoon of typing. Historically that was weeks, and weeks you weren't spending on anything a customer would notice.

So in the MVP stage, people hard code it. And honestly, I get it. I've built plenty of duct-tape-and-chewing-gum products. My first one was a form glued to a Zapier endpoint. When you're trying to find out if anybody wants the thing at all, spending three weeks on resilience for a product with four users is the wrong call. That's correct prioritization, not laziness.

That excuse just got a lot weaker

Two calendar blocks side by side, one spanning three full weeks labeled before, the other a single afternoon labeled now, with an arrow shrinking between them
Same work. The price of doing it correctly collapsed.

AI is a leverage machine, and I cannot overstate how much it compresses this specific kind of work. Refactoring is mechanical. Tedious, careful, pattern-following work across a lot of files. That is exactly what these tools are genuinely great at.

The three-week job is an afternoon now. Maybe a day if the codebase is messy.

Big caveat, and I'll keep saying it: you have to actually know what you're doing. You cannot type "add abstraction" and walk away. You have to specify the interface, the fallback order, what a failure means, how to handle the provider-specific weirdness, what gets logged. The judgment is still entirely yours, and if you hand the architecture decisions to the model you will get something confident and wrong. I've watched it try to put my office computer on the public internet. It does not know what matters.

But if you can specify it clearly, the implementation cost has fallen through the floor. Which means the gap between "scrappy MVP that barely holds together" and "real product that handles a bad Tuesday" is a much shorter trip than it was two years ago.

Three reasons to do it

One, resilience. The obvious one. No single vendor can take your product down. For anything load-bearing, email, texts, payments, storage, that alone justifies it.

Two, cleaner code. This one's underrated. When every email in your app goes through one function, there's exactly one place to change how email works. Want to add logging? One place. Want to swap providers entirely? One place. Want to add a rule that marketing emails go out through a different sender? One place. Compare that to hunting through forty files for every spot somebody called the provider directly. Abstraction isn't just insurance, it makes the codebase easier to reason about, and being able to reason about your system is most of the job.

Three, it's cheap now. See above. The cost-benefit math that justified skipping it has changed.

One warning, because I believe in the middle

Don't go abstract everything. I've written about this in On Architecture and I'll repeat it: too little structure gives you a hack you'll rip out in six months, and too much gives you a machine nobody can maintain. Piling abstraction layers on top of each other for their own sake is its own kind of slop.

The test I use is simple. Abstract the things that can take you down or that you might realistically need to swap. Providers, yes. Payments, yes. Storage, yes. The specific way you format a date, no. If nothing bad happens when it changes, it doesn't need a layer.

The takeaway

Abstraction is just this: don't let your app talk directly to anything you can't afford to lose. Put a layer in the middle, give it a list of options, order them by cost, and let it fall back on its own.

It used to be a luxury you earned after product-market fit. Now it's an afternoon. If you've got a hard-coded provider sitting in the load-bearing path of your product, that's your weekend.

And if you'd rather somebody who's already had the Tuesday afternoon panic build it, that's what we do.

Want to talk about what you're building?

Get in touch