For the first eight months of building Nventory we obsessed over sync speed.
p99 sync lag. Propagation time per channel. Webhook delivery rates. Time from order confirmation to every channel reflecting the updated stock count.
We got it to under 800 milliseconds on average. We were proud of that number.
Then a seller asked us something we didn't have a good answer to.
"How will I know when I can stop thinking about inventory?"
Not when will it be accurate. Not when will the oversells stop.
When can I stop thinking about it entirely.
We talked about sync speeds. She nodded politely and said: "That's still thinking about it."
The metric we were measuring vs the metric that mattered
javascript
// What we were measuring
const metrics = {
syncLagP99: '780ms',
propagationSuccessRate: '99.7%',
oversellRate: '0%',
webhookDeliveryRate: '99.9%'
};
// What she was asking us to measure
const metricThatActuallyMattered = {
weeksWithoutInventoryCrossingHerMind: 0 // we had no idea
};
We had built a tool that made inventory management faster and more accurate.
We hadn't built a tool that made it disappear from someone's mental load entirely.
Those are different products.
What "mental load" actually means technically
Mental load in a software context is the number of decisions a user has to consciously make to keep the system working correctly.
Every manual stock update is a decision.
Every cross-channel reconciliation is a decision.
Every "did this sync correctly" check is a decision.
Every low stock alert that requires a human to evaluate and act on is a decision.
We had eliminated the obvious decisions — the manual updates, the reconciliation, the oversell firefighting. But we hadn't eliminated the ambient ones. The background hum of "is everything okay" that runs constantly in an operator's head.
javascript
// Decisions we had eliminated
const eliminatedDecisions = [
'manually update stock after each sale',
'check each channel for sync failures',
'pause listings when stock hits zero',
'reconcile end of day inventory'
];
// Decisions we hadn't eliminated yet
const remainingMentalLoad = [
'check dashboard to confirm everything is fine',
'evaluate low stock alerts and decide action',
'verify that automations ran correctly',
'wonder if anything broke overnight'
];
// The second list is smaller but higher frequency
// It's what wakes operators up at 3am
The second list is what she meant when she said "that's still thinking about it."
What we built differently
The shift wasn't in the core sync architecture. That was already right.
It was in the notification layer and the automation layer.
Notifications that only fire when action is required — not to confirm normalcy
javascript
// What we were doing
async function sendInventoryAlert(sku, currentQty, threshold) {
if (currentQty < threshold) {
await notify({
message: ${sku} is below threshold. Current: ${currentQty},
action: 'Review inventory' // still requires human decision
});
}
}
// What we changed to
async function handleLowStock(sku, currentQty, threshold) {
if (currentQty < threshold) {
// Execute the action automatically if rule exists
const rule = await automationRules.getForSku(sku, 'low_stock');
if (rule) {
await rule.execute({ sku, currentQty });
// Only notify if execution failed or needs confirmation
if (rule.requiresConfirmation) {
await notify({ message: `Executed: ${rule.description}`, sku });
}
// Otherwise — silent execution. No notification. No decision required.
} else {
// No rule exists — now it makes sense to notify
await notify({
message: `${sku} below threshold — no automation rule set`,
action: 'Set up automation rule once, never think about this SKU again'
});
}
}
}
The difference: notifications that fire when something needs a human are valuable. Notifications that fire to tell you a machine did its job are noise that keeps you thinking about the system.
Automations that encode decisions permanently
javascript
// The old model — alert then decide
// Low stock → alert → human evaluates → human acts → repeat next time
// The new model — decide once, automate forever
const automation = await automationBuilder.fromDescription(
"When Hoodie SKU drops below 20 units, pause listings on eBay and TikTok,
keep Shopify active, and alert the warehouse team — not me"
);
await workflowEngine.register(automation);
// The seller makes this decision once
// It runs automatically every time the condition is met
// They never think about this SKU again
The key phrase from the seller's perspective: "not me." The warehouse team needs to know. She doesn't. Building that distinction into the notification routing was the change that made inventory disappear from her mental load.
The new metric
Three months after rebuilding the notification and automation layer — she messaged us.
"I haven't thought about inventory in two weeks."
We added a metric to our internal dashboard.
javascript
const mentalLoadMetrics = {
// Proxy metrics for mental load reduction
automationCoverageRate: 'percentage of recurring decisions with automation rules',
notificationActionRate: 'percentage of notifications that require human action',
// Target: >95% of notifications require action
// If you're notifying for confirmations — you're creating mental load
supportTicketTopics: 'categorised by whether they represent a recurring decision',
// Recurring decisions that generate support tickets = unautomated mental load
dashboardOpenRate: 'how often sellers open dashboard vs how often they need to',
// Target: dashboard opened because something needs attention
// Not because seller is checking everything is okay
};
We still track sync speed. But the metric we actually care about is how long sellers go without thinking about inventory.
The best infrastructure is invisible. Not fast. Not accurate. Invisible.
The question for developers
What's the difference between the metric you're optimising for and the metric your users actually care about?
And how would you even know if those two things were different?
Worth exploring: nventory.io — free forever
Shopify App Store: apps.shopify.com/nventory
Top comments (0)