Classifier · MachineMail Docs

What the numbers mean

The classifier fills trust.classification: five probabilities, a category and a language. It runs in two stages. Rules score every message, then a hosted decision model judges it, and the two are merged so that hard evidence from the rules can’t be talked away.

{"classification": {
  "is_spam": 0.02,
  "is_phishing": 0.02,
  "contains_instructions_to_agent": 0.98,
  "requests_credentials_or_payment": 0.02,
  "urgency": 0.047,
  "category": "other",
  "language": "en",
  "backend": "heuristic"
}}

Each probability answers one question about the message:

Field Question
contains_instructions_to_agent Does it contain prompt injection: text aimed at an AI assistant or agent reading the mailbox, trying to make it act, change its behaviour, reveal data or ignore its rules? Hidden text counts. Ordinary requests to the human recipient don’t, and neither does text that only discusses prompt injection.
is_phishing Does it pretend to be a trusted person, brand or service in order to steal credentials, money, codes or access?
requests_credentials_or_payment Does it ask for a password, one-time code, API key or other secret, a sign-in through a link, money, gift cards, an invoice payment, or a change of bank details? Delivering a code to you, or a receipt for a completed payment, does not count.
is_spam Is it unsolicited bulk or commercial mail? Subscribed newsletters, receipts and notifications are not spam.
urgency How much time pressure does it put on the reader?

Values run from 0.02 to 0.98: the classifier never claims certainty. Read them as confidence, not as calibrated frequencies. They matter most at the thresholds the trust layer acts on:

Threshold Effect
is_phishing or contains_instructions_to_agent at 0.8 or above level is quarantined
any of is_spam, is_phishing, contains_instructions_to_agent, requests_credentials_or_payment at 0.5 or above level is at least suspicious
is_spam at 0.9 or above SPAM label instead of INBOX

urgency never changes the level. It is there so an agent can notice pressure tactics, and it is reduced for one-time codes, which expire by design.

The probabilities also make up 35% of the score: the content component is 1 minus the largest of contains_instructions_to_agent, is_phishing, 0.8 × is_spam and 0.6 × requests_credentials_or_payment.

A one-time code, password reset or sign-in email reads to a model like a credential request (“use the code below”). When such mail passed DMARC aligned with its From domain, the rules stage reads no credential request in it (its own requests_credentials_or_payment is under 0.5 and no request appears outside security boilerplate), the code is not carried in a link and every link stays on the sender’s own hosts, requests_credentials_or_payment counts at 0.3 × in the content component; the reasons say Credential/payment request probability ... (reduced): DMARC passed and aligned. Mail that failed or did not align keeps the full weight, and a shortener, a mismatched link or a sign-in page on another site still makes verification mail suspicious.

Stage one: rules

The rules stage runs on every message, in-process, with no network calls. It is deterministic: the same message always gets the same scores. Each probability is a logistic function of weighted signals, and every signal that fires is written to trust.reasons as Classifier <target>: <description>, or Classifier <target> (reduced): ... when it lowered the score.

It looks at the agent-safe text and subject, the hidden text, text extracted from attachments, headers, the link and attachment reports, authentication results and the sender relationship.

Injection goes up for the agent-directed patterns described in Content scrubbing: instructions found in visible text, much more for the same in hidden text, instructions in attachments, words split with zero-width characters, Unicode tag characters carrying hidden text, and hidden text in a message that also addresses an AI. Quoted phrases count for less. Visible instructions from an authenticated sender you correspond with count for slightly less.

Credentials or payment goes up for requests to enter or send a password, PIN, card details or one-time code, to verify an account, and to pay, wire money or change bank details. Phrases preceded by a negation (“we will never ask for your password”) don’t count, and receipts for completed payments count against it.

Phishing goes up for credential requests, threats of account suspension, claims of unusual activity, “click to verify” wording, mailbox-quota and password-expiry lures, business-email-compromise phrasing, mismatched and suspicious links, a display name that claims a brand the sending domain doesn’t belong to, failed authentication, a Reply-To on a different domain (especially a free mail provider), a sender claiming to be your own domain without authentication, and HTML or other high-risk attachments. It goes down for an aligned DMARC pass, prior correspondence and allow-listed senders.

Spam goes up for bulk-mail headers from senders you haven’t written to, marketing and spam vocabulary, excessive capitals and exclamation marks, many links, tracking pixels and failed authentication. It goes down for authenticated transactional, notification and one-time-code mail, prior correspondence, and very strongly for allow-listed senders.

Urgency goes up for deadlines, expiry wording and urgent subject lines.

Stage two: a hosted model

A hosted decision model then reads a rendering of the message: subject, sender, authentication results, link and attachment findings, the body, the inbox preview line (the hidden preheader most HTML templates carry, labelled as such), other hidden text and text extracted from attachments, cut to a fixed length if the message is very long. It answers the same five questions, plus category and language.

The two stages are merged:

So the model can catch what the rules miss, such as a polite, novel injection, and can clear what the rules over-flag, such as a newsletter about prompt injection. It can’t override a hidden instruction.

The backend field

backend says which stage produced the final numbers:

Value Meaning
heuristic The rules alone.
a model name The hosted model judged the message, merged with the rules as above.
heuristic (fallback: <reason>) The model was called but failed or timed out, so the rules’ scores stand. Mail is never held up waiting for a model.

Treat a fallback report like any other: the rules are conservative about hard evidence, so hidden injections are still quarantined.

Category

category is one of:

Category Typical mail
otp A short message carrying a one-time code.
transactional Receipts, invoices, order, shipping, booking and refund confirmations.
notification Automated notices: GitHub, CI, alerts, “no-reply” senders, Auto-Submitted mail.
newsletter Bulk mail with List-Unsubscribe, Precedence: bulk or list headers.
conversation Person-to-person mail, replies, people you’ve written to.
bounce Delivery failure reports.
other Nothing matched strongly.

When the hosted model picks the category, the Classifier category: line in reasons names that category, and the rules’ choice when it differs (Classifier category: otp (<model>; the heuristic said: No category matched strongly)), so the reasons always agree with classification.category.

Category is descriptive. It does not change the level, except that authenticated otp, transactional and notification mail is less likely to be scored as spam.

Language

language is the ISO 639-1 code of the body’s main language when it can be told with confidence: en, es, fr, de, pt, it or nl. For other languages, or very short messages, it is left out.

What the classifier is not