Template

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:

  1. Label 200 historical emails with true intents; run them through your model without acting.
  2. Plot confidence vs. accuracy; set [CONF_THRESHOLD] where accuracy ≥ your target (e.g., 95%) and volume auto-routed is acceptable.
  3. 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).