Template - Support
PredicateSpam detection
Whether an inbound message is spam rather than a real customer contact.
A predicate decision for shared inboxes and contact forms, tuned to protect real customer messages from being discarded.
The decision
Is this inbound message spam?
Evidence checks (asked in the same request)
- Promotional content: The message is unsolicited promotional content.
- References an account: The sender refers to a specific account, order, or ticket.
Expected output: A probability from 0 to 1 that the message is spam, plus one probability per evidence check.
Input
The Decisions API reads one input. Combine these fields into it as labeled text so the model sees the facts the decision depends on.
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | The customer's message, verbatim. Include the subject line if there is one. |
sender_domain | string | No | The sender's email domain. Known customer domains are strong evidence against spam. |
Worked examples
Unsolicited SEO pitch
Dear Sir, we offer guaranteed ranking on Google with our SEO services package. 500 backlinks for $99!!! Click here: http://seo-boost.example http://rank-fast.example
A careful reviewer would say: Yes. Unsolicited promotion with no reference to the product or an account.
Run this exampleA real customer who can't log in
I can't log in to my account since this morning. The reset link never arrives. Ticket #5512 was closed without a fix.
A careful reviewer would say: No. A specific account problem that references an existing ticket.
Run this example
Expected answers are human judgments written for this template, not model output.
How it works
Spam filtering for a support inbox has a different cost profile from email spam filtering. Deleting one real customer message is worse than letting ten sales pitches through.
The "references an account" check is a counter-signal. If it fires, the message is probably real however promotional it reads.
Human review and thresholds
Recommended review policy
Never delete automatically. Move confident spam to a folder that someone skims weekly, and leave everything else in the queue. Review the folder for real customers until you trust the threshold.
Choosing thresholds
Set the bar for "spam" high. The cost of a false positive is a customer who believes you ignored them. The cost of a false negative is a few seconds of an agent's time.
- Starting automation threshold
- 95%
- Starting don’t-decide threshold
- 70%
Starting points for the playground, not recommendations. The right values depend on your costs and on how the model’s confidence tracks accuracy on your data. Calibrate them.
Limitations
- Vendor outreach, partnership requests, and recruiting emails sit between spam and legitimate contact. Decide where they belong and say so in the question.
- Phishing aimed at your team needs security tooling, not a triage classifier.
- Short messages ("hello?", "call me") have too little evidence to classify reliably.
Use it in your code
The template definition is plain JSON: the question, the evidence checks, the input fields, and the starting policy. The SDK call below sends the same question and checks to the Decisions API from your server.
{
"$format": "thethinkloop.template/v1",
"slug": "spam-detection",
"title": "Spam detection",
"decides": "Whether an inbound message is spam rather than a real customer contact.",
"question": {
"name": "decision",
"type": "predicate",
"instructions": "Is this inbound message spam?"
},
"evidenceChecks": [
{
"name": "factor_1",
"label": "Promotional content",
"type": "predicate",
"instructions": "The message is unsolicited promotional content."
},
{
"name": "factor_2",
"label": "References an account",
"type": "predicate",
"instructions": "The sender refers to a specific account, order, or ticket."
}
],
"inputs": [
{
"name": "message",
"type": "string",
"required": true,
"description": "The customer's message, verbatim. Include the subject line if there is one."
},
{
"name": "sender_domain",
"type": "string",
"required": false,
"description": "The sender's email domain. Known customer domains are strong evidence against spam."
}
],
"expectedOutput": "A probability from 0 to 1 that the message is spam, plus one probability per evidence check.",
"examples": [
{
"title": "Unsolicited SEO pitch",
"input": "Dear Sir, we offer guaranteed ranking on Google with our SEO services package. 500 backlinks for $99!!! Click here: http://seo-boost.example http://rank-fast.example",
"expected": "Yes"
},
{
"title": "A real customer who can't log in",
"input": "I can't log in to my account since this morning. The reset link never arrives. Ticket #5512 was closed without a fix.",
"expected": "No"
}
],
"startingPolicy": {
"automateAt": 0.95,
"abstainBelow": 0.7,
"note": "A starting point for exploration, not a recommendation. Calibrate against your own labeled examples."
}
}import OpenAI from "openai"; // openai >= 7.30.0
const client = new OpenAI(); // reads OPENAI_API_KEY on the server
const decision = await client.decisions.create({
model: "gpt-6-luna",
input: "Dear Sir, we offer guaranteed ranking on Google with our SEO services package. 500 backlinks for $99!!! Click here: http://seo-boost.example http://rank-fast.example",
questions: [
{
type: "predicate",
name: "decision",
instructions: "Is this inbound message spam?"
},
{
type: "predicate",
name: "factor_1",
instructions: "The message is unsolicited promotional content."
},
{
type: "predicate",
name: "factor_2",
instructions: "The sender refers to a specific account, order, or ticket."
}
],
});
// Answers come back in question order. A question can also be refused.
const [main, ...checks] = decision.answers;
if (main.type === "refusal") throw new Error("The model declined to answer.");
if (main.type === "predicate") {
console.log(main.probability); // 0–1 estimate that the condition is true
}
Download it as spam-detection.json, or browse all developer resources.
Need this decision tuned to your policy?
Related
- Predicate template
Lead qualification
Whether an inbound lead is worth a salesperson's time.
- Predicate template
Automate or review
Whether a request is safe to resolve automatically or needs a person.
- Choice template
Feature request classification
Whether a message is a bug report, a feature request, a how-to question, or something else.
- Guide
Confidence thresholds
Probability, confidence, accuracy, calibration, and correctness are different things. How to set a review policy from the cost of each kind of mistake.
- Guide
Calibrating AI decisions
The procedure for checking whether confidence means what it says: how much data you need, what to compute, and how to read the result without fooling yourself.
- Guide
Evaluating AI decisions
Accuracy alone hides the mistakes that matter. How to build a test set, which numbers to report, how to read failures, and when an evaluation is good enough to act on.