Classify Without Spam Checks

Send a raw message to get only its Email Classification: primary, transactions, updates, or promotions. No spam, phishing, or allow and block checks run.

POST/tabs/classify-stream

When to Use It

  • Another system already filters spam, and you only want SpamFoo to sort the mail that is left.
  • The message is already known not to be spam, and you want to file it.
  • For a spam verdict, or a spam verdict and Email Classification together, use Classify a Message instead.

Requests run on SpamFoo Client's background queue, separate from the queue that /classify-stream uses. The license must include Email Classification. When SpamFoo Client has not yet confirmed the license, the request still runs. When its saved license information has expired, the request still runs in embedded mode, and returns 403 in standalone mode.

SpamFoo Client records each result for the admin console. The same message for the same X-User-ID is recorded only once in a five-minute window, but it is still classified every time.

Request

The body is the raw message (raw .eml content), sent unchanged. See Send the Message in the Request Body.

Header Required Type Description
Content-Type Recommended string message/rfc822
X-SpamFoo-Proxy-Auth In standalone mode, or when not loopback string The proxy secret. See Authenticate Requests.
X-User-ID No string The recipient mailbox, usually a full email address. When sent, the user's own classification corrections can set the result.
X-Sender-IP No IPv4 or IPv6 IP address of the server that delivered the message. Only recorded with the result for the admin console. If omitted, SpamFoo Client reads it from the message headers.
X-Sender-Email No email address Envelope sender. Only recorded with the result for the admin console. If omitted, SpamFoo Client reads it from the message headers.
X-SpamFoo-Log-ID No string, up to 64 characters Your own ID for the message, written to the classification log. Letters, digits, and . _ : - only; other values are ignored.
X-SpamFoo-Priority No bulk Marks work that isn't live delivery, such as sorting a mailbox's old mail. See Mark Bulk Work.
X-SpamFoo-Deadline-Ms No integer, 1 to 600000 How many milliseconds you'll wait for the result. When the background queue can't meet it, the request gets 503 with "code": "deadline_exceeded" instead of waiting. See Set a Deadline.

This endpoint ignores X-SpamFoo-Classify and X-Recipient-Email.

Response

A successful request returns 200 with a JSON object:

{
    "classification": "promotions",
    "confidence": 0.91,
    "classificationProbabilities": {
        "primary": 0.04,
        "transactions": 0.02,
        "updates": 0.03,
        "promotions": 0.91
    },
    "personalizationSource": null,
    "elapsedMs": 41
}
Field Type Description
classification string primary, transactions, updates, or promotions. SpamFoo Client picks primary when the model is not confident, or when primary scored close to the top classification.
confidence number The highest probability the model gave any classification, from 0 to 1. When SpamFoo Client falls back to primary, this can be another classification's probability. 1 when the user's corrections decided.
classificationProbabilities object Probability for each of the four classifications, keyed by name.
personalizationSource string or null tabs_knn when the user's past classification corrections set the result. null when the global model decided.
elapsedMs integer Time SpamFoo Client spent classifying, in milliseconds. Time spent waiting in the queue is not included.

Errors

Status Body Cause
400 {"error": "No email content provided"} The body was empty.
400 {"error": "X-SpamFoo-Deadline-Ms must be ..."} X-SpamFoo-Deadline-Ms isn't a whole number from 1 to 600000, or was sent more than once.
401 {"error": "unauthorized"} The proxy secret was missing or wrong, and the request needed it.
403 {"error": "feature_not_licensed", "features": ["classification"]} The license does not include Email Classification, or, in standalone mode, the license has expired.
413 {"error": "request_body_too_large", "maxBytes": ...} The message is over the body size limit.
429 {"error": "...", "code": "auth_blocked"} Too many wrong secrets came from this address. See Repeated Wrong Secrets.
503 {"error": "server_busy", "code": "server_busy", ...} or {"error": "Classification queue timeout", "code": "queue_timeout", ...} SpamFoo Client is busy. See Handle Busy Responses.
503 {"error": "Classification deadline exceeded", "code": "deadline_exceeded", ...} Only with X-SpamFoo-Deadline-Ms. The deadline couldn't be met. See Set a Deadline.

Example

curl -X POST http://localhost:16253/tabs/classify-stream \
    -H "Content-Type: message/rfc822" \
    -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \
    -H "X-User-ID: [email protected]" \
    --data-binary @message.eml
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;

using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253") };

await using var eml = File.OpenRead("message.eml");
var content = new StreamContent(eml);
content.Headers.ContentType = new MediaTypeHeaderValue("message/rfc822");

using var request = new HttpRequestMessage(HttpMethod.Post, "/tabs/classify-stream") { Content = content };
request.Headers.Add("X-SpamFoo-Proxy-Auth", Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET"));
request.Headers.Add("X-User-ID", "[email protected]");

using var response = await http.SendAsync(request);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadFromJsonAsync<JsonElement>();
var classification = result.GetProperty("classification").GetString();
var confidence = result.GetProperty("confidence").GetDouble();
Console.WriteLine($"classification={classification} confidence={confidence:F2}");
import os

import requests

headers = {
    "Content-Type": "message/rfc822",
    "X-SpamFoo-Proxy-Auth": os.environ.get("SPAMFOO_PROXY_SECRET", ""),
    "X-User-ID": "[email protected]",
}

with open("message.eml", "rb") as eml:
    response = requests.post(
        "http://localhost:16253/tabs/classify-stream",
        data=eml,
        headers=headers,
        timeout=60,
    )

response.raise_for_status()
result = response.json()
print(f"classification={result['classification']} confidence={result['confidence']:.2f}")