Conversations, tickets and messages from the support tool
Valid ticket, tied to the customer, with the clock on the customer's first message
Volume, wait, resolution and reason by customer and month
Health score, SLA dashboard and AI agent
What it solves
- Every ticket tied to the paying customer, with their MRR next to it.
- Wait time measured from the moment the customer wrote, split between AI and human.
- Reason and module of every ticket ready for product to prioritize.
Business questions it answers
Ask your agent these questions once the case is live.
What you need
- Your support tool connected to Nekt (conversations, tickets and messages)
- The customer key stored on the ticket or the contact, tied to billing
- A closed list of reasons and modules, and the SLA agreed per customer type
What you get
- A ticket table with customer, channel, who handled it, times and reason
- A customer x month aggregate ready for the health score
- A dashboard of SLA, unowned queue and reopens by agent and team
- An AI agent that answers support questions with the right cut already applied
Where to activate
The customer x month aggregate feeds the support component of the score: volume change, bug tickets and reopens.
Answers how many tickets a customer opened, how long they waited and why, already without internal tickets and automations.
A heads-up when the unowned queue grows or when a top account is close to breaching the SLA.
The rules are straightforward. The work is tying each ticket to the customer and rebuilding times from the messages when the tool doesn't store what you need. The detailed view has the business rules, the pitfalls and the acceptance test. Or send everything straight to your agent with the Nekt MCP.
The support tool counts conversations the customer never opened, tickets without a company and bot messages as replies. Response time starts when the ticket is created, not when the customer wrote, and an empty time becomes zero in the average. The result is a per-customer volume that doesn't match the tool's report, a response time that looks too good and a health score that punishes the wrong customer.
How it is usually done, and the rule
Each row is a mistake that already happened in practice.
In Intercom, a conversation counts when it wasn't triggered by automation (source.delivered_as other than automated) and the customer replied (first_contact_reply filled in). Proactive messages, campaigns and reminders stay out. Reproduce the tool's native report rule first and only then add your own cuts, publishing the difference when there is one.
Internal requests (finance asking support, operations tasks) live alongside customer tickets in the same tool. Separate them by a ticket type field, not by team name: one team can have both. Remove internal and test companies. When the question doesn't say which cut, the agent asks before answering.
Tickets often arrive without a company. Resolve in order: the ticket's own company, the contact's company, email, normalized phone (area code plus the last 8 digits). Email domain only counts if it isn't a free provider. A company mapping is used only when it is one-to-one. Record how the customer was identified (direct, mapping or unidentified) and publish the link rate each month.
In chat, the customer writes before the ticket exists: in a real base, the first message was at 07:43 and the ticket at 07:59. Wait time is the company's first reply after the customer's first message, computed from the messages. If the company spoke first (outbound support), count from the customer's first message, otherwise the time comes out negative. Conversations with inferred timestamps, such as one crossing the new year without a full date, stay out of SLA.
With an AI agent replying first, first response drops to seconds and hides the wait for a person. Classify each ticket as AI only, AI with handoff, human only, no reply or no transcript, and measure the wait until the first human reply separately. Native agent-reply counters may ignore the AI's message: keep them for audit and compute from the messages. In Intercom, an assumed AI resolution (assumed_resolution in ai_agent.resolution_state) is not a resolution confirmed by the customer.
A ticket with no human reply has an empty time. Replacing empty with zero drags the average down. Summarize times by median and p90, only over eligible tickets (chat with a transcript and reliable timestamps), and show how many were left out. If the SLA is in business hours, use the business-hours time the tool provides (in Intercom, first response in office hours in the conversation statistics) and say which of the two you are showing.
Resolution time runs from creation to the first close. A reopened ticket (hs_ticket_reopened_at in HubSpot, count_reopens in Intercom) goes into its own reopen metric by agent, a sign of poor closing. In HubSpot, time_to_close, time_to_first_agent_reply and time per stage (hs_v2_cumulative_time_in_<stage>) come in milliseconds: convert once, in the first layer.
In a real base, almost half the tickets had no owner and the category was empty for most of them. Label a null owner as Unassigned and never discard it: the unowned queue is a signal of support health. Use queue, pipeline or tier (L1, L2, L3) as the main breakdown when the category isn't reliable, and map statuses in more than one language (Resolvido and Closed) to open, pending and closed.
A customer with 200 users opens more tickets than one with 5. For the health score, use the customer x month aggregate with change versus last month and a 3-month moving average, and normalize by active users or by MRR. Separate questions from bugs: questions point to onboarding and docs, bugs point to product. The share of questions uses only closed tickets classified as question or bug.
The target depends on the customer, for example 24 hours for top accounts and 72 hours for the rest, in business days. Store the target on the ticket and flag a breach when it was resolved after the target or is still open past it. Old tickets from before SLA automation have no target and stay out of the denominator, never counted as met.
Who handled the ticket
From the transcript messages, after the customer's first message.
Definitions
Pitfalls that already happened
The mapping picked the most frequent customer for each company id in the tool. One of those ids belonged to the internal agent who opened tickets on customers' behalf, and thousands of tickets landed on a single customer, who showed up as the heaviest support user.
Accept only one-to-one mappings; the rest stays unidentified.
Joining a company x domain table to the tickets table duplicated rows, because one company had several login emails. The counter showed about 13% more tickets than existed and fed the health score that way.
Resolve the customer per ticket and count by ticket id.
The load of years of tickets wrote in append mode, and reprocessing a file repeated identical tickets. Every counter came out inflated without any error.
Deduplicate by ticket id at the entrance of the metrics layer.
The calculation replaced empty response times with zero before averaging, and the team's first response looked much better than it was.
Remove empties from the average, use median and p90 and show the count.
With the AI agent up front, first response dropped to seconds. The wait for a person on handed-off tickets didn't change, and nobody saw it because there was only one time column.
Publish human wait next to first response.
Resolvido and Closed lived in the same field, and part of the closed tickets was counted as open.
Map every status to open, pending and closed in a table.
Acceptance test
The case is done when every item passes.
- The number of valid tickets per month matches the tool's native report (difference under 2%).
- No ticket has a negative wait time, and no empty time is counted as zero.
- The customer link rate is published each month, and a ticket without a customer shows as unidentified rather than disappearing.
- AI only + handed off + human + no reply + out of SLA = total valid tickets.
- Each ticket appears once (unique id), even after reprocessing the load.
Agent package
Paste into Claude, ChatGPT or any client with the Nekt MCP connected.
Support by customer - Valid ticket = opened by the customer, not triggered by automation or by the team, with a customer reply. In Intercom: source.delivered_as other than automated and first_contact_reply filled in. - Always separate customer (external) tickets from the team's internal tickets, by ticket type. If the question doesn't say which cut, ask. - Internal and test companies are excluded. A ticket with no identified customer counts in volume as unidentified. - Wait time starts at the customer's first message, not at ticket creation. Human wait is measured separately; empty when only the AI handled it. - Handling type: AI only, handed off, human, no reply, out of SLA. - Times are summarized by median and p90 over eligible tickets. Never replace an empty time with zero. - Resolution = creation to first close. Reopens are their own metric. - Null owner = Unassigned. Never discard. - HubSpot times come in milliseconds. - SLA: target per customer type stored on the ticket. Tickets without a target stay out of the SLA calculation. Per-client exceptions: - <client>: SLA of <hours> business hours by contract. - <client>: tickets opened through channel <channel> are excluded from experience time.
Build the "Support by customer, with wait time, resolution and reason" case in my Nekt workspace following the rules at https://academy.nekt.com/en/industrias/saas/suporte-por-cliente.md. 1) Find the source tables and confirm the columns the rules use. 2) Create a transformation at the stated grain applying every rule. 3) Run the acceptance test and show me the result of each item. 4) Create the case's context document in the workspace, leaving my clients' exceptions open for me to fill in.
Variations
The ticket is the group or the number: tie it to the customer by the normalized group name or the last 8 digits of the phone, and anonymize phone, email and tax ID before exporting the text.
The backlog becomes a monthly snapshot of open tickets, with no cut by opening date, with aging and escalation across L1, L2 and L3.
An affected-module field on the ticket ties each bug to the part of the product and to the customer, and product prioritizes by affected customers and MRR.