Skip to content

Triage a Suspicious Email Before Anyone Clicks

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

tsimport { 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

tsexport 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.

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.

Run the phishing sample (sign-in required) 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.