---
title: "Triage a Suspicious Email Before Anyone Clicks"
description: "Ask one yes/no question about a message's risk and one choice question about handling it, then route on the probability. Low-cost enough to run on every inbound message, not just reported ones."
canonical: "https://system-one.dev/en/use-cases/phishing-triage"
language: "en"
---

# Triage a Suspicious Email Before Anyone Clicks

**Key points**

1. Phishing triage is two bounded judgements: is this message credential-harvesting, and what should happen to it.
2. A probability per message lets you set different bands for quarantine, user warning and human review instead of a single yes/no.
3. It is a triage signal, not a verdict: report anything it misses, and keep the final call with your security team.

## Why probability bands beat a label

A filter that answers "phishing" or "not phishing" offers two options: deliver or drop. In practice the interesting cases sit between them — a supplier invoice whose domain is one character off, or a password-reset mail with a shortened link.

A probability lets you split the outcomes:

- **High**: quarantine and notify the recipient.
- **Middle**: deliver with a banner, or hold for review.
- **Low**: deliver normally, and count it as negative evidence for the next threshold pass.

## Question shape

```ts
import { booleanQuestion, choice } from '@system-one-ai/core';

export const mailQuestions = {
  credentialRisk: booleanQuestion(
    'Does this message try to obtain credentials or payment through a link or attachment?',
  ),
  handling: choice('What should happen to this message?', {
    deliver: 'Deliver as normal',
    warn: 'Deliver with a warning to the recipient',
    quarantine: 'Quarantine and notify the recipient',
    review: 'Send to the security team',
  }),
};
```

Pass the sender, subject, body text and link hosts as state. Strip scripting and remote images first: the model reads text, and you do not want a remote fetch triggered by triage.

## Routing on the numbers

```ts
export async function triageMail(client, message: { from: string; subject: string; body: string; links: string[] }) {
  const result = await client.evaluate({
    state: message,
    questions: mailQuestions,
  });
  const p = result.answers.credentialRisk.probability;
  // Placeholder bands; calibrate against your own labelled mailbox sample
  if (p >= 0.85) return 'quarantine';
  if (p >= 0.5) return 'review';
  if (p >= 0.25) return 'warn';
  return 'deliver';
}
```

> Illustrative code, not tested.

## What it will not catch

- **Attachments it cannot read.** Jev reads text and JSON; a scanned PDF or a macro-enabled document is invisible to it. Extract text first, or use a model that reads images.
- **Lookalike domains**, unless the sender domain is in the state you send. Include the domain and link hosts explicitly.
- **A compromised legitimate account.** Wording can be normal because a real person wrote it.

Send the fields that matter and keep the question narrow. For the general pattern, see [agent guardrails](https://system-one.dev/en/blog/agent-guardrails).

## Cost

Every 4KB is one block, and anything smaller still counts as one block. Cost = blocks × questions × the model rate. A short email is one block, so a two-question request on Jev costs 2 credits.

New accounts start with 100 credits, and credits never expire. See [pricing](https://system-one.dev/en/pricing).

[Run the phishing sample (sign-in required)](https://system-one.dev/en/demo/phishing-email) to see the same questions on sample data.


## Frequently asked questions

### Can this replace an email security gateway?

No. It is a message-level judgement that adds to existing filtering; gateways still handle attachments, URLs, sender reputation and policy.

### Does it read attachments?

It reads text and JSON only. Extract text from documents first, or use an image-capable model.

### How do I set the bands?

Label a sample of real messages, then choose bands that match the cost of a false negative versus a false positive in your organisation, and re-check them periodically.

### What should I send as state?

Sender address and domain, subject, plain-text body, link hosts, and whether the message arrived from outside the organisation. Strip scripts and remote images.
