# Recipe: triage documents and email

> Pick the document type and the department, check for a deadline and rate the importance of a Dutch email in one call, then file it with the right department.

A payment reminder arrives in Dutch. One request says what kind of document it is, which department gets it, whether it sets a deadline and how important it is. The email is read once for all four questions, and your code files it with the right department.

## The request

- **State.** The email as JSON: sender, subject, text and the name of the attachment. Dex reads text only, so it sees the file name, not the PDF. To use what the PDF says, extract its text first and pass it as a field.
- **A Dutch email, English questions.** The instructions, options and `criteria` are in English while the email is in Dutch. Mixing languages is fine, and `criteria` in English are followed more reliably than Dutch ones: see [Known limits](/docs/concepts/known-limits/#criteria-in-dutch-are-followed-less-reliably).
- **Two `pick`s**, `doc_type` and `department`, each with `other` as the way out. They are separate questions because the answers do not map one to one: a contract can go to legal or to finance.
- **`deadline` is a `check`.** Dex says whether a date is set; it does not return the date. Pulling values out of text is a job for your code.
- **`importance` is a `rate`** in beta, with a house rule in `criteria`: a reminder that threatens interest or collection costs is at least `Normal`.
- **Four questions, one state.** All four point at `{{email.text}}`, and the state is billed once: 157 state tokens, however many questions read it.

```json
{
  "state": {
    "email": {
      "from": "administratie@groothandel-vandijk.nl",
      "subject": "Herinnering factuur 2026-0917",
      "text": "Geachte heer, mevrouw, volgens onze administratie staat factuur 2026-0917 van 1.284,50 euro nog open. Wij verzoeken u het bedrag uiterlijk 15 oktober over te maken. Bij uitblijven van betaling brengen wij wettelijke rente in rekening. Met vriendelijke groet, Van Dijk Bakkerijgroothandel"
    },
    "attachments": ["factuur-2026-0917.pdf"]
  },
  "questions": {
    "doc_type": {
      "type": "pick",
      "instructions": "What kind of document is {{email.text}}?",
      "options": {
        "invoice": "An invoice or a payment reminder",
        "contract": "A contract, offer or terms",
        "complaint": "A complaint about our service or products",
        "other": null
      },
      "min_confidence": 0.5
    },
    "department": {
      "type": "pick",
      "instructions": "Which department should receive {{email.text}}?",
      "options": {
        "finance": "Accounts payable and receivable",
        "legal": "Contracts, disputes and legal letters",
        "customer_service": "Questions and complaints from customers",
        "other": null
      },
      "min_confidence": 0.5
    },
    "deadline": {
      "type": "check",
      "instructions": "Does {{email.text}} set a deadline or a due date?",
      "min_confidence": 0.6
    },
    "importance": {
      "type": "rate",
      "instructions": "How important is it to act on {{email.text}}?",
      "criteria": "A payment reminder that threatens interest or collection costs is at least Normal.",
      "levels": ["Low", "Normal", "High"],
      "min_confidence": 0.3
    }
  }
}
```

## Run it

Put a test key in `DEX_API_KEY` (see the [Quickstart](/docs/quickstart/#get-a-key-in-60-seconds)). Save the Python code as `document_triage.py` and run `python document_triage.py`. Save the TypeScript code as `document-triage.mts` and run `npx tsx document-triage.mts`: the code uses `await` at the top level, and the `.mts` ending makes the file an ES module. Install the SDKs from [Downloads](/docs/reference/sdks/#downloads).

```bash tab="curl"
curl https://api.thinqit.ai/v1/decide \
  -H "authorization: Bearer $DEX_API_KEY" \
  -H "content-type: application/json" \
  --data-binary @- <<'DEX_REQUEST'
{
  "state": {
    "email": {
      "from": "administratie@groothandel-vandijk.nl",
      "subject": "Herinnering factuur 2026-0917",
      "text": "Geachte heer, mevrouw, volgens onze administratie staat factuur 2026-0917 van 1.284,50 euro nog open. Wij verzoeken u het bedrag uiterlijk 15 oktober over te maken. Bij uitblijven van betaling brengen wij wettelijke rente in rekening. Met vriendelijke groet, Van Dijk Bakkerijgroothandel"
    },
    "attachments": ["factuur-2026-0917.pdf"]
  },
  "questions": {
    "doc_type": {
      "type": "pick",
      "instructions": "What kind of document is {{email.text}}?",
      "options": {
        "invoice": "An invoice or a payment reminder",
        "contract": "A contract, offer or terms",
        "complaint": "A complaint about our service or products",
        "other": null
      },
      "min_confidence": 0.5
    },
    "department": {
      "type": "pick",
      "instructions": "Which department should receive {{email.text}}?",
      "options": {
        "finance": "Accounts payable and receivable",
        "legal": "Contracts, disputes and legal letters",
        "customer_service": "Questions and complaints from customers",
        "other": null
      },
      "min_confidence": 0.5
    },
    "deadline": {
      "type": "check",
      "instructions": "Does {{email.text}} set a deadline or a due date?",
      "min_confidence": 0.6
    },
    "importance": {
      "type": "rate",
      "instructions": "How important is it to act on {{email.text}}?",
      "criteria": "A payment reminder that threatens interest or collection costs is at least Normal.",
      "levels": ["Low", "Normal", "High"],
      "min_confidence": 0.3
    }
  }
}
DEX_REQUEST
```

```python tab="Python"
# Save as dex_request.py, then run: python dex_request.py
import json

from thinqit_dex import Client

client = Client()  # reads DEX_API_KEY from the environment

request = json.loads(r'''
{
  "state": {
    "email": {
      "from": "administratie@groothandel-vandijk.nl",
      "subject": "Herinnering factuur 2026-0917",
      "text": "Geachte heer, mevrouw, volgens onze administratie staat factuur 2026-0917 van 1.284,50 euro nog open. Wij verzoeken u het bedrag uiterlijk 15 oktober over te maken. Bij uitblijven van betaling brengen wij wettelijke rente in rekening. Met vriendelijke groet, Van Dijk Bakkerijgroothandel"
    },
    "attachments": ["factuur-2026-0917.pdf"]
  },
  "questions": {
    "doc_type": {
      "type": "pick",
      "instructions": "What kind of document is {{email.text}}?",
      "options": {
        "invoice": "An invoice or a payment reminder",
        "contract": "A contract, offer or terms",
        "complaint": "A complaint about our service or products",
        "other": null
      },
      "min_confidence": 0.5
    },
    "department": {
      "type": "pick",
      "instructions": "Which department should receive {{email.text}}?",
      "options": {
        "finance": "Accounts payable and receivable",
        "legal": "Contracts, disputes and legal letters",
        "customer_service": "Questions and complaints from customers",
        "other": null
      },
      "min_confidence": 0.5
    },
    "deadline": {
      "type": "check",
      "instructions": "Does {{email.text}} set a deadline or a due date?",
      "min_confidence": 0.6
    },
    "importance": {
      "type": "rate",
      "instructions": "How important is it to act on {{email.text}}?",
      "criteria": "A payment reminder that threatens interest or collection costs is at least Normal.",
      "levels": ["Low", "Normal", "High"],
      "min_confidence": 0.3
    }
  }
}
''')

decision = client.decide(
    request["state"],
    request["questions"],
)
for question_id, answer in decision.answers.items():
    print(question_id, answer)
```

```ts tab="TypeScript"
// Save as dex-request.mts, then run: npx tsx dex-request.mts (Node.js 18 or newer)
import { Client, parseRequest } from "@thinqit/dex";

const client = new Client(); // reads DEX_API_KEY from the environment

// parseRequest keeps the key order of the text (JSON.parse would move labels such as "1" to the front).
const request = parseRequest(`{
  "state": {
    "email": {
      "from": "administratie@groothandel-vandijk.nl",
      "subject": "Herinnering factuur 2026-0917",
      "text": "Geachte heer, mevrouw, volgens onze administratie staat factuur 2026-0917 van 1.284,50 euro nog open. Wij verzoeken u het bedrag uiterlijk 15 oktober over te maken. Bij uitblijven van betaling brengen wij wettelijke rente in rekening. Met vriendelijke groet, Van Dijk Bakkerijgroothandel"
    },
    "attachments": ["factuur-2026-0917.pdf"]
  },
  "questions": {
    "doc_type": {
      "type": "pick",
      "instructions": "What kind of document is {{email.text}}?",
      "options": {
        "invoice": "An invoice or a payment reminder",
        "contract": "A contract, offer or terms",
        "complaint": "A complaint about our service or products",
        "other": null
      },
      "min_confidence": 0.5
    },
    "department": {
      "type": "pick",
      "instructions": "Which department should receive {{email.text}}?",
      "options": {
        "finance": "Accounts payable and receivable",
        "legal": "Contracts, disputes and legal letters",
        "customer_service": "Questions and complaints from customers",
        "other": null
      },
      "min_confidence": 0.5
    },
    "deadline": {
      "type": "check",
      "instructions": "Does {{email.text}} set a deadline or a due date?",
      "min_confidence": 0.6
    },
    "importance": {
      "type": "rate",
      "instructions": "How important is it to act on {{email.text}}?",
      "criteria": "A payment reminder that threatens interest or collection costs is at least Normal.",
      "levels": ["Low", "Normal", "High"],
      "min_confidence": 0.3
    }
  }
}`);

const decision = await client.decide(request);
console.log(decision.answers);
```

## Expected output

```json
{
  "id": "req_01M3JE1QF2XKE5CZKRNCKFST2C",
  "object": "decision",
  "created": 1790546337,
  "model": "dex-1.0.1",
  "served_by": "gpu",
  "calibration": "cal-20260926-1",
  "answers": {
    "doc_type": {
      "type": "pick",
      "choice": "invoice",
      "probabilities": {
        "invoice": 0.9435,
        "contract": 0.023,
        "complaint": 0.0199,
        "other": 0.0136
      },
      "confidence": 0.9205,
      "abstained": false
    },
    "department": {
      "type": "pick",
      "choice": "finance",
      "probabilities": {
        "finance": 0.917,
        "legal": 0.0328,
        "customer_service": 0.0302,
        "other": 0.02
      },
      "confidence": 0.8842,
      "abstained": false
    },
    "deadline": {
      "type": "check",
      "probability": 0.9509,
      "confidence": 0.9017,
      "abstained": false
    },
    "importance": {
      "type": "rate",
      "rating": 1.0585,
      "levels": ["Low", "Normal", "High"],
      "probabilities": [0.0202, 0.9011, 0.0787],
      "confidence": 0.6909,
      "abstained": false
    }
  },
  "usage": {
    "input_tokens": 290,
    "state_tokens": 157,
    "question_tokens": 133,
    "allowance_tokens": 0,
    "paid_tokens": 0,
    "charge_micro_cents": 0,
    "unit_price_micro_cents": 0,
    "tier": "test"
  }
}
```

Captured from the live API on 2026-09-27 with a test key: model `dex-1.0.1`, calibration `cal-20260926-1`, `served_by: gpu`, 290 input tokens (157 for the state, 133 for the questions). A test key is charged nothing, so `tier` is `test` and the charge is 0. On this exact version the same request always returns these answers.

| Question | Type | Answer | Confidence | `min_confidence` | Abstained |
| --- | --- | --- | --- | --- | --- |
| `doc_type` | pick | `invoice` (0.9435) | 0.9205 | 0.5 | no |
| `department` | pick | `finance` (0.917) | 0.8842 | 0.5 | no |
| `deadline` | check | yes with probability 0.9509 | 0.9017 | 0.6 | no |
| `importance` | rate | rating 1.0585, most likely `Normal` (0.9011) | 0.6909 | 0.3 | no |

The email is an `invoice` (0.9435) for `finance` (0.917), and it sets a deadline (0.9509). The importance rating of 1.0585 sits on `Normal` (0.9011), as the house rule asks for a reminder that threatens interest. No answer abstained.

## Act on it

`email_id` and the functions `route_to_person`, `file_email`, `add_label` and `set_priority` stand for your own code. Here the email is filed with finance and labelled `invoice` and `has_deadline`. It keeps its normal priority, because 1.0585 is below the bar of 1.5.

```python tab="Python"
department = decision.pick("department")
if department.abstained:
    route_to_person(email_id)  # two departments are close: a person files it
else:
    file_email(email_id, department.choice)

doc_type = decision.pick("doc_type")
if not doc_type.abstained:
    add_label(email_id, doc_type.choice)

deadline = decision.check("deadline")
if not deadline.abstained and deadline.probability >= 0.5:
    add_label(email_id, "has_deadline")  # Dex says a date is there; your code or a person reads it

importance = decision.rate("importance")
if not importance.abstained and importance.rating >= 1.5:
    set_priority(email_id, "high")
```

```ts tab="TypeScript"
const { doc_type: docType, department, deadline, importance } = decision.answers;

if (department?.type === "pick") {
  if (department.abstained) routeToPerson(emailId); // two departments are close: a person files it
  else fileEmail(emailId, department.choice);
}
if (docType?.type === "pick" && !docType.abstained) addLabel(emailId, docType.choice);
if (deadline?.type === "check" && !deadline.abstained && deadline.probability >= 0.5) {
  addLabel(emailId, "has_deadline"); // Dex says a date is there; your code or a person reads it
}
if (importance?.type === "rate" && !importance.abstained && importance.rating >= 1.5) setPriority(emailId, "high");
```

As in the [support recipe](/docs/cookbook/support-routing/#act-on-it), the TypeScript answers have the general `Answer` type, so the code narrows each one on `type`.

## Adapt it

- **Your own departments.** Up to 255 options per pick; describe each department by what it receives. For a deep structure, pick the department first, then send a second request that picks a queue within it.
- **Dates and amounts in code.** To know whether a due date is close, read the date in your code and pass `days_until_due` as a field. Dex reads; it does not calculate. See [Known limits](/docs/concepts/known-limits/#date-and-amount-arithmetic-is-unreliable).
- **Keep `criteria` in English**, even for a mailbox that only gets Dutch email, and test each rule on a few of your own messages.
- **Email is written by outsiders.** Text in the state can try to steer the answer. For a high-impact action, add a guardrail check as in the [agent tool gate recipe](/docs/cookbook/agent-guardrails/).
