- HTML 89.2%
- JavaScript 9.7%
- Dockerfile 1.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .gitignore | ||
| Containerfile | ||
| index.html | ||
| migaduSpamDomainExtractor.yaml | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| server.js | ||
Migadu Spam Domain Extractor
A small self-hosted web tool that logs into your Migadu mailbox over IMAP, scans the mailbox of your choice (e.g. Junk), extracts every sender domain, and turns them into ready-to-paste rules for Migadu's spam and deny filters.
Log in, pick the spam mailbox, get all mail domains in Migadu rule syntax
(*@domain, one per line), copy them in one click, and paste them straight
into the Migadu admin panel.
How it works
- Small Node.js backend (
server.js, Express + imapflow) that talks IMAP toimap.migadu.com:993(TLS) - You enter your mailbox credentials in the web UI, pick a mailbox, and the server scans all messages and returns the unique sender domains
- All domain processing (folding, validation, whitelist, merge) happens client-side in your browser
- Credentials are used in-memory for a single IMAP session per action and are never stored or logged — self-host this and only you see them
Features
- Login flow — enter your Migadu mailbox email + password, mailboxes are listed automatically (Junk pre-selected when detected)
- Mailbox picker — extract from any mailbox: Junk, INBOX, Archive, anything
- Domain extraction — scans the
Fromenvelope of every message - Subdomain folding (toggleable) — collapses
news.spam.exampledown tospam.example, handling common two-level TLDs (co.uk,com.au, ...) - Migadu syntax check — every domain is validated against the rules the
Migadu admin panel actually enforces before it ever reaches your output:
- every label must start with a letter, then alphanumeric/hyphen only,
1–63 chars, no trailing hyphen (digit-leading domains like
0589.deare rejected by Migadu, so they are skipped here too) - the TLD must exist in the IANA root zone (
cloud.rr-style garbage is dropped) - public-suffix awareness — never emits bare suffixes like
ac.id,gov.brornet.uathat Migadu refuses; keeps the full registrable domain instead - skipped entries are counted and shown ("N not Migadu-valid skipped") so you always know something was left out
- every label must start with a letter, then alphanumeric/hyphen only,
1–63 chars, no trailing hyphen (digit-leading domains like
- Whitelist — skips common legitimate senders (PayPal, GitHub, Apple, ...)
so you don't ban real mail; edit the lists at the top of the script in
index.html - Merge existing deny list — paste your current Migadu deny list (accepts
*@domain,@domainor baredomain, one per line) and it is validated and merged with the extracted domains into one big list - Per-domain removal — click
×on any domain to strike it from the output, click again to undo - Output formats:
- Migadu custom deny rules (
*@domain, one per line, wildcard before the@) - Sieve discard rules (for per-address filtering in Migadu)
- Plain domain list
- Migadu custom deny rules (
- One-click copy — copy the whole rule list to your clipboard
- Purple/pink themed UI — no build step, no JavaScript frameworks
How to use
- Open the website.
- Enter your Migadu mailbox address and password, click Log in & list mailboxes.
- Pick the mailbox to scan (your Junk/Spam folder is pre-selected when found) and click Extract domains. Domains that can never be valid Migadu rules are automatically skipped and counted.
- Optionally remove domains you don't want to ban.
- Optionally paste your existing deny list under Merge existing deny list and click Merge into list to build one combined list.
- Pick the output format and click Copy.
- In the Migadu admin panel, open your domain → Spam Filtering (or the per-address filter settings) and paste the rules.
Example
Spam mailbox contains messages from:
From: "Mega Deals" <deals@spammy-shop.example>
From: job.offers@totally-legit-jobs.example>
From: "Winner" <you-won@news.lottery-scam.example>
Output — Migadu deny rule syntax:
*@spammy-shop.example
*@totally-legit-jobs.example
*@lottery-scam.example
Output — Sieve rules:
require ["fileinto"];
if address :domain :is "from" "spammy-shop.example" { discard; }
if address :domain :is "from" "totally-legit-jobs.example" { discard; }
if address :domain :is "from" "lottery-scam.example" { discard; }
Repository layout
.
├── Containerfile # Podman/Docker image (node:22-alpine, non-root)
├── index.html # Web UI + all client-side logic (single file)
├── miaduSpamDomainExtractor.yaml # Complete Kubernetes deployment in one manifest
├── package.json # Node dependencies
├── package-lock.json # Pinned dependency tree (commit this!)
├── README.md
├── server.js # Express + imapflow backend
└── .gitignore
Only express and imapflow are dependencies — the whole app is three files
(server.js, index.html, package.json) plus the lockfile and deployment
artifacts.
Configuration
| Environment variable | Default | Description |
|---|---|---|
PORT |
3000 |
HTTP listen port |
The IMAP host is hardcoded to imap.migadu.com (TLS, port 993). The /api
endpoints also accept an optional host field in the request body if you ever
need to point at a different IMAP server.
Development
npm install
npm start
# open http://localhost:3000
Deployment
Kubernetes (recommended for self-hosting)
Build the image, push it to your registry, and apply the manifest:
# 1. Build and push (podman)
podman build -t your-registry.example.com/migadu-spam-domain-extractor:latest .
podman push your-registry.example.com/migadu-spam-domain-extractor:latest
# 2. Adjust the image reference and the hostname in miaduSpamDomainExtractor.yaml
# 3. Deploy
kubectl apply -f miaduSpamDomainExtractor.yaml
miaduSpamDomainExtractor.yaml contains all resources in one file, deployed
into the migadu-spam-domain-extractor namespace:
- Namespace
- Deployment — 2 replicas, Node.js serving the app, liveness/readiness probes, small resource requests (10m CPU / 32Mi), non-root security context
- Service — ClusterIP port 80 → container port 3000
- Traefik Middleware — HSTS
- Traefik IngressRoute —
websecureentrypoint, TLS via theletsencryptcertResolver — set your hostname in thematchrule before applying
To test locally without going through Traefik:
kubectl port-forward -n migadu-spam-domain-extractor svc/migadu-spam-domain-extractor 8080:80
Important: your mailbox password is sent to the instance on every action.
Keep the IngressRoute on the websecure entrypoint (HTTPS via Let's Encrypt)
and never expose the service over plain HTTP or host it somewhere you don't trust.
Podman / Docker (standalone)
podman build -t migadu-spam-domain-extractor .
podman run -p 8080:3000 migadu-spam-domain-extractor
# open http://localhost:8080
Any Node host
npm install --omit=dev
PORT=3000 node server.js
Privacy
Credentials are sent only to this self-hosted instance, held in memory for the duration of one IMAP session per action, and never stored, cached, or logged. The extracted domains, the syntax validation and the deny-list merge are processed entirely in your browser. Host it yourself so your mailbox password never leaves your infrastructure.