Skip to content

Getting Started

In this tutorial, you install SPOT on a new host and analyze your first email. By the end, SPOT runs with its rules and NLP analyzers, and you have read a verdict in the dashboard, from the command line and through the API. It takes about half an hour, most of it spent pulling images.

Before you start

You need:

  • a Linux host with 4 GiB of memory and 20 GiB free for Docker, where ports 80 and 443 are free;
  • Docker Engine 25.0.4 or later, Docker Compose 2.24 or later, git, and jq for the API step;
  • a user that can run docker: root, or a member of the docker group. The steps install SPOT in /opt/spot, as the other pages assume: as another user than root, create it first with sudo install -d -o "$USER" /opt/spot;
  • a domain for SPOT, spot.example.com on this page, with DNS records that point it and api.spot.example.com at the host.

Let's Encrypt issues the certificates when the host is reachable from the internet. For an evaluation on a private network, entries in /etc/hosts work too: Traefik then serves a self-signed certificate, your browser warns about it, and the curl commands below need -k (see Local DNS and ACME).

1. Install SPOT

Clone the spot repository, check out its latest release (main is development), then run setup from its deploy/ directory. Install from deploy/, never from the repository's root: the docker-compose.yml there is the development stack.

cd /opt
git clone https://codeberg.org/SPOT_Project/spot.git
cd spot
git checkout --detach "$(git describe --tags --abbrev=0)"
cd deploy
scripts/spot-cli setup --profile minimal --domain spot.example.com \
  --acme-email ops@example.com --admin-email admin@example.com

Run on a terminal without options, scripts/spot-cli setup asks for each, the profile first, and prints this line before it starts.

setup checks the host and lists every problem it finds, with its fix, before it changes anything. Then it writes .env with every secret generated, starts SPOT and installs the minimal profile: analyzer-rules, analyzer-nlp and their workflow, minimal-workflow, which becomes the default. Last, it analyzes a test email, and fails unless the verdict is a real one: a class other than unknown, with every analyzer answering.

When it succeeds, it ends with:

[setup] SPOT runs with the minimal profile, set up in 14 min 2 s
[setup] Dashboard: https://spot.example.com
[setup] Admin: admin; its password is SPOT_ADMIN_PASSWORD in .env
[setup] Back up with spot-cli backup

The standard and full profiles add analyzer-llm on an NVIDIA GPU (Profiles). The GPU variant they run has only passed docker compose config: it has not run on a GPU host yet. Run every command below from /opt/spot/deploy.

2. Sign in to the dashboard

setup never prints the admin's password: it is SPOT_ADMIN_PASSWORD in /opt/spot/deploy/.env. Open the file in an editor and read it there, rather than printing it on your terminal.

Open https://spot.example.com and sign in as admin with it. The Analyses page lists one analysis: setup's test email, with its verdict and its score.

3. Analyze an email from the command line

Save this message as phish.eml. It is a textbook phishing attempt: a sender that spoofs PayPal with a lookalike domain (paypaI.com, with a capital I) and a link that harvests passwords.

cat > phish.eml <<'MESSAGE'
From: PayPal Security <security@paypaI.com>
To: user@example.com
Subject: URGENT: Verify Your Account
Date: Thu, 15 Jan 2026 10:00:00 +0000
Message-ID: <test@example.com>

Your account is limited. Click here within 24 hours to verify your password:
http://paypaI.com/verify
MESSAGE

Submit it and wait for its verdict. setup signed the CLI in as the admin, so no login is needed:

scripts/spot-cli analyze submit -f phish.eml --wait

The CLI sees the directory you run it from, read-only: -f phish.eml reads ./phish.eml, and -f - reads the message from standard input. It prints the job, then its verdict once the workflow has decided. Your score may differ:

Submitted; job_id = 3f0e7c1a-..., email_id = 9b2d4e6f-...
job_id      3f0e7c1a-...
status      completed
verdict     suspicious
score       64
decided_by  method:weighted_mean
workflow    minimal-workflow
subject     URGENT: Verify Your Account
sender      security@paypaI.com

Add --verbose to scripts/spot-cli analyze status <job_id> to see the signals behind the verdict. If the CLI answers Not authenticated, its session has expired: sign in again with scripts/spot-cli auth login.

4. Analyze it through the API

Scripts and mail tools call the API at https://api.spot.example.com. Trade the admin's credentials for an access token. read -rs keeps the password off the screen and out of your shell history:

DOMAIN=spot.example.com
read -rs -p 'Admin password: ' password; echo
TOKEN=$(printf '%s' "$password" | curl -s https://api.$DOMAIN/auth/token \
  --data-urlencode username=admin --data-urlencode password@- | jq -r .access_token)

Submit the raw message. SPOT parses and stores it, queues its analysis and answers at once with a job id:

JOB_ID=$(curl -s https://api.$DOMAIN/api/v1/analyze \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: message/rfc822" \
  --data-binary @phish.eml | jq -r .job_id)

Read the job until its status is completed or failed, a few seconds later:

curl -s -H "Authorization: Bearer $TOKEN" https://api.$DOMAIN/api/v1/analyze/$JOB_ID \
  | jq '{status, verdict, score, decided_by, workflow_id}'
{
  "status": "completed",
  "verdict": "suspicious",
  "score": 64,
  "decided_by": "method:weighted_mean",
  "workflow_id": "minimal-workflow"
}

The Analyses page of the dashboard now lists your two analyses.

5. Read the verdict

Every analysis ends with one verdict, whichever way the email came in:

Verdict Meaning
malicious An attack: phishing, fraud, malware
suspicious Likely harmful: worth a review
spam Unwanted, not an attack
benign Nothing found
unknown SPOT could not decide: no analyzer answered, a required one did not, or the job failed. Never read it as benign
  • score is 0 to 100: how likely the email is malicious.
  • decided_by says what decided: a decisive signal (signal:<name>), the workflow's method (method:<name>), or why the verdict is unknown (required_unknown, all_unknown, failed).
  • signals are the evidence behind the verdict, strongest first. Each names the analyzer that emitted it, its weight in score points, and where it was found in the email.

minimal-workflow runs analyzer-rules and analyzer-nlp at once and decides by the weighted mean of their scores: 80 or more is malicious, 50 or more suspicious. Workflows explains how a workflow turns the analyzers' answers into a verdict.

Next steps

You now have a running SPOT that analyzes the emails you submit. From here: