Make.com Is Great Until You Need It to Think
I use Make.com for real work. I also know exactly where it stops being a tool and starts being a liability. That line is sharper than most people draw it.
I've built probably thirty Make.com scenarios in the last two years. I've also ripped three of them out and replaced them with Laravel webhook listeners because they got too clever. The boundary between "great use of Make" and "you're going to regret this" is real, and most people cross it without noticing.
What Make.com Actually Is (In Plain Terms)
Make is a visual automation platform — like Zapier but with more power and a steeper learning curve. You build scenarios: trigger something, transform the data, push it somewhere else. A webhook fires, you parse the payload, you hit a few APIs, you send a Slack message. That's its wheelhouse.
The pitch is that non-developers can build integrations. That's true, within limits. The honest use case is glue code — connecting two systems that both already have APIs, where the mapping between them is stable and the transformation logic is simple. Think: new Stripe customer → create row in Airtable → send welcome email. That's a perfect Make scenario. It's boring, it works, and I'd feel no shame handing it to a non-engineer to maintain.
Where I Actually Use It
At NWOS I use Make for things that would be tedious to code but have no real logic involved:
- New form submission from Typeform → normalize fields → POST to our Laravel app's ingest endpoint → notify client via Slack
- Shopify order placed → filter for specific SKUs → create fulfillment task in a project management tool
- Scheduled scenario to pull a report CSV from an SFTP server and email it to a client every Monday morning
- Catch a webhook from a third-party service that doesn't support custom endpoints, transform the payload shape, re-deliver to our app
Notice what those have in common: the business logic — the decisions about what matters and what to do about it — lives somewhere else. Make is the postal service. It picks up the package and delivers it. It doesn't decide if the package should be delivered.
The Moment It Goes Wrong
Here's a real situation. A client in print management needed to route inbound orders from three different vendor portals into their production queue. The routing rules were simple at first: vendor A goes to queue 1, vendor B goes to queue 2. I built it in Make. Two modules, done in an hour.
Six months later the routing rules had grown. Vendor A orders under a certain dollar threshold go to queue 1, but only on weekdays, unless the SKU prefix is RUSH, in which case they go to queue 3 regardless of vendor. Also, if a customer account flag is set to NET30, hold the order in a pending state and fire a different webhook to the billing system.
I was maintaining that Make scenario, not a client. And let me tell you — debugging conditional paths in Make's visual canvas when you have four routers, seven filter conditions, and a nested iterator is not fun. It's not readable. There's no version control. When something breaks at 11pm you're clicking through a web UI trying to figure out which branch didn't execute and why.
I rewrote it as a Laravel webhook listener in an afternoon and felt immediate relief.
What a Laravel Listener Actually Looks Like Here
This is the skeleton I'd reach for. Nothing exotic.
// routes/api.php
Route::post('/webhooks/vendor-order', [VendorOrderWebhookController::class, 'handle'])
->middleware('webhook.verify:vendor_secret');
// app/Http/Controllers/VendorOrderWebhookController.php
class VendorOrderWebhookController extends Controller
{
public function handle(Request $request): JsonResponse
{
$payload = $request->validated(); // or json_decode, depending on your middleware
dispatch(new ProcessVendorOrder($payload));
return response()->json(['status' => 'queued'], 202);
}
}
// app/Jobs/ProcessVendorOrder.php
class ProcessVendorOrder implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(private readonly array $payload) {}
public function handle(OrderRouter $router): void
{
$order = VendorOrderData::fromPayload($this->payload);
$queue = $router->resolve($order);
$queue->enqueue($order);
}
}
// app/Services/OrderRouter.php
class OrderRouter
{
public function resolve(VendorOrderData $order): ProductionQueue
{
if ($order->isRush()) {
return ProductionQueue::rush();
}
if ($order->customer->isNet30() && $order->vendor === Vendor::A) {
BillingSystem::holdForApproval($order);
return ProductionQueue::pending();
}
return match ($order->vendor) {
Vendor::A => $order->isBusinessHours()
? ProductionQueue::forVendorA()
: ProductionQueue::overnight(),
Vendor::B => ProductionQueue::forVendorB(),
default => throw new UnroutableOrderException($order),
};
}
}
That's readable. It's testable — I can write unit tests for OrderRouter::resolve() and cover every branch in five minutes. It's in Git. When the routing rules change (and they will change), I update OrderRouter, write a new test, deploy. The next developer who touches this can understand it.
Try writing a PHPUnit test for a Make scenario. I'll wait.
The Specific Gotchas That Make Makes Worse
Error handling is opaque. When a Make scenario fails, you get a log entry with a status code and whatever error message the downstream API returned. If your error is in your own logic — a filter that silently dropped a record because the condition was wrong — you might not know for days.
No version control, no review process. Anyone with scenario edit access can change logic and there's no diff, no audit trail beyond Make's own history UI. For a two-person startup this is fine. For a client in healthcare or biotech, this is a problem I won't take on.
Rate limits and retries are manual. Make has retry logic, but it's coarse. If I need exponential backoff, dead-letter handling, or idempotency checks on redelivered webhooks, I'm either hacking around Make's limitations or giving up and writing code.
Secrets management is uncomfortable. API keys stored in Make's connection vaults are fine for low-stakes stuff. But I'm not putting a client's EHR credentials or LIMS API key into a third-party SaaS automation platform. That's not paranoia, that's a reasonable security boundary.
Conditional logic doesn't scale visually. Make's router module is genuinely clever up to about three branches. Beyond that the canvas becomes a spaghetti diagram that I have to re-learn every time I open it.
Where I'd Still Reach for Make
I'm not anti-Make. I'll use it without hesitation when:
- The transformation is simple field mapping with no branching logic
- The maintainer might be a non-developer client or account manager
- The data is not sensitive enough to worry about where it transits
- The failure mode is low-stakes (a missed Slack notification is not a missed patient record)
- Iteration speed matters more than correctness guarantees — prototype something, see if it's worth building properly
There's also a legitimate cost argument. If a Make scenario does the job and never needs to change, it's cheaper to run than deploying and maintaining a Laravel job. I'm not going to pretend developer time is free.
The Line I Actually Draw
Here's the rule I've settled on after doing this long enough:
If I'd want a test for it, I write code. If I wouldn't, Make is fine.
Field mapping, notification fanouts, simple filtering, scheduled report delivery — no test needed, Make it. Routing logic, state transitions, anything that touches money or medical data, anything where a silent wrong answer is worse than a loud failure — that gets a Laravel listener, a queue, tests, and a deploy pipeline.
Make is a tool for connecting APIs. Laravel is a tool for modeling business behavior. The mistake I see teams make is treating them as substitutes rather than complements. They're not. Make doesn't replace your application layer; it replaces your cron job and your handwritten Guzzle glue code for the simple stuff.
The moment your Make scenario needs a "complex aggregator" or you're writing JavaScript in a "custom function" module to handle a case the built-in modules can't, you've already crossed into application logic territory. Stop. Write the controller. Your future self will find it in the codebase instead of in a browser tab.
Need help shipping something like this? Get in touch.