Check Health

Check that SpamFoo Client is up, whether it's ready to classify mail, and how busy its classification queues are. Use these endpoints for service monitors, container health checks, load balancers, and tuning queue limits.

None of them need the proxy secret or check the license.

Check That It Is Running

GET/health/live

Returns 200 with {"status":"live"} whenever the process is running and answering. The Docker image's health check uses it. If the request can't connect, SpamFoo Client is down.

Check That It Is Ready

GET/health/ready

Returns 200 when SpamFoo Client can classify mail:

{"status": "ready", "contract": 1}

Otherwise it returns 503 with a Retry-After: 5 header and the reasons:

{
    "status": "not_ready",
    "code": "not_ready",
    "reasons": ["models_loading", "not_activated"],
    "contract": 1
}
Reason Meaning
not_activated No license key is set up.
models_loading The models haven't finished downloading. Classification requests wait for them.
shutting_down SpamFoo Client is stopping, for example to install an update. code is then shutting_down instead of not_ready.

Use it to decide whether to send mail, or to take SpamFoo Client out of a load balancer. In standalone mode, when SpamFoo Client is asked to stop, it keeps answering for a few seconds with shutting_down before it closes, so callers notice before connections are refused.

Check Queue Load

GET/health

When to Use It

  • Confirm that SpamFoo Client is accepting requests before you send mail to it.
  • Watch for shutting_down so a load balancer or mail server can stop sending requests while SpamFoo Client restarts for an update.
  • Track queue counters to decide whether to raise or lower SpamFoo Client's concurrency and intake limits.

The endpoint needs no proxy secret and has no license check. It returns 200 whenever SpamFoo Client is running, including while it prepares to restart, so check the status field rather than the status code. A request that cannot connect, or times out, means the service is down.

Note healthy means the service is running and not waiting to restart. It does not check whether the models have finished downloading or loading. Use /health/ready for that.

In standalone mode, a caller that isn't on loopback and doesn't send the proxy secret gets only {"status": "healthy", "service": "SpamFoo Client"}, without the queue counts.

Request

No headers, query parameters, or body.

Response

{
    "status": "healthy",
    "service": "SpamFoo Client",
    "shuttingDown": false,
    "activeRequests": 1,
    "modelReloading": false,
    "spoolQueue": {
        "active": 1,
        "waiting": 0,
        "maxConcurrency": 2,
        "totalProcessed": 18234,
        "totalTimedOut": 0
    },
    "backgroundQueue": {
        "active": 0,
        "waiting": 0,
        "maxConcurrency": 1,
        "totalProcessed": 912,
        "totalTimedOut": 0
    },
    "classifyIntake": {
        "inFlight": 1,
        "maxInFlight": 1024,
        "peakInFlight": 9,
        "totalRejected": 0
    },
    "bulkIntake": {
        "inFlight": 0,
        "maxInFlight": 1,
        "peakInFlight": 1,
        "totalRejected": 0
    },
    "authHeaderRaises": {
        "live_compared": 18234,
        "header_only": 0,
        ...
    }
}
Field Type Description
status string healthy, or shutting_down after SpamFoo Client has started to restart for an update. It finishes the requests in progress before it restarts.
service string SpamFoo Client.
shuttingDown boolean true when status is shutting_down.
activeRequests integer Classification and embedding requests in progress right now.
modelReloading boolean true while SpamFoo Client pauses its queues to swap in updated models. New requests wait until the swap finishes.
spoolQueue object The real-time queue used by /classify-stream.
backgroundQueue object The queue used by /tabs/classify-stream and SpamFoo Client's own background work.
classifyIntake object The limit on how many classification requests can hold a message in memory at once.
bulkIntake object The same counts for requests sent with X-SpamFoo-Priority: bulk, which have their own limit. See Mark Bulk Work.
authHeaderRaises object Counters SpamFoo uses to study how email authentication results are checked. You can ignore them.

Queue Fields

spoolQueue and backgroundQueue have the same fields. Counters start at zero when SpamFoo Client starts.

Field Type Description
active integer Jobs running now.
waiting integer Jobs waiting for a free slot.
maxConcurrency integer How many jobs can run at once.
totalProcessed integer Jobs finished since start.
totalTimedOut integer Jobs that waited too long and returned 503 with Classification queue timeout.

Intake Fields

classifyIntake and bulkIntake have the same fields.

Field Type Description
inFlight integer Requests to /classify-stream and /tabs/classify-stream in progress now.
maxInFlight integer The limit. For classifyIntake it's set by SPAMFOO_MAX_CLASSIFY_INFLIGHT. For bulkIntake it's one less than Max Concurrency, and at least 1. Requests over it return 503 with server_busy.
peakInFlight integer The highest inFlight value since start. Use it to pick a realistic limit for your traffic.
totalRejected integer Requests turned away with server_busy since start.

Errors

/health and /health/live have no error responses of their own. /health/ready returns 503 when SpamFoo Client isn't ready. If SpamFoo Client is not running, the connection is refused. In standalone mode, an address blocked for wrong secrets gets 429 on these endpoints too. See Repeated Wrong Secrets.

Example

curl -s http://localhost:16253/health
using System.Net.Http.Json;
using System.Text.Json;

using var http = new HttpClient
{
    BaseAddress = new Uri("http://localhost:16253"),
    Timeout = TimeSpan.FromSeconds(5)
};

try
{
    var health = await http.GetFromJsonAsync<JsonElement>("/health");
    var status = health.GetProperty("status").GetString();
    var waiting = health.GetProperty("spoolQueue").GetProperty("waiting").GetInt32();
    Console.WriteLine($"status={status} waiting={waiting}");
}
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException)
{
    Console.WriteLine("SpamFoo Client is not reachable.");
}
import requests

try:
    health = requests.get("http://localhost:16253/health", timeout=5).json()
    print(f"status={health['status']} waiting={health['spoolQueue']['waiting']}")
except requests.RequestException:
    print("SpamFoo Client is not reachable.")