Report a Classification Fix

Tell SpamFoo Client that a user moved a message to a different Email Classification, for example from Promotions to Primary. SpamFoo Client learns from the fix for that user and may report it to SpamFoo to improve the global model.

POST/tabs/feedback-stream

When to Use It

  • A user drags a message to another classification tab or folder.
  • A user picks a new classification for a message from a menu.
  • To report spam or not spam, use Report Spam or Not Spam instead.

What SpamFoo Client does with a fix:

  • Learns for the user: it stores an embedding of the message (a list of numbers that represents its content) with the new classification in the user's classification memory. Later messages from the same sender, or very similar messages, can get that classification. See How Personalization Works.
  • Keeps a local copy: it saves a compressed copy of the message on the SpamFoo Client server, so the fix can be rebuilt when the embedding model changes.
  • Reports model mistakes: when the new classification differs from the original one, SpamFoo Client queues an anonymized correction for SpamFoo. The user ID is hashed first. It skips the report when the original and new classifications match, or when the user's own past fixes decided the original classification. See How Corrections Are Used.

The message must have a From address, because SpamFoo Client links the fix to the sender. This endpoint has no license check.

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 Yes string The proxy secret, required even from loopback. See Authenticate Requests.
X-SpamFoo-Priority No bulk Send bulk for corrections that aren't from a user acting right now, such as a batch import. They run on the background queue with their own limit, so they can't slow down live mail. Without it, the correction is handled right away.
X-User-ID Yes string The user who made the fix. Use the same value you send as X-User-ID when classifying.
X-Classification Yes string The classification the user chose: primary, transactions, updates, or promotions. Case-insensitive.
X-Original-Classification No string The classification SpamFoo returned when the message arrived, one of the same four values. If you omit it, SpamFoo Client uses the model's top classification at fix time. With any other value, the fix is still learned for the user but not reported to SpamFoo.
X-Send-Email-Text No true or false Send true only when the user agreed to share message content. The report to SpamFoo then includes the message text, with email addresses, phone numbers, card numbers, and similar details replaced by placeholders, plus phishing details built from the message's text, headers, and links. Default false.
X-Subject-Strip-Prefixes No string, repeatable A literal tag your mail server adds to subjects, such as [SPAM]. SpamFoo Client removes it before it analyzes or reports the subject. Send the header once per tag, or send one JSON array of strings.
X-SpamFoo-Event-Id No string, up to 128 characters Your own ID for this fix, so a retry isn't applied twice. See Send a Fix More Than Once.

Response

A successful request returns 200:

{
    "status": "success",
    "userId": "[email protected]",
    "classification": "primary"
}
Field Type Description
status string Always success on success. It does not tell you whether a report was queued for SpamFoo.
userId string The X-User-ID you sent.
classification string The X-Classification value, exactly as you sent it.

Send a Fix More Than Once

If your integration can send the same fix twice, such as a retry after a lost answer, add X-SpamFoo-Event-Id with an ID that stays the same across retries.

  • The ID is 1 to 128 printable ASCII characters with no spaces. An ID that doesn't fit is ignored, and the fix is applied normally.
  • A repeat of an ID that was already applied for the same user on this endpoint returns 200 with {"status": "duplicate", "duplicate": true, "userId": "[email protected]"} and changes nothing. That answer has no classification field.
  • An ID counts as used only after its fix succeeds, so you can retry a failed attempt with the same ID.
  • SpamFoo Client remembers IDs in memory for 24 hours. A restart forgets them.
  • Without the header, every fix is applied, because a user can fix the same message more than once.

Errors

Status Body Cause
400 {"error": "X-User-ID header is required"} X-User-ID was missing or blank.
400 {"error": "X-Classification header is required"} X-Classification was missing or blank.
400 {"error": "Invalid classification. Valid: primary, transactions, updates, promotions"} X-Classification is not one of the four classifications.
400 {"error": "No email content provided"} The body was empty.
400 {"error": "Email could not be parsed"} The body is not a readable message.
400 {"error": "Could not extract sender email from message"} The message has no usable From address.
401 Empty The proxy secret was missing or wrong.
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", ...} Only with X-SpamFoo-Priority: bulk. SpamFoo Client is busy. Wait the seconds in Retry-After and send it again.

Example

This example reports that a user moved a message from Promotions to Primary.

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

var secret = Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET")
    ?? throw new InvalidOperationException("Set SPAMFOO_PROXY_SECRET.");

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/feedback-stream") { Content = content };
request.Headers.Add("X-SpamFoo-Proxy-Auth", secret);
request.Headers.Add("X-User-ID", "[email protected]");
request.Headers.Add("X-Classification", "primary");
request.Headers.Add("X-Original-Classification", "promotions");

using var response = await http.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");
import os

import requests

headers = {
    "Content-Type": "message/rfc822",
    "X-SpamFoo-Proxy-Auth": os.environ["SPAMFOO_PROXY_SECRET"],
    "X-User-ID": "[email protected]",
    "X-Classification": "primary",
    "X-Original-Classification": "promotions",
}

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

print(response.status_code, response.text)