No description
  • Common Lisp 79.9%
  • Python 12.9%
  • Shell 4.5%
  • Makefile 2.7%
Find a file
2026-07-14 15:45:22 +02:00
deploy Elevate install steps via SUDO instead of requiring sudo make 2026-06-27 18:56:04 +02:00
mel-base@16057d9e9f Update mel-base submodule (new README) 2026-03-19 14:15:32 +01:00
prompts Add trusted-domains hint to spam classifier prompts 2026-06-27 18:52:37 +02:00
src Add trusted-domains hint to spam classifier prompts 2026-06-27 18:52:37 +02:00
test Add SMTP filter server for Postfix before-queue content filtering 2026-03-27 06:34:03 +01:00
.gitignore update .gitignore 2026-07-14 15:45:22 +02:00
.gitmodules Replace postoffice with mel-base for IMAP operations 2026-03-19 12:34:43 +01:00
config.lisp.example Add trusted-domains hint to spam classifier prompts 2026-06-27 18:52:37 +02:00
imap-cleaner.asd Add SMTP filter server for Postfix before-queue content filtering 2026-03-27 06:34:03 +01:00
Makefile Elevate install steps via SUDO instead of requiring sudo make 2026-06-27 18:56:04 +02:00
README.md Add buildapp build, CLI options, and deployment scripts 2026-03-20 06:50:12 +01:00
run.lisp Add buildapp build, CLI options, and deployment scripts 2026-03-20 06:50:12 +01:00
run.sh Add buildapp build, CLI options, and deployment scripts 2026-03-20 06:50:12 +01:00
test-config.lisp Add run.lisp, test-config.lisp, and simplify README setup instructions 2026-03-19 13:23:11 +01:00

imap-cleaner

An IMAP spam filter that uses the Claude API to classify messages. Written in Common Lisp.

How it works

imap-cleaner connects to an IMAP mailbox over SSL and classifies incoming messages as spam or ham using a two-stage approach:

  1. Header analysis -- Email headers are sent to Claude for classification. Most spam is caught at this stage based on sender domains, authentication results, subject patterns, and spam filter headers.

  2. Body analysis -- If the header verdict is below the confidence threshold (default 80%), the message body is also fetched and sent for a second classification pass.

Messages classified as spam are either flagged (dry-run mode) or moved to a spam folder.

IMAP IDLE

The program uses two IMAP connections for real-time mail processing:

  • A monitor connection stays in IMAP IDLE mode, receiving instant push notifications when new mail arrives.
  • A worker connection opens on-demand to fetch and process messages, then disconnects.

This avoids race conditions between unsolicited server pushes and command responses, and eliminates keepalive concerns during potentially long Claude API calls.

If the server doesn't support IDLE, imap-cleaner falls back to polling.

Dry-run mode

When :dry-run t is set (the default), spam messages are flagged with \Flagged instead of being moved. This lets you review classifications in your mail client before trusting the system. Set :dry-run nil once you're satisfied.

Building

Requirements

Install Quicklisp

curl -O https://beta.quicklisp.org/quicklisp.lisp
sbcl --noinform --non-interactive \
  --load quicklisp.lisp \
  --eval '(quicklisp-quickstart:install)' \
  --eval '(ql:add-to-init-file)'

Install buildapp

sbcl --noinform --non-interactive \
  --eval '(ql:quickload "buildapp")' \
  --eval '(buildapp:build-buildapp "buildapp")'
sudo install buildapp /usr/local/bin/

Build

git clone --recurse-submodules https://github.com/hanshuebner/imap-cleaner.git
cd imap-cleaner
make

This produces a standalone imap-cleaner binary.

Install

sudo make install

This installs:

  • The binary to /usr/local/bin/imap-cleaner
  • Prompt files and example config to /usr/local/etc/imap-cleaner/

On Debian, use SYSCONFDIR=/etc to put config files in /etc/imap-cleaner/:

sudo make install SYSCONFDIR=/etc

Configuration

Edit the config file (installed to /usr/local/etc/imap-cleaner/config.lisp or /etc/imap-cleaner/config.lisp):

(
 :imap-host "mail.example.com"
 :imap-port 993
 :imap-user "user@example.com"

 ;; Direct value or shell command:
 :imap-password "your-password"
 ;; :imap-password-command "pass show email/imap"

 :inbox "INBOX"
 :spam-folder "Junk"

 ;; Direct value or shell command:
 :claude-api-key "sk-ant-..."
 ;; :claude-api-key-command "pass show api/anthropic"

 :dry-run t  ; set to nil once you trust the classifications
)

Secrets can be provided directly or via shell commands (e.g. using pass, op, or security).

Classification prompts

The prompts that guide Claude's classification are installed alongside the config. You can customize headers-prompt.txt and body-prompt.txt for your mailbox -- for example, adding context about what kind of mail your address typically receives, or whitelisting specific senders.

Configuration reference

Key Default Description
:imap-host (required) IMAP server hostname
:imap-port 993 IMAP port (SSL)
:imap-user (required) IMAP username
:imap-password (required) IMAP password (or use :imap-password-command)
:inbox "INBOX" Mailbox to monitor
:spam-folder "Junk" Folder to move spam to
:claude-api-key (required) Anthropic API key (or use :claude-api-key-command)
:claude-model "claude-haiku-4-5-20251001" Claude model to use
:use-idle t Use IMAP IDLE for push notifications
:idle-timeout-seconds 1500 Re-issue IDLE every N seconds (max ~29 min per RFC)
:poll-interval-seconds 120 Polling interval when IDLE is disabled
:max-messages-per-poll 50 Max messages to process per cycle
:header-confidence-threshold 80 Below this, also check body content
:body-max-chars 4000 Truncate body text sent to API
:dry-run nil Flag spam instead of moving it
:headers-prompt-file (auto-detected) Custom headers classification prompt
:body-prompt-file (auto-detected) Custom body classification prompt
:log-file (stderr) Log file path
:debug nil Enable debug logging

Usage

imap-cleaner [OPTIONS]

Options:
  --config PATH   Configuration file (default: ~/.imap-cleaner/config.lisp)
  --scan N        Scan last N messages, print statistics, and exit
  --help          Show help message

Test your configuration by scanning a few messages:

imap-cleaner --config /usr/local/etc/imap-cleaner/config.lisp --scan 5

Run in monitoring mode:

imap-cleaner --config /usr/local/etc/imap-cleaner/config.lisp

Deployment

Debian / Ubuntu

Create a service user and install the systemd service:

sudo useradd --system --create-home --shell /usr/sbin/nologin imap-cleaner
sudo chown imap-cleaner:imap-cleaner /etc/imap-cleaner/config.lisp
sudo mkdir -p /home/imap-cleaner/.imap-cleaner
sudo chown imap-cleaner:imap-cleaner /home/imap-cleaner/.imap-cleaner
sudo make install-service-debian SYSCONFDIR=/etc
sudo systemctl enable imap-cleaner
sudo systemctl start imap-cleaner

View logs:

journalctl -u imap-cleaner -f

FreeBSD

Create a service user and install the rc script:

sudo pw useradd imap-cleaner -d /home/imap-cleaner -m -s /usr/sbin/nologin -c "IMAP Cleaner"
sudo chown imap-cleaner:imap-cleaner /usr/local/etc/imap-cleaner/config.lisp
sudo mkdir -p /home/imap-cleaner/.imap-cleaner
sudo chown imap-cleaner:imap-cleaner /home/imap-cleaner/.imap-cleaner
sudo touch /var/log/imap-cleaner.log
sudo chown imap-cleaner:imap-cleaner /var/log/imap-cleaner.log
sudo gmake install-service-freebsd
sudo sysrc imap_cleaner_enable=YES
sudo service imap_cleaner start

View logs:

tail -f /var/log/imap-cleaner.log

Monitoring

imap-cleaner logs to stderr (or a configured log file) and reconnects automatically on connection failures with backoff. The systemd service is configured with Restart=on-failure and the FreeBSD rc script uses daemon(8), so the process will be restarted if it exits unexpectedly.

To check if the service is running:

# Debian
systemctl status imap-cleaner

# FreeBSD
service imap_cleaner status

Running from source

For development, you can run directly with SBCL without building:

sbcl --noinform --non-interactive --load run.lisp [--config PATH] [--scan N]

Or use the test script to verify your configuration:

sbcl --noinform --non-interactive --load test-config.lisp

Compatibility

imap-cleaner is developed and tested with SBCL. The built binary and helper scripts (run.lisp, test-config.lisp) use SBCL-specific features. The core system (imap-cleaner.asd) does not use SBCL-specific features and may work on other Common Lisp implementations, but this has not been tested. The mel-base IMAP library supports SBCL, CCL, and LispWorks.

Dependencies

Loaded via Quicklisp:

  • mel-base -- IMAP/SSL (included as submodule, forked from 40ants/mel-base)
  • dexador -- HTTP client for Claude API
  • yason -- JSON parsing
  • alexandria -- Utilities
  • cl-ppcre -- Regular expressions
  • babel -- Character encoding

License

MIT