HTTP API, TCP server and spamd

HTTP API#

Shell
spamscanner http --port 7832 --token "$SPAMSCANNER_TOKEN"

It listens on 127.0.0.1 unless --host says otherwise. With a token, every request except /health needs Authorization: Bearer <token>. Put it behind a reverse proxy with TLS before exposing it beyond the machine.

Method and pathBodyAnswer
GET /health{"ok": true, "version": "7.0.0"}
POST /scanThe raw messageThe scan result as JSON
POST /checkThe raw messageThe message with X-Spam-* headers added, as message/rfc822
POST /learn/spamThe raw message{"ok": true, "learned": "spam"}; needs a token
POST /learn/hamThe raw message{"ok": true, "learned": "ham"}; needs a token

Query parameters describe the SMTP session:

ParameterMeaning
ipThe client's IP address
hostnameIts verified reverse DNS name
heloIts HELO or EHLO name
fromThe envelope sender
toA recipient; repeat it or separate several with commas
verbose=1/scan: also return the word list and the subject
subjectTag/check: prefix the subject of spam, for example %5BSPAM%5D

/check also returns X-Spam-Flag, X-Spam-Score and X-Spam-Action as response headers, so a client can decide without parsing the message.

Messages larger than 25 MB get 413. A scan that fails gets 500 with {"error": "..."}.

Shell
curl -s -X POST -H "Authorization: Bearer $SPAMSCANNER_TOKEN" \
  --data-binary @message.eml "http://127.0.0.1:7832/scan?ip=203.0.113.5&to=bob@example.org" | jq '.isSpam, .score, .tests'

With --out model.json, what /learn teaches is saved to that file after each request. Without it, learning lasts until the server restarts.

From Node.js:

JavaScript
const response = await fetch('http://127.0.0.1:7832/scan?ip=203.0.113.5', {
  method: 'POST',
  headers: {authorization: `Bearer ${process.env.SPAMSCANNER_TOKEN}`},
  body: rawMessage,
});
const {isSpam, score, action} = await response.json();

From Python:

Python
import os, urllib.request

request = urllib.request.Request(
    'http://127.0.0.1:7832/scan',
    data=open('message.eml', 'rb').read(),
    headers={'Authorization': f"Bearer {os.environ['SPAMSCANNER_TOKEN']}"},
    method='POST',
)
print(urllib.request.urlopen(request).read().decode())

TCP server#

Shell
spamscanner server --port 7830

Send the raw message, close the sending side of the connection, and read one line of JSON:

Shell
nc -N 127.0.0.1 7830 < message.eml

With --verbose, the answer is a line of text instead: SPAM 16.3/5.0 BAYES_999,PHISHING_LOOKALIKE_DOMAIN,DECEPTIVE_LINK,FROM_NAME_BRAND or HAM -2.5/5.0 BAYES_00.

spamd#

Shell
spamscanner spamd --port 783

A SpamAssassin-compatible server for spamc, Exim, Haraka and other SpamAssassin clients. Setting up Exim and Haraka

CommandAnswer
CHECKSpam: True ; 16.3 / 5.0
SYMBOLSThe verdict and the names of the tests that fired
REPORTThe verdict and a table of tests, points and reasons
REPORT_IFSPAMLike REPORT, with an empty report for ham
PROCESSThe verdict and the message with X-Spam-* headers
HEADERSThe verdict and the message's header block with X-Spam-* headers
PINGPONG
SKIPNothing
TELLLearns spam or ham, with --allow-tell; saves to --out

Compressed requests (Compress: zlib) are refused.

Edit this page on GitHub · Markdown ·