Email Intent Classifier
Support teams, CRMs, and helpdesk automations receive thousands of emails a day, and routing each one by hand does not scale. This prompt turns any incoming email into a structured classification: a single intent, a confidence score, a one-sentence summary, a suggested reply tone, and a flag for human follow-up. It is built for triage pipelines that auto-assign tickets, draft first replies, or escalate angry customers before they churn. Watch out for the traps: polite complaints often hide behind 'just a quick question', unsubscribe requests are sometimes buried in a friendly paragraph, and marketing mail can look like a genuine request. The confidence score exists for exactly that ambiguity — route high-confidence mail automatically and send anything below your threshold to a human queue instead of guessing.
System Prompt
You are an email intent classifier. Read the email below and return a single JSON object that matches the provided schema exactly. Classify the email's primary intent as one of: question, complaint, request, feedback, spam, unsubscribe. Choose the single best fit; if the email is clearly unsolicited bulk mail or a phishing attempt, use spam. Set confidence to a number between 0 and 1 reflecting how certain you are about the intent. Write summary as one concise sentence capturing what the sender wants. Choose suggested_reply_tone from friendly, formal, apologetic: use apologetic when the sender is upset or something went wrong, formal for official or legal matters, friendly otherwise. Set needs_follow_up to true when a human must act or reply beyond a simple acknowledgement, otherwise false. Rules: - Output only the JSON object, with no extra text, no markdown fences, and no commentary. - Extract the best value present in the email. Do not invent details that are not in the email. - If a required field cannot be determined from the email, do not produce any output. - The summary must be a single sentence and must not be empty.
Raw JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "EmailIntent",
"type": "object",
"additionalProperties": false,
"required": ["intent", "confidence", "summary", "suggested_reply_tone", "needs_follow_up"],
"properties": {
"intent": {
"type": "string",
"enum": ["question", "complaint", "request", "feedback", "spam", "unsubscribe"]
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"summary": {
"type": "string",
"minLength": 1
},
"suggested_reply_tone": {
"type": "string",
"enum": ["friendly", "formal", "apologetic"]
},
"needs_follow_up": {
"type": "boolean"
}
}
}
Zod (TypeScript)
import { z } from "zod";
export const EmailIntentSchema = z.strictObject({
intent: z.enum(["question", "complaint", "request", "feedback", "spam", "unsubscribe"]),
confidence: z.number().min(0).max(1),
summary: z.string().min(1),
suggested_reply_tone: z.enum(["friendly", "formal", "apologetic"]),
needs_follow_up: z.boolean(),
});
export type EmailIntent = z.infer<typeof EmailIntentSchema>;
Pydantic v2 (Python)
from pydantic import BaseModel, ConfigDict, Field, field_validator
from typing import Literal
class EmailIntent(BaseModel):
model_config = ConfigDict(extra="forbid")
intent: Literal["question", "complaint", "request", "feedback", "spam", "unsubscribe"]
confidence: float = Field(..., ge=0, le=1)
summary: str = Field(..., min_length=1)
suggested_reply_tone: Literal["friendly", "formal", "apologetic"]
needs_follow_up: bool
@field_validator("summary", mode="before")
@classmethod
def _strip_summary(cls, v):
if not isinstance(v, str):
return v
return v.strip()