Get started/ Handling verdicts
Get started

Handling verdicts

Turn a label and a reason into a policy: what to drop, what to review, what to show the visitor, and how to fail safely.

A verdict is only useful once your code does something with it. This page is the policy most integrations end up with.

Three buckets, not two

Treat spam verdicts differently depending on how they were reached.

bucketconditionaction
Botspam with any of HONEYPOT_FILLED, FAST_SUBMIT, URL_IN_NAME, DUP_KNOWN_SPAM, IP_REPUTATION_BAD, EMAIL_REPUTATION_BAD, EMAIL_DOMAIN_REPUTATION_BADDrop. Show the normal “thanks” page and send nothing.
Reviewany other spam verdict (ML_SPAM, LLM_SPAM, MANY_URLS, ML_GRAY_FALLBACK)Store it in a review folder with the reasons. Tell the visitor how to reach you if they do not hear back.
DeliverhamDeliver as normal.
const BOT = new Set(["HONEYPOT_FILLED", "FAST_SUBMIT", "URL_IN_NAME", "DUP_KNOWN_SPAM",
  "IP_REPUTATION_BAD", "EMAIL_REPUTATION_BAD", "EMAIL_DOMAIN_REPUTATION_BAD"]);

function bucket(verdict) {
  if (!verdict) return "deliver";                     // API unreachable: fail open
  if (verdict.label === "ham") return "deliver";
  return verdict.reasons.some((r) => BOT.has(r)) ? "bot" : "review";
}

The review bucket is small (about one verdict in twenty is a gray-zone case) and is where false positives live if you ever have any. Giving it a home means nobody loses a customer to a filter they cannot see.

Keep the id

Store the verdict id, label and reasons next to the message. The id is what you send to /v1/feedback when a verdict was wrong, and to DELETE /v1/submissions/{id} when a person asks for their data to be removed.

Give people a button

The single most effective way to reduce support load is a “Not spam” button that works. Wire it to /v1/feedback with label: "ham". The message is delivered next time, the sender gets a good mark, and your memory learns the pattern. A “Mark as spam” button on the inbox side does the reverse. Both can be batched with /v1/feedback/batch.

Show the reason

Next to every filtered message, show the reason in plain words. People trust a filter they can see. The reason code reference has suggested wording for every code; a few examples:

codewording
HONEYPOT_FILLEDA hidden field was filled in.
DUP_KNOWN_SPAMThe same message was sent to other sites recently.
ML_SPAMLooks like an unsolicited pitch.
LLM_SPAMNot the kind of message this site is for.

Fail open

If spamex is slow, unreachable, or your quota is exhausted, deliver the message and log the failure. Losing a real enquiry costs more than one spam email. In code, that means every non-200 response and every timeout maps to “deliver”.

200            use the label
401 / 403      configuration problem: deliver, alert yourself
402            quota exhausted: deliver, alert yourself
413 / 422      your payload: deliver, fix the request
429            back off per Retry-After, deliver
5xx / timeout  deliver

Fail closed only for forms that trigger an expensive action on submit, such as creating an account or sending an SMS.

Timeouts

Set a five-second client timeout. Rules and model verdicts take a few milliseconds; a gray-zone verdict can take up to three seconds while the language model answers. If your form handler answers the visitor’s browser directly and you cannot afford the wait, use fast mode.

Do not retry a 200

Classification is not idempotent by design. The same message sent twice comes back DUP_KNOWN_SPAM the second time if the first verdict was confident spam, and the second call counts against your quota. Retry only on network errors and 5xx.

↑↓ navigate↵ openDocs and blog