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.
/tabs/classify-streamWhen 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}")