Engineering
How Meridel groups a thousand messages into six themes
The three steps between a raw support ticket and a theme on your board, what each one costs, and where we still ask a person to decide.
Every week a Meridel workspace takes in a few hundred to a few thousand pieces of feedback: support tickets, notes from sales calls, survey answers and app reviews. By Monday morning they are a short list of themes. This post walks through how that happens, and where we deliberately stop and leave the call to you.
Step one: make every message look alike
Feedback arrives in very different shapes. A ticket has a subject, a thread and an account. A call note is three bullet points someone typed in a hurry. A survey answer is one sentence with a score next to it. Before we can compare them, each one becomes the same small record:
- the text a customer wrote or said, without signatures, greetings and quoted replies;
- the account it came from, so a theme can tell you who is asking;
- the source and the date, so you can filter by either;
- a link back to the original, which is never edited.
The cleaning step is boring on purpose. It is a set of rules per source, not a model, because rules are cheap, fast and easy to explain when they go wrong.
// One record per piece of feedback, whatever the source.
export interface FeedbackRecord {
id: string;
text: string; // signatures and quoted replies removed
accountId: string | null;
source: 'ticket' | 'call' | 'survey' | 'review';
receivedAt: string; // ISO date
originalUrl: string; // never edited
}
export function normalise(ticket: Ticket): FeedbackRecord {
return {
id: `ticket:${ticket.id}`,
text: stripQuotedReplies(stripSignature(ticket.firstMessage)),
accountId: ticket.organisationId ?? null,
source: 'ticket',
receivedAt: ticket.createdAt,
originalUrl: ticket.url,
};
}Step two: put similar requests next to each other
Two customers rarely describe a problem in the same words. “Exports time out”, “the CSV never finishes” and “download spins forever on our big project” are one request. Keyword tags miss that; meaning does not.
We turn each record into a vector that captures what it is about, then group records whose vectors sit close together. A group needs at least three records from at least two accounts before it is shown as a theme: one loud customer is a conversation, not a theme.
The thresholds below are the defaults. Each workspace can make them stricter, but not looser than the minimum.
| Setting | Default | Minimum | What it changes |
|---|---|---|---|
| Records per theme | 3 | 3 | How many messages a theme needs before it shows |
| Accounts per theme | 2 | 2 | How many different customers must ask |
| Similarity | 0.82 | 0.75 | How alike two messages must be to share a theme |
| Look-back window | 90 days | 30 days | How old a message can be and still count |
Step three: name the theme, then hand it over
A theme needs a name a person would write. We draft one from the three most typical messages in the group and show it with those messages underneath, so you can see why it was named that way.
A name that sounds right but hides what customers said is worse than no name at all. So the quotes come first and the name second.
This is where we stop. Merging two themes, splitting one that grew too wide, or deciding that a theme is not worth building: those calls stay with your team. You can do all three from the theme page, and Meridel learns from each change for your workspace only.
What it costs to run
For a workspace with 2,000 messages a month, the three steps take under a minute of compute in total. The grouping runs every night and again whenever you connect a new source, so a fresh workspace shows its first themes within a few minutes.
- Cleaning: a few milliseconds per message.
- Grouping: about twenty seconds for the whole workspace.
- Naming: a few seconds per new or changed theme.
If you want the details behind any of this, write to us. We are happy to explain how a specific theme came together.