Use Classification Only

This page shows how to get Email Classifications (Primary, Transactions, Updates, and Promotions) from SpamFoo Client when another product already decides what is spam, and how a license without spam filtering changes the API's behavior.

Choose an Approach

Approach Use It When
POST /tabs/classify-stream Your other product has already decided a message is not spam, and you want only its Email Classification. This endpoint never runs spam checks, whatever your license includes.
POST /classify-stream You want one integration that works with any license. With a license that has no spam filtering, SpamFoo Client skips the spam checks and returns only the Email Classification, in the same response format as always.

Both endpoints take the raw message as the body, the same way as described in Start With the Local API.

Classify Without Spam Checks

POST/tabs/classify-stream

Send only messages your other product passed as not spam. The endpoint assumes the message is not spam and records it that way.

Header Value
X-User-ID The recipient mailbox, as a full email address. Turns on personalization for that user.
X-Sender-IP IP address of the server that delivered the message. Shown in the admin console.
X-Sender-Email Envelope sender (SMTP MAIL FROM). Shown in the admin console.
X-SpamFoo-Log-ID Your own ID for this message, written to SpamFoo Client's classification log.
X-SpamFoo-Proxy-Auth The proxy secret. Required in standalone mode, and for any request that does not come from loopback.
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 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<TabsResult>();
Console.WriteLine($"{result!.Classification} ({result.Confidence:P0})");

record TabsResult(
    string Classification,
    float Confidence,
    Dictionary<string, float> ClassificationProbabilities,
    string? PersonalizationSource,
    long ElapsedMs);
import os
import requests

with open("message.eml", "rb") as eml:
    response = requests.post(
        "http://localhost:16253/tabs/classify-stream",
        data=eml,
        headers={
            "Content-Type": "message/rfc822",
            "X-SpamFoo-Proxy-Auth": os.environ.get("SPAMFOO_PROXY_SECRET", ""),
            "X-User-ID": "[email protected]",
        },
        timeout=60,
    )
response.raise_for_status()
result = response.json()
print(result["classification"], result["confidence"])

The response:

{
    "classification": "promotions",
    "confidence": 0.91,
    "classificationProbabilities": {
        "primary": 0.04,
        "transactions": 0.02,
        "updates": 0.03,
        "promotions": 0.91
    },
    "personalizationSource": null,
    "elapsedMs": 41
}
Field Meaning
classification primary, transactions, updates, or promotions. File the message by this value.
confidence The highest probability among the four classifications, from 0 to 1. When the model is unsure, or Primary is close behind, SpamFoo Client returns primary, so this value can belong to a different classification than the one returned.
classificationProbabilities The model's probability for each classification.
personalizationSource tabs_knn when the user's past corrections decided the classification, or null when the global model decided.
elapsedMs Time SpamFoo Client spent classifying the message, in milliseconds.

Things to know about this endpoint:

  • Allow and block rules from the admin console are not checked, because no spam decision is made.
  • Messages appear in the admin console as not spam, but they are left out of the spam and not spam counts.
  • It uses a separate queue from /classify-stream. When it cannot classify the message in time, it returns 503; leave the message in the inbox.

See Classify Without Spam Checks for the full reference.

Know How Licenses Affect Requests

A SpamFoo license includes one or both of these features, shown in the admin console as Spam Filtering and Classification. SpamFoo Client learns which features you have when it checks your license with SpamFoo, and it adjusts each request to match.

License Includes /classify-stream /tabs/classify-stream
Spam Filtering and Classification Spam verdict and Email Classification. Email Classification.
Classification only Email Classification only. isSpam is false and spamProbability is 0, except when an admin console allow or block rule decides the message. Email Classification.
Spam Filtering only Spam verdict only. classification is empty for mail that is not spam. 403 with "error": "feature_not_licensed".
  • Spam-only requests need Spam Filtering. Sending X-SpamFoo-Classify: false to /classify-stream with a Classification only license returns 403.
  • Before the license check: until SpamFoo Client has current license information, for example just after it starts and before it reaches SpamFoo, it does not block local work. /classify-stream runs both spam checks and classification, and /tabs/classify-stream is available.
  • After the license expires: in embedded mode, SpamFoo Client keeps running both spam checks and classification. In standalone mode, once the license has expired and its grace period has ended, or SpamFoo Client hasn't reached SpamFoo for too long, both endpoints return 403 with "error": "feature_not_licensed" until a valid license is active. Treat it as "no verdict" and deliver the message normally.
  • Same response format: with a Classification only license, /classify-stream still returns every field it normally does, so code written for a full license keeps working. Check classification and ignore the spam fields.

To see which features your license includes, open the license details in the admin console.

Send Classification Corrections

When a user moves a message to a different classification, send the correction to POST /tabs/feedback-stream. This works with any license and is how SpamFoo Client learns each user's preferences. See Report a Classification Fix.

Spam and not spam corrections from your other product do not need to go to SpamFoo Client unless SpamFoo Client also makes spam decisions for you.