What One Account Actually Costs You to Serve
Northwind Logistics pays $500 a month. They have been on the plan for two years, they renew without a conversation, and their name shows up in the quarterly board deck under "healthy accounts" because the revenue line is flat and green.
Here is what is not in the board deck. Northwind files roughly four support tickets a week. Two of them are the same question about CSV export encoding, asked again by a different person on their side. The third is usually a genuine bug in the routing module they use more heavily than anyone else. The fourth is a request that starts in the support queue and ends up in #eng-platform, where two of your senior engineers spend an afternoon reproducing it, arguing about whether it is a data problem or a code problem, and then writing a workaround they will have to write again next month.
The support lead knows Northwind is heavy. The engineers know the name. Nobody has ever put a number on it, because the number does not live anywhere. The ticket count lives in Zendesk. The engineering time lives in a Slack thread where someone typed "ok I'll take a look" at 2pm and posted a fix at 6pm. The $500 lives in Stripe. Nothing joins them.
The question nobody asks out loud
"What does it cost us to serve this account?" is a normal question in any business with a cost of goods sold. In software it gets waved away because the marginal cost of a seat is near zero, which is true right up until the moment a human is involved.
The moment a human is involved, the cost is real and it is large. A senior engineer's fully loaded hour is not cheap. Four hours of two engineers, once a month, is eight engineer hours. Add sixteen tickets a month at whatever your average handle time is. Put a number on those and hold it next to $500 in monthly revenue, and the answer is sometimes uncomfortable and sometimes fine. The point is that you do not currently know which.
This matters at three specific moments:
- Renewal. Northwind asks for a discount. You do not know whether to fight for the price or let them walk.
- Roadmap. The routing module keeps generating tickets. Is that a $500 problem or a $40,000 problem across the book?
- Pricing. If accounts with a certain shape reliably cost three times what they pay, that is a packaging decision, not a support decision.
Why the tools you already own do not answer it
Be fair to them. This is not a case of BI tools being bad.
Your BI stack is genuinely capable. Power BI Copilot will answer a plain-English question about a modelled dataset. Looker's conversational layer will do the same against a semantic model. Tableau Pulse will surface a metric change you did not ask about. If the number you want is in a warehouse table, these tools will find it and probably explain it.
The problem is upstream of all that capability. Looker speaks SQL dialects. Metabase ships nineteen official drivers and every one of them is a database. Whatever your BI tool's natural language layer can do, it can only do it to things it can see, and what it can see is databases and modelled sources.
The engineering time in this question is not in a database. It is in a Slack thread. Specifically it is in the fact that Priya replied to a support escalation on the 14th at 2:07pm, then posted eleven more messages in that thread over the next four hours, then said "deployed, should be fixed." There is no row anywhere that says "Priya, Northwind, 4 hours." There is a conversation that a human reading it would score as roughly four hours, and turning that into a fact table means someone first deciding it is worth building a pipeline for prose.
Internal tools builders have the mirror problem. Retool will generate a working app from a prompt, and it will write back to your database when a user clicks a button. That is real. But a CRUD panel needs a table underneath it, and there is no engineering_time_by_account table. Building the app is not the hard part. Having the data is the hard part, and the data is prose.
Zapier and Make are also not the answer, for a different reason. They are trigger-based. They fire when something happens. This question is retrospective: you are asking about the last six months, which have already occurred, and no trigger you set up today will go back and watch them.
What the question actually requires joining
Three sources, none of which know about each other:
| Source | What it holds | Shape |
|---|---|---|
| Zendesk (or Intercom, Linear, Jira Service Management) | Ticket count, first response time, resolution time, requester, tags | Structured, per ticket |
| Slack | Escalation threads mentioning the account, who participated, over what span of time | Prose, timestamped |
| Stripe (or your billing DB) | Plan, MRR, tenure, renewal date | Structured, per customer |
The join key is the account name, and it is spelled differently in all three. Stripe has "Northwind Logistics Inc." Zendesk has an organization called "Northwind." Slack has people typing "northwind" and "NWL" and occasionally the name of their ops manager instead of the company. That fuzzy matching is exactly the sort of thing a language model handles well and a JOIN clause handles badly, which is a large part of why this report has never been built.
Answering it with Skopx
Skopx connects to nearly 1,000 SaaS tools and to databases directly, and you ask it questions in chat. So the first version of this is not an app at all, it is a sentence. Something like:
For our top 40 accounts by MRR, pull ticket volume and total handle time from Zendesk for the last two quarters. Then search Slack for escalation threads that mention each account by name, and for each thread estimate engineering hours from who participated and how long the thread ran. Put that next to their MRR from Stripe and show me cost to serve against revenue, using $120 an hour as the loaded rate.
You get an answer with citations, which matters here more than usual, because the Slack half of this is an estimate and you will want to click through and check whether the estimate is sane. Some of it will not be. You will find a thread where the account name was mentioned in passing and nothing was done, and you will tell it to exclude threads shorter than five messages. You will find that "NWL" also matches an unrelated internal project. You refine the sentence two or three times until the numbers stop making you wince.
Then you turn it into a screen. Skopx's internal apps feature builds a console from a description: you say what you want to see, it runs the read-only query against the tools you already connected, looks at the rows that actually came back, and renders the page to fit them. Something like:
Build me an account cost-to-serve console. One row per account: MRR, tickets last 90 days, total handle time, estimated engineering hours from Slack escalations, estimated cost to serve at $120 an hour, and the ratio of cost to revenue. Sort worst ratio first. Add a bar chart of the ten worst. Add a button on each row that posts the account's summary to #cs-leadership.
What the console shows
Four metric tiles across the top: total cost to serve across the book, the number of accounts where cost exceeds revenue, total engineer hours absorbed by escalations, and the median ratio.
Below that, the table, worst ratio first. Northwind is near the top. The row reads $500 MRR, 51 tickets in 90 days, 22 hours of support handle time, 19 estimated engineering hours, and a cost figure that is comfortably above what they pay. Next to it are four other accounts you would not have guessed, and one you absolutely would have.
A bar chart of the ten worst by ratio, because the table is for reading and the chart is for showing someone else.
And on each row, one button: post this account's summary to a Slack channel. A person clicks it, confirms, and the message goes out through the connected Slack account. That is the only thing on the page that writes anywhere, and it does not fire on its own.
What the console does not do is worth stating plainly. It does not store the cost figure anywhere. There is no cost_to_serve table being maintained behind it, and no nightly job filling one. Every time someone opens the page it re-reads Zendesk, Slack and Stripe and works it out again. That is a limitation in some ways and a feature in others: there is no stale number, and there is no new system to own.
The honest limits
Slack participation is a proxy, not a timesheet. This is the big one and it needs saying clearly. When the model reads a thread where Priya posted at 2:07pm and again at 6:14pm with nine messages in between, it infers that she spent a meaningful part of that afternoon on it. She may have spent forty minutes on it and the rest of the afternoon in a design review. Or she may have spent six hours, including two before she posted anything. The estimate is directionally useful and individually wrong. Treat the ranking as real and the absolute hours as a rough band.
Work that never touched Slack is invisible. A pairing session, a call, a fix someone made quietly. If your engineers escalate over Zoom or DM, this measures less than it should.
Name matching will misfire. Accounts with generic names are the worst offenders. Expect to add exclusions.
Ticket handle time is only as good as your queue hygiene. If people leave tickets open over the weekend, handle time is inflated. If they close and reopen, it is deflated.
The loaded hourly rate is an assumption you state. It lives in the sentence that builds the page, not in a settings screen or a saved record, so if finance revises the rate you say so and the console is rebuilt with the new one. Whatever number you use drives every dollar figure on the screen, so keep it visible in the page's own title or description.
Nobody gets told. The console does not watch for accounts crossing the line and it does not send anything to anyone. It is a page someone opens, most usefully a week before a renewal batch or when the pricing conversation comes round again.
None of that makes the number useless. It makes it an estimate with a known error bar, which is a different thing from no number at all, which is what you have now.
Close
The reason nobody has built this report is not that it is hard to compute. It is that half the inputs are sentences. Support cost and revenue have lived in queryable systems for twenty years, and the engineering half has lived in whatever chat tool the company happened to adopt, in the form of one person saying they will look at something and later saying they fixed it.
You do not need a data model for that. You need something that can read the thread, hold it against the ticket queue and the billing record, and give you a number you can argue with. Then when Northwind asks for a discount at renewal, the conversation you have internally is about a figure rather than a feeling.
Skopx Team
The Skopx engineering and product team