Recipe catalog / contact-opt-out
Recognize a contact opt-out
What scope of future contact does the sender ask to stop?
You need to distinguish stopping all contact from stopping a channel or campaign.
Explore this recipe interactively ยท Source and implementation guide
Use contact-opt-out in TypeScript
Install with npm install jev-recipes. Requires Node.js 22.9 or newer and ES modules. Set TYPESAFE_API_KEY in your server environment for live calls, which send input to TypeSafe and use API quota. See the installation guide.
import { contactOptOut } from 'jev-recipes/contact-opt-out';
const result = await contactOptOut({
"message": "Please stop texting me. Email about my open support case is fine.",
"minConfidence": 0.8
});
console.log(result);
Input contract
| Field | Type | Needed |
|---|---|---|
| message | string | Required |
| context | string | Optional |
| minConfidence | number | Optional |
Full input and result schemas
{
"input": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"message": {
"type": "string"
},
"context": {
"type": "string"
},
"minConfidence": {
"type": "number",
"minimum": 0,
"maximum": 1
}
},
"required": [
"message"
]
},
"result": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"model": {
"type": "string"
},
"usage": {
"type": "object",
"properties": {
"input_tokens": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"output_tokens": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": [
"input_tokens",
"output_tokens"
],
"additionalProperties": false
},
"status": {
"type": "string",
"enum": [
"ready",
"review"
]
},
"verdict": {
"type": "string",
"enum": [
"all_contact",
"channel",
"campaign",
"none",
"unclear"
]
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"probabilities": {
"type": "object",
"propertyNames": {
"type": "string",
"enum": [
"all_contact",
"channel",
"campaign",
"none",
"unclear"
]
},
"additionalProperties": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"required": [
"all_contact",
"channel",
"campaign",
"none",
"unclear"
]
}
},
"required": [
"model",
"usage",
"status",
"verdict",
"confidence",
"probabilities"
],
"additionalProperties": false
}
}Saved example result
This hand-authored response demonstrates the contract. It is not a model accuracy measurement. Run it without an API key: npx jev-recipes demo contact-opt-out.
{
"model": "demo-fixture",
"usage": {
"input_tokens": 0,
"output_tokens": 0
},
"status": "ready",
"verdict": "channel",
"confidence": 0.95,
"probabilities": {
"all_contact": 0.01,
"channel": 0.95,
"campaign": 0.01,
"none": 0.01,
"unclear": 0.02
}
}
Evaluation evidence
jev-1.13.0 / 2026-09-27 / 40 held-out cases
Scoring revision 1.
33 ready decisions, with 100% accuracy among those decisions.
95% case-level interval: 87% to 100%. Related synthetic cases are correlated.
Measured on these synthetic cases
This measurement uses an earlier or unverified recipe or evaluator version. Rerun with the current recipe and evaluator before treating these numbers as current.
Use the evaluation guide to measure this decision on your own labeled cases.
Limitations
- Reports the expressed scope category, not legal consent or permission to contact. A none result never grants consent.
- Does not resolve channel or campaign identifiers or change subscription settings. Applications own those mappings and prior preferences.
- Mixed channel and campaign restrictions, conflicting requests, and missing references require review.
Related recipes
- cancellation-check: Use cancellation-check for cancelling a product or service, rather than future contact.
- buying-intent: Use buying-intent for purchase interest. Declining an offer alone does not establish an opt-out.
- followup-timing: Use followup-timing for a requested delay or condition for future contact.