RAG Worked Because Your Data Sat Still
For three years, RAG was the default way developers connected AI to data: pull documents, chunk them, embed them, store…
You can catch churn risk 2 to 4 weeks before the cancellation request lands, across every account your CS team manages, from signals your health score will never see. Unresolved complaints that never got a follow-up. Tone drifts across consecutive replies from the same contact. A competitor’s name appearing in a side-thread. Response times that used to be hours and are now days. The signals are all there, inside email threads your CS platform cannot read.
This post shows how to pull them as structured JSON that your product can route into dashboards, Slack alerts, and renewal prep workflows.
Here’s the shape of what the detector returns:
{
"at_risk_customers": [
{
"customer": "Brightpath Analytics",
"risk_level": "critical",
"signals": [
{
"type": "unresolved_complaint",
"detail": "Billing dispute open 18 days, no resolution communicated",
"raised_by": "VP of Operations",
"severity": "high"
},
{
"type": "competitor_mention",
"detail": "Referenced evaluating a competitor in latest reply",
"raised_by": "Head of Engineering",
"severity": "high"
},
{
"type": "sentiment_shift",
"detail": "Tone shifted from collaborative to transactional over last 3 exchanges",
"evidence": "Replies shortened from multi-paragraph to single sentences, stopped using first names",
"severity": "medium"
}
],
"recommended_action": "Executive outreach to acknowledge billing issue and competitor concern. Reset the relationship before renewal."
},
{
"customer": "Cloudvault Systems",
"risk_level": "high",
"signals": [
{
"type": "engagement_drop",
"detail": "Response frequency dropped sharply after feature gap complaint in February",
"severity": "high"
},
{
"type": "unanswered_question",
"detail": "Asked about API rate limits, no response for 16 days",
"raised_by": "Senior Developer",
"severity": "medium"
}
],
"recommended_action": "Answer the rate limit question immediately and address the feature gap directly."
}
],
"summary": "2 accounts at risk. Brightpath is critical: billing dispute + competitor mention + tone shift is the pre-cancellation pattern."
}
Brightpath is the case that CS teams miss in health scores. Three separate signals converging on one account inside a month. Any one of them in isolation is a normal customer interaction. All three together is the pattern that shows up in post-mortem analyses of canceled accounts.
Illustrative — a hypothetical CS dashboard built on top of the iGPT API. iGPT ships as an API; this is one way a CS platform could surface the detector output to its team.
The full setup is four steps, each taking minutes.
pip install igptai
from igptai import IGPT
igpt = IGPT(api_key="IGPT_API_KEY", user="csm_user_id")
The user parameter scopes every API call to that CSM’s own inbox. User A’s customer conversations never appear in User B’s results.
sources = igpt.datasources.list()
if not sources:
auth_url = igpt.connectors.authorize()
print(f"Connect Gmail or Outlook: {auth_url}")
# After the user authorizes, re-run the script
iGPT indexes the full inbox, including PDF and HTML attachments. Threads get reconstructed, participants disambiguated, and messages ordered by timestamp before any query runs.
CHURN_SCHEMA = {
"at_risk_customers": [{
"customer": "string",
"risk_level": "string", # low | medium | high | critical
"signals": [{
"type": "string",
"detail": "string",
"raised_by": "string",
"severity": "string"
}],
"recommended_action": "string"
}],
"summary": "string"
}
The schema is enforced server-side, so the response always matches. Your downstream code does not need to parse text or handle field drift.
response = igpt.recall.ask(
input=(
"Which customers are at risk of churning? "
"Include unresolved complaints, sentiment shifts, "
"competitor mentions, and engagement pattern changes. "
"Highlight accounts where multiple signals converge."
),
quality="cef-1-high",
output_format={"schema": CHURN_SCHEMA},
)
for customer in response["at_risk_customers"]:
if customer["risk_level"] == "critical":
slack.post("#cs-escalations", format_alert(customer))
crm.update_account(customer["customer"], {
"churn_risk": customer["risk_level"],
"signals": customer["signals"],
})
That’s the full build. The reading of threads, matching of conversations across contacts, detection of tone shifts, and catching of convergence all happen inside the single recall.ask call.
A team trying to build this from Gmail or Outlook APIs runs into three problems. Threads come back as raw MIME with quoted replies nested inside replies, so a 10-message thread contains the original message quoted nine times and any semantic search is biased toward whichever content repeats most. Participant attribution is flattened, which means forwarded messages and CC drift make the apparent sender unreliable. And cross-thread reasoning is not a feature of email APIs at all, which is the hardest problem, because churn signals live across the billing dispute thread, the feature request thread, and the renewal conversation. Correlating them requires structured retrieval with consistent entity resolution.
Teams that start from raw APIs typically spend four to six months building the preprocessing before they write any churn detection logic. None of that work produces something the customer sees.
iGPT handles all of it at index time. Threads get reconstructed once. Quoted text gets collapsed into a single canonical version with attribution preserved. Attachments get parsed. Entities get resolved across sender addresses. The recall.ask call returns the correlated answer across every relevant thread on the account, with source citations so your CS team can verify anything that looks off.
The structured JSON has consistent field types, so it plugs directly into whatever CS stack you already run.
Renewal prep workflows. 60 days before a renewal, the detector runs against the account’s threads and the results land in the renewal dashboard. The CSM walks into the conversation knowing the unresolved complaints, the tone trajectory, and whether a competitor has come up, instead of checking a green health score and getting blindsided.
Real-time escalation. Critical-risk results route to CS leadership through Slack the moment they appear. Three converging signals on one account is the pre-cancellation pattern, and catching it 2 to 4 weeks before the cancellation request is the window where intervention still works.
Complaint resolution tracking. unresolved_complaint signals feed into your ticketing system with a severity score and an age. A billing dispute flagged for 18 days with no resolution gets an owner and an escalation, turning a churn signal into an operational action item.
Portfolio-level trending. Run the detector monthly across the full book of business and track which accounts improve and which deteriorate. The longitudinal view that product telemetry alone cannot produce.
Run the detection query on your own customer inbox at igpt.ai/hub/playground. For a working repo that uses the same recall.ask pattern with a different schema, see github.com/igptai/igpt-invoice-agent. For the technical explanation of why standard email pipelines break on thread data, see Why RAG Fails on Email Threads. For teams weighing the build decision, see Email Context for AI Agents: Build or Buy?.