Intake Desk Starter Pack: JSON Schema + Confidence Rubric + Slack Alert Template
Copy‑paste mini‑bundle for solo operators: a strict JSON Schema for email extraction, a confidence‑to‑route rubric with deterministic overrides and queue rules, and two Slack alert payloads (interactive and webhook) with run metrics placeholders. Plug it into Make/Zapier/n8n and ship a safe intake desk today.
Use this pack to stand up a safe AI intake desk in under an hour. Paste the JSON Schema into your model’s structured-output feature, wire the Confidence→Route rubric into your router (Make Router, Zapier Paths/Filters, or n8n Switch/If), and drop the Slack payload into your webhook or app for run-time visibility.
Operational defaults to start fast:
- Set [CONF_THRESHOLD] = 0.78 (tune later on your own labeled sample).
- Cap model tokens to keep latency ≤ 2–3 s typical: [MAX_INPUT_TOKENS] = 1500, [MAX_OUTPUT_TOKENS] = 256.
- Log per run: provider, route, confidence, latency_ms, tokens_in/out, and any Retry-After/backoff values.
Then iterate: raise/lower threshold based on misroutes caught in the human-review queue and your tolerance for risk.
1) Structured Output JSON Schema (copy‑paste)
Copy this into your LLM’s structured-output/JSON Schema setting. It validates fields your router depends on and forces safe fallbacks when outputs are malformed.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://headcountzero.me/schema/intake-desk-email-extract.v1.json",
"title": "IntakeDeskEmailExtract v1.0",
"type": "object",
"additionalProperties": false,
"required": [
"message_id",
"provider",
"mailbox",
"subject",
"from",
"to",
"date_received",
"thread_id",
"body_excerpt",
"classification",
"confidence",
"routing",
"metrics"
],
"properties": {
"message_id": { "type": "string", "minLength": 1, "maxLength": 256 },
"provider": { "type": "string", "enum": ["gmail", "microsoft_graph", "imap", "other"] },
"mailbox": { "type": "string", "format": "email" },
"subject": { "type": "string", "maxLength": 300 },
"from": {
"type": "object",
"required": ["name", "email"],
"additionalProperties": false,
"properties": {
"name": { "type": "string", "maxLength": 120 },
"email": { "type": "string", "format": "email" }
}
},
"to": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["email"],
"additionalProperties": false,
"properties": {
"name": { "type": "string", "maxLength": 120 },
"email": { "type": "string", "format": "email" }
}
}
},
"cc": {
"type": "array",
"items": {
"type": "object",
"required": ["email"],
"additionalProperties": false,
"properties": {
"name": { "type": "string", "maxLength": 120 },
"email": { "type": "string", "format": "email" }
}
}
},
"date_received": { "type": "string", "format": "date-time" },
"thread_id": { "type": "string", "minLength": 1, "maxLength": 256 },
"body_excerpt": { "type": "string", "maxLength": 2000, "description": "Plaintext excerpt, sanitized." },
"attachments": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["filename", "size_bytes"],
"properties": {
"filename": { "type": "string", "maxLength": 200 },
"mime_type": { "type": "string", "pattern": "^[^/]+/[^/]+$" },
"size_bytes": { "type": "integer", "minimum": 0 },
"suspected_sensitive": { "type": "boolean", "default": false }
}
}
},
"entities": {
"type": "object",
"additionalProperties": false,
"properties": {
"company_name": { "type": "string", "maxLength": 200 },
"person_name": { "type": "string", "maxLength": 200 },
"phone": { "type": "string", "pattern": "^\\+?[0-9][0-9 .()\-]{7,}$" },
"email": { "type": "string", "format": "email" },
"urls": { "type": "array", "items": { "type": "string", "format": "uri" }, "maxItems": 10 },
"order_id": { "type": "string", "maxLength": 64 },
"invoice_id": { "type": "string", "maxLength": 64 },
"amount": { "type": "number", "minimum": 0 },
"currency": { "type": "string", "pattern": "^[A-Z]{3}$" },
"meeting_time": { "type": "string", "format": "date-time" }
}
},
"classification": {
"type": "object",
"additionalProperties": false,
"required": ["intent", "tags"],
"properties": {
"intent": {
"type": "string",
"enum": [
"sales_lead",
"support_request",
"billing",
"cancellation",
"legal",
"job_application",
"meeting_scheduling",
"newsletter",
"spam",
"internal",
"other"
]
},
"tags": { "type": "array", "items": { "type": "string", "maxLength": 32 }, "maxItems": 10 }
}
},
"risk_flags": {
"type": "array",
"items": {
"type": "string",
"enum": [
"billing",
"cancellation",
"refund",
"legal",
"breach",
"subpoena",
"chargeback",
"invoice_dispute",
"security",
"credential_leak",
"payment_method",
"pii",
"vip_sender"
]
},
"maxItems": 12
},
"confidence": { "type": "number", "minimum": 0, "maximum": 1, "description": "Model-reported score in [0,1]. Calibrate offline before trusting." },
"routing": {
"type": "object",
"additionalProperties": false,
"required": ["proposed_action", "route"],
"properties": {
"proposed_action": { "type": "string", "enum": ["auto_route", "human_review"] },
"route": {
"type": "string",
"enum": [
"crm_lead",
"support_ticket",
"billing_queue",
"cancel_queue",
"legal_queue",
"calendar_hold",
"archive",
"spam",
"unknown"
]
},
"overrides_triggered": { "type": "array", "items": { "type": "string", "maxLength": 64 }, "maxItems": 8 },
"reason": { "type": "string", "maxLength": 300 }
}
},
"metrics": {
"type": "object",
"additionalProperties": false,
"required": ["tokens_in", "tokens_out", "latency_ms"],
"properties": {
"tokens_in": { "type": "integer", "minimum": 0 },
"tokens_out": { "type": "integer", "minimum": 0 },
"latency_ms": { "type": "integer", "minimum": 0 },
"provider_backoff_ms": { "type": "integer", "minimum": 0, "default": 0 },
"retry_after_s": { "type": "integer", "minimum": 0, "default": 0 },
"model": { "type": "string", "maxLength": 120 }
}
},
"links": {
"type": "object",
"additionalProperties": false,
"properties": {
"thread_url": { "type": "string", "format": "uri" },
"message_url": { "type": "string", "format": "uri" }
}
},
"version": {
"type": "object",
"additionalProperties": false,
"properties": {
"schema": { "type": "string", "const": "1.0.0" },
"run_id": { "type": "string", "maxLength": 64 }
}
}
},
"examples": [
{
"message_id": "178a7f3c9f1",
"provider": "gmail",
"mailbox": "inbox@acme.co",
"subject": "Pricing for automation help",
"from": { "name": "Sam Lee", "email": "sam@widget.com" },
"to": [{ "email": "inbox@acme.co" }],
"date_received": "2026-04-18T15:04:05Z",
"thread_id": "thread-92ab",
"body_excerpt": "We’re evaluating help to automate onboarding…",
"attachments": [],
"entities": { "company_name": "Widget Co" },
"classification": { "intent": "sales_lead", "tags": ["onboarding", "automation"] },
"risk_flags": [],
"confidence": 0.82,
"routing": { "proposed_action": "auto_route", "route": "crm_lead", "overrides_triggered": [] },
"metrics": { "tokens_in": 864, "tokens_out": 142, "latency_ms": 1450, "provider_backoff_ms": 0, "retry_after_s": 0, "model": "gpt-4o" },
"links": { "thread_url": "https://mail.google.com/..." },
"version": { "schema": "1.0.0", "run_id": "run_2026-04-18_150405Z" }
}
]
}
Tip: In Make/Zapier/n8n, validate against this schema before branching. If validation fails, set routing.proposed_action = human_review and push the item to your [HUMAN_QUEUE_DB].
2) Confidence‑to‑Route Rubric (defaults you can tune)
Drop these rules into your router. Start with the defaults, then tune [CONF_THRESHOLD] after a week of logs.
# Confidence→Route Rubric v1.0
params:
CONF_THRESHOLD: 0.78 # start here; tune on your labeled sample
MAX_ALLOWED_LATENCY_MS: 3000 # target P50 ≤ 3000ms per message
HUMAN_QUEUE_DB: "[NOTION_OR_AIRTABLE_TABLE]"
CRM_SYSTEM: "[CRM_SYSTEM]" # e.g., HubSpot, Pipedrive
TICKETING_TOOL: "[TICKETING_TOOL]" # e.g., Zendesk, Linear, GitHub Issues
# 1) Deterministic overrides (always send to human)
overrides_hard:
- when: subject_or_body_matches
any_of_regex:
- "(?i)\b(cancellation|cancel my|terminate)\b"
- "(?i)\b(refund|chargeback|invoice dispute)\b"
- "(?i)\b(legal|lawsuit|subpoena|legal hold)\b"
- "(?i)\b(breach|security incident|compromised)\b"
action: human_review
reason: "high-risk keywords"
- when: risk_flags_contains
any_of: [billing, cancellation, refund, legal, breach, subpoena, chargeback, invoice_dispute, security, credential_leak, payment_method, pii]
action: human_review
reason: "risk_flags present"
# 2) Schema safety
schema_guard:
- if: json_schema_valid == false
then: { action: human_review, reason: "invalid_schema" }
# 3) Confidence gate
confidence_gate:
- if: confidence >= CONF_THRESHOLD
then: { allow_auto: true }
- else: { action: human_review, reason: "below_threshold" }
# 4) Intent→Route map (used only when allow_auto == true AND no overrides fired)
intent_route_map:
sales_lead: { route: crm_lead, dest: "[CRM_SYSTEM]", action: "create_or_update_lead" }
support_request:{ route: support_ticket, dest: "[TICKETING_TOOL]", action: "create_ticket" }
meeting_scheduling: { route: calendar_hold, dest: "[CALENDAR_TOOL]", action: "hold_slot_or_reply_with_link" }
newsletter: { route: archive, dest: "mail", action: "label_and_archive" }
spam: { route: spam, dest: "mail", action: "mark_spam" }
billing: { route: billing_queue, dest: HUMAN_QUEUE_DB, action: "triage_manual" }
cancellation: { route: cancel_queue, dest: HUMAN_QUEUE_DB, action: "triage_manual" }
legal: { route: legal_queue, dest: HUMAN_QUEUE_DB, action: "triage_manual" }
job_application:{ route: unknown, dest: HUMAN_QUEUE_DB, action: "triage_manual" }
internal: { route: archive, dest: "mail", action: "label_internal" }
other: { route: unknown, dest: HUMAN_QUEUE_DB, action: "triage_manual" }
# 5) Provider throttle safety (apply before any send/reply)
provider_throttle:
gmail:
send_backoff: "truncated_exponential_with_jitter"
max_backoff_seconds: 64
note: "messages.send costs ~100 units; respect per-user/project caps; queue sends if quota tight."
microsoft_graph:
on_429: "wait Retry-After seconds, then retry; escalate backoff if repeats"
note: "Exchange caps ~30 messages/min/mailbox; pace queues accordingly."
# 6) Logging (do not skip)
run_log_fields: [provider, mailbox, classification.intent, confidence, routing.route, routing.proposed_action, metrics.tokens_in, metrics.tokens_out, metrics.latency_ms, metrics.retry_after_s, metrics.provider_backoff_ms, version.run_id]
Calibration playbook:
- Label 200 historical emails with true intents; run them through your model without acting.
- Plot confidence vs. accuracy; set [CONF_THRESHOLD] where accuracy ≥ your target (e.g., 95%) and volume auto-routed is acceptable.
- Re-check monthly; keep a 20‑message canary set for drift spotting.
3) Slack Alert — Block Kit with Approve/Override (app-based)
Use this if you have a Slack app with chat:write and interactivity enabled (buttons let you approve/override in-channel). Replace bracketed variables.
{
"channel": "[SLACK_CHANNEL_ID]",
"text": "[ROUTE]: [SUBJECT]",
"blocks": [
{ "type": "header", "text": { "type": "plain_text", "text": "[ROUTE_LABEL] • [INTENT] ([CONFIDENCE_PCT]%)" } },
{ "type": "section", "fields": [
{ "type": "mrkdwn", "text": "*From*\n[FROM_NAME] <[FROM_EMAIL]>" },
{ "type": "mrkdwn", "text": "*Provider*\n[PROVIDER] • [MAILBOX]" },
{ "type": "mrkdwn", "text": "*Latency*\n[LATENCY_MS] ms" },
{ "type": "mrkdwn", "text": "*Tokens*\n[in: [TOKENS_IN]] [out: [TOKENS_OUT]]" }
]},
{ "type": "section", "text": { "type": "mrkdwn", "text": "*Subject*\n[SUBJECT]\n\n*Excerpt*\n[BODY_EXCERPT]" } },
{ "type": "context", "elements": [
{ "type": "mrkdwn", "text": "*Risk flags*: [RISK_FLAGS]" },
{ "type": "mrkdwn", "text": "*Overrides*: [OVERRIDES_TRIGGERS]" },
{ "type": "mrkdwn", "text": "*Backoff*: [PROVIDER_BACKOFF_MS] ms • *Retry-After*: [RETRY_AFTER_S] s" }
] },
{ "type": "actions", "elements": [
{ "type": "button", "text": { "type": "plain_text", "text": "Open Thread" }, "url": "[THREAD_URL]", "style": "primary" },
{ "type": "button", "text": { "type": "plain_text", "text": "Send to Human" }, "value": "[RUN_ID]|[MESSAGE_ID]|human_review", "action_id": "route_human" },
{ "type": "button", "text": { "type": "plain_text", "text": "Approve Auto-Route" }, "style": "primary", "value": "[RUN_ID]|[MESSAGE_ID]|auto_route", "action_id": "route_auto" }
]}
]
}
Notes:
- [CONFIDENCE_PCT] = round([CONFIDENCE] * 100).
- If you don’t store [THREAD_URL], link [MESSAGE_URL] or your [HUMAN_QUEUE_DB] record.
- For Outlook/Graph, include [RETRY_AFTER_S] from the 429 response when present.
4) Slack Alert — Simple Webhook (no interactivity)
If you’re using a simple incoming webhook (no buttons), use this minimal payload. Still includes the key run metrics.
{
"text": "[ROUTE_LABEL] • [INTENT] ([CONFIDENCE_PCT]%) — [SUBJECT]",
"blocks": [
{ "type": "section", "text": { "type": "mrkdwn", "text": "*From:* [FROM_NAME] <[FROM_EMAIL]> | *Provider:* [PROVIDER] • [MAILBOX]\n*Latency:* [LATENCY_MS] ms | *Tokens:* in [TOKENS_IN] / out [TOKENS_OUT]\n*Risk:* [RISK_FLAGS]\n*Backoff/Retry-After:* [PROVIDER_BACKOFF_MS] ms / [RETRY_AFTER_S] s" } },
{ "type": "section", "text": { "type": "mrkdwn", "text": "*Excerpt:* [BODY_EXCERPT]" } },
{ "type": "context", "elements": [
{ "type": "mrkdwn", "text": "<[THREAD_URL]|Open Thread> • <[HUMAN_QUEUE_URL]|Human Queue> • run_id: [RUN_ID]" }
] }
]
}
Tip: Color-code messages via channel-level filters (e.g., route ‘human_review’ to a dedicated #intake-needs-human channel).