imap-mail-filter (IMF) is a small daemon that bolts spam detection and routing rules onto any IMAP mailbox — built for people who run their own mail server. On Gmail or Outlook.com? You're already covered, no need for this.
Mail servers and clients often do this natively, but every time you switch software you get to reconfigure it all from scratch. IMF only needs IMAP, so it doesn't care what's behind it: point it at a new server and everything — rules included — just keeps working.
Teaching it a new rule is dead simple too: drop an example email into the right imf-rules/ subfolder, and every future email matching the same criteria gets routed automatically. See Learning rules by example for how that folder structure works.
- Java 17.
- An IMAP account, reachable over IMAPS (implicit TLS) — there's no plain-IMAP mode, sorry. Folder-creation rights too, since IMF manages its own
imf-rules/folder tree; the default on pretty much any account you'd actually own. - An internet connection — to your mail server, and for the SPF/DKIM/DMARC/FCrDNS checks, which are just DNS lookups. Nothing else ever leaves the box.
That's it.
Brings to your IMAP account:
- SPF, DKIM, DMARC and FCrDNS checks
- Rules based on from and subject
- ML-based spam detection (
SUBJECT_CLASSIFIER_EQUALS), trained locally on your own emails — no dataset, no third-party service, just a model built and kept from your own mailbox as it grows - IP/domain reputation scoring (
IP_REPUTATION_EQUALS/FROM_DOMAIN_REPUTATION_EQUALS) against lists you download yourself — periodic bulk download, never a live per-message lookup, so nothing about a specific email is ever sent anywhere
Additionally:
- Logs show exactly which rule acted on each message
- No third-party service ever sees your mail — the only outbound traffic is DNS lookups for SPF/DKIM/DMARC/FCrDNS checks, which reveal sender domains/IPs but never message content.
A few things worth understanding before you dive in:
- No UI, no manual rule editing — the primary way to teach IMF a rule is to drop an example email into the right
imf-rules/subfolder. See Learning rules by example. - First match wins — rules are evaluated in the order they appear in
config.json(learned rules included, wherever you place them — after everything else by default); the first one that matches runs its action and evaluation stops there. See Rule evaluation order. - Everything is HAM except Spam — for classifier corpus collection, every folder is treated as legitimate mail (HAM) except the configured Spam folder.
INBOX,imf-rules/, and any excluded folders (e.g.SpamML) are skipped entirely rather than counted as either. - INBOX doesn't count — INBOX is never scanned for the corpus, so mail you leave sitting there teaches the classifier nothing. Filing/archiving read mail into folders (an "inbox zero" habit) is what actually feeds it examples of legitimate mail.
- Unread in Spam means "review me" — by convention (see the starter config), strong verdicts (SPF/DKIM/DMARC
fail) are moved to Spam pre-marked read, while weaker, corroborating-only signals are left unread — a manual-review flag, since IMF has no UI to show confidence. - Always verified live — SPF/DKIM/DMARC/FCrDNS are recomputed from scratch via DNS on every check; any
Authentication-Results/Received-SPFheader already on the message is never trusted, since anyone could have forged it before delivery. - Reputation lists are the opposite: never live —
IP_REPUTATION_EQUALS/FROM_DOMAIN_REPUTATION_EQUALScheck IPs/domains against lists downloaded in bulk ahead of time, for the whole process — never a query per message. Each source is called again oncerefreshHourshas elapsed. See Reputation lists. - Manual reprocessing — drop any message into
imf-rules/ToProcessto run the current rule set against it (handy for reclassifying an old message after adding or fixing a rule); it ends up inimf-rules/Doneonce handled, whether or not a rule actually matched. See Manually reprocessing a message.
- Feed the classifier more than just the subject — sender, recipient, IP.
- Simple web UI to edit more complex rules.
- More matchers:
- Recipient-based: TO_EQUALS/CC_EQUALS/recipient-domain matching
- Subject: CONTAINS, MATCHES (regex)
- Generic headers: HEADER_EQUALS/HEADER_CONTAINS(name, value)
- Body: BODY_CONTAINS, BODY_MATCHES
- SIZE
- Attachment: HAS_ATTACHMENT, FILENAME_ENDS_WITH, FILENAME_IS
- Dated: MESSAGE_AGE, MESSAGE_DATE
- IMAP_FLAGS
- More actions:
- COPY_TO
- DELETE
- SET_FLAG
- FORWARD/REDIRECT/REPLY — Later, it's a much bigger endeavor
- New here? Quick start guide — download the jar, try it, then run it as a systemd service.
- docs/ — how IMF works, the configuration reference, a dedicated page for every matcher and action.
docs/IMPLEMENTATION-DETAILS.md— digging into the code? The "why" behind a few non-obvious internals.
config.example.json is a reasonable config to start from: SPF, DKIM,
DMARC, FCrDNS, and reputation lists all enabled, with sane logging. Copy it to your dataFolder
as config.json and fill in host/username/password. What it does:
- SPF/DKIM/DMARC
fail→ Spam, pre-marked read — the strong, protocol-verified signals. - SPF
softfail+ FCrDNSfail/none(AND) → Spam, left unread for review.FCRDNS_RESULT_EQUALSis the weakest of the four, so it's never used standalone here, only as corroboration. - Subject classifier (
SUBJECT_CLASSIFIER_EQUALS >0.99) →SpamML(its own folder, notSpam), pre-marked read. Does nothing until trained on at least 50 examples of each class — see Classifier corpus collection.classifierExcludedFolderskeepsSpamMLout of corpus collection — otherwise it'd get scanned and mislabeledHAM, and the classifier would train on its own past verdicts. See Excluding a folder from the corpus. - IP reputation — Spamhaus DROP and
FireHOL's
blocklist_de_mail, two independent feeds (different methodologies, so agreement means more than either alone): both match → Spam pre-marked read; only one → Spam left unread. - Domain reputation — same
AND/ORpattern, with HaGeZi's TIF mini and the Blocklist Project's phishing list. - Disposable email domains — a community list of throwaway providers, used standalone → Spam left unread. A different kind of signal: anonymous, not necessarily malicious.
- Newly registered domains — HaGeZi's NRD7, domains registered in the last 7 days, standalone → Spam left unread. Same reasoning as disposable domains: unusual, not proof. Heads up: this one's ~2.5M entries/~40MB, much bigger than everything else here — see Reputation lists.
See Reputation lists for how these are downloaded (once for the whole process, refreshed periodically, never queried live per message).