The Discount Promised in an Email and Never Applied in Billing
A customer emails your CFO in March. The subject line is "Invoice question, third one in a row." The body says: "Your rep confirmed 20% off when we upgraded in November. We've been paying full list since December. Please advise."
The CFO forwards it to the account owner, who left in January. It bounces. She forwards it to the VP of Sales, who forwards it to the rep who inherited the account, who has never heard of a discount. Someone opens Stripe. The subscription shows the standard annual plan at list price, no coupon applied, four clean invoices, all paid. Someone else opens the CRM. The opportunity is closed-won at full ACV. Someone opens the contract in the e-signature tool. It references the order form, and the order form says list price.
By every system of record you own, the customer is wrong.
Except the customer is not wrong. Three days later, after someone thinks to search the shared inbox for the customer's domain, the email turns up. November 14th, from the rep who has since left, in a thread that started about SSO provisioning and drifted into pricing by the fourth reply:
"Totally hear you on budget. I can do 20% off the annual for you given you're taking all three seat tiers at once. Let's get the paperwork moving and I'll make sure billing reflects it."
That is the entire commitment. One sentence, buried in a thread about something else, sent from a mailbox that still exists but belongs to nobody. It was never written on the order form. It was never entered in Stripe. It never became a field anywhere. It was, and remains, prose.
Why nothing you own can find this
Your billing system is doing exactly what it was built to do. Stripe records what was charged. It has a coupon object, a discount object, a subscription with a price attached. If a discount exists there, it was applied. If it does not exist there, then as far as Stripe knows, no discount was ever contemplated. Stripe has no opinion about promises. It has no field for "a person said this in an email and meant it."
Your contract tool is doing the same. It records what was signed: the order form, the MSA, the amendment. If the 20% never made it onto a document that got countersigned, the contract tool is silent, correctly.
Your CRM records what was forecast and what closed. Some CRMs will let you log a discount percentage on the opportunity, and some reps use that field. This one did not, because the conversation happened after the opportunity was already marked closed-won, and nobody goes back to edit a closed record.
So you have three systems that between them cover what was signed and what was charged, and a gap in the middle that is exactly the shape of what was said. The commitment lived only in the gap.
This is also why your analytics stack cannot help, and it is worth being precise here, because the usual complaint about BI tools is unfair. Power BI has Copilot. Looker has Conversational Analytics. Tableau has Pulse. You can type a question in plain English into any of them and get a real answer back. That is not the limitation.
The limitation is what they can see at all. Looker's semantic layer sits on SQL dialects. Metabase ships nineteen official drivers, and every one of them is a database. Point any of these tools at your Stripe data and they will tell you what every customer was invoiced, down to the line item, in whatever visual you like. They cannot tell you what was promised, because the promise is not in a table and never was. It is in a mailbox, in a Slack DM, in a ticket comment, in the fourth reply of a thread about SSO. A modelled source cannot model a sentence that nobody ever put into a column.
An internal-tools builder has the mirror problem. Retool, Appsmith and the rest are good at putting an interface in front of records: a query, a resource, an endpoint that returns rows. You can point one at your Stripe data in an afternoon. What you cannot hand it is the judgment, which is reading a four-reply thread about SSO and deciding that one sentence in the middle of it was a commitment about money. That is not a query result. That is a reading.
Automation platforms are the wrong shape entirely. Zapier and Make are trigger-based: they fire when something happens next. You could set up a trigger today that flags future discount language in email, and that would be a sensible thing to have. It does nothing about the deals already sitting on your books with promises in them that nobody has reconciled. This question is retrospective. It is about what already happened.
What the question actually requires joining
Say the question out loud in full: which active subscriptions were promised a price concession in a written conversation that does not appear in the billing record?
To answer that, you need three things in the same place.
| Source | What it contributes | Format |
|---|---|---|
| Gmail, Outlook, Slack, Zendesk | The language of the promise: "I can do 20% off", "we'll waive the setup fee", "first quarter at half rate" | Prose in threads |
| Stripe | The subscription, its price, its discounts, its invoice history | Structured records |
| CRM and e-signature | The account owner, close date, what the signed paperwork actually says | Structured records |
The hard part is the first row. There is no discount field to query. There is no consistent phrasing to grep for. Reps say "I can do 20", "let's call it 20 off", "I'll knock the annual down to 40k", "we'll honour the pilot rate". A keyword search for "discount" misses most of it. A human reading the threads would catch every one of them instantly, because a human reads for meaning rather than for string matches.
Reading for meaning across a mailbox is the part that has changed. That is what an AI layer sitting on top of your connected tools does: it reads the thread the way a person would, and it tells you which subscription that thread belongs to.
How you would answer it
Skopx connects to nearly 1,000 tools, and the ones that matter here are ordinary: Gmail or Outlook, Slack, Stripe, your CRM, your helpdesk. With those connected, you would open chat and type something close to this:
"Go through the last twelve months of email and Slack with our customers and find every message where someone on our side agreed to a price concession: a percentage off, a waived fee, a custom rate, a free period. For each one, pull the Stripe subscription for that customer and tell me whether the concession is reflected in what we've actually been charging. Show me the ones where it isn't, with the quoted sentence and the link to the original message."
It works through the threads, matches customer domains to Stripe customers, compares the promised terms against the actual price and applied discounts on each subscription, and comes back with citations. Not a summary that says "we found some discrepancies." A list where each row has the sentence a person wrote, a link to the message it came from, the subscription it maps to, and the difference in money.
When the answer is something you want to keep looking at rather than read once, you turn it into an internal app. You say: "Make this a page I can open: unapplied concessions, showing customer, the promised terms, who promised it, the current Stripe price, the difference per billing period, and a button to email the account owner asking them to confirm before we credit."
It runs that against what is connected, measures how many rows come back, and renders the console to fit. That is what the internal apps feature does: you describe the screen in a sentence, and it builds the screen out of the sources you already have.
What the console shows
For a mid-size B2B company with a couple of hundred subscriptions, the result might look like this.
A short row of metrics along the top. Open concessions found. Total annual value of the gap, which for the sake of illustration comes out around 90,000 dollars across eleven accounts, one or two of them running in the other direction because a discount was applied that nobody ever actually promised. Oldest unreconciled promise, in months.
Underneath, the table. One row per customer. The quoted sentence sits in the row, in full, because the sentence is the evidence and paraphrasing it destroys its value. Next to it, the person who wrote it and the date. Then the current Stripe price, the promised price, the per-period difference, and the cumulative difference since the promise was made. A link on each row opens the original message in Gmail or Slack, so the finance person can read the surrounding thread before deciding anything.
A bar chart, because a handful of accounts sorted by cumulative exposure tells you where to start faster than a sorted table does.
And on each row, a button. The button sends one message: an email to the current account owner with the quote and the numbers, asking them to confirm the concession is legitimate before finance issues a credit. Clicking it shows a confirmation first, with the exact recipient and the exact text, and nothing leaves until a person approves it. One person, one click, one action, in one connected tool. That is the whole write path. Nothing runs on its own, nothing fires overnight, nothing happens because a condition was met.
The honest limits
The console reads and it acts. It does not store anything. There is no discount register being maintained here, no new field on the customer record, nowhere for anyone to log future concessions. It reads Stripe and your mailboxes each time you open it and shows you what is there. If you want a permanent record of approved concessions, that belongs in your CRM or your billing system, and you should put it there.
It only sees what is connected and what is written down. A discount agreed on a Zoom call and never mentioned in writing is invisible to this and to everything else. If the promise never became text, no tool recovers it.
It will produce false positives. "I could probably do 20% off if you took the annual" is a hypothetical, not a commitment, and the difference between the two is genuinely ambiguous in English. Expect to dismiss rows. The value is not that every row is correct. It is that the handful of rows worth reviewing surfaced out of tens of thousands of messages instead of staying buried.
Deleted mailboxes are a real problem. If your departing rep's account was purged rather than delegated, those threads are gone. That is a decent argument for delegating rather than deleting, separate from anything about software.
The SQL side is read-only by design. If you also connect the database behind your product, to check entitlements against billing, it can query that database. It cannot change it.
The point
The dispute in the opening is not a billing failure. Billing charged exactly what it was told to charge. It is a handoff failure, and the handoff happened in prose, in a thread about SSO, between two people who both understood each other perfectly at the time.
Every company has a layer of commitments like this that exists only as sentences: pricing agreed in email, scope agreed in Slack, renewal terms agreed in a ticket comment. None of it is in a table, so none of your reporting sees it, and you find out it exists when a customer emails your CFO in March.
The way you find it is by reading. The only question is whether a person spends three days doing that, or whether something reads all of it at once and hands you the few that matter.
Skopx Team
The Skopx engineering and product team