https://github.com/francesco149/dmarc-analyzer
Automatically scan and analyze DMARC aggregate reports, for quick visual inspection
https://github.com/francesco149/dmarc-analyzer
email nixos nixos-flake nixos-module nixos-service vibe-coded
Last synced: about 2 months ago
JSON representation
Automatically scan and analyze DMARC aggregate reports, for quick visual inspection
- Host: GitHub
- URL: https://github.com/francesco149/dmarc-analyzer
- Owner: Francesco149
- License: gpl-3.0
- Created: 2026-03-17T07:19:26.000Z (3 months ago)
- Default Branch: master
- Last Pushed: 2026-04-01T13:59:03.000Z (3 months ago)
- Last Synced: 2026-04-01T15:34:56.859Z (3 months ago)
- Topics: email, nixos, nixos-flake, nixos-module, nixos-service, vibe-coded
- Language: HTML
- Homepage: https://github.com/Francesco149/dmarc-analyzer?tab=readme-ov-file#dmarc-analyzer
- Size: 307 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# dmarc-analyzer
> [!WARNING]
> Vibe coded — use at your own risk.
>
> This is an ad hoc tool I put together for my own setup. It is not robust, not
> production-hardened, and not intended for general use. It works for me. It may
> not work for you, may break unexpectedly, and comes with no guarantees
> whatsoever. If you use it, you're on your own.
A self-hosted DMARC aggregate report analyzer. Scans a Maildir for DMARC report
emails, extracts and parses the XML attachments, and serves a small web UI
showing a feed of reports with pass/fail breakdowns per sending IP.

---
## How it works
Three moving parts:
- **`dmarc-scanner.py`** — a Python script that scans a Maildir, extracts DMARC
zip/xml attachments from matching emails, parses the XML, and writes a
`reports.json` feed file. Runs as a systemd oneshot on a timer.
- **`dmarc-server.py`** — a minimal stdlib Python HTTP server that serves the
frontend HTML and the `reports.json` feed on `localhost:PORT`. Intended to sit
behind a reverse proxy.
- **`src/`** — the frontend source. `build.py` combines `index.html`,
`style.css`, `app.js`, inlined fonts, and bundled JSZip into a single
self-contained `dmarc-feed.html`.
---
## Project layout
```text
dmarc-analyzer/
├── build.py # assembles dmarc-feed.html from src/
├── dmarc-feed.html # built output — committed, used by the flake
├── src/
│ ├── index.html # HTML shell with {{PLACEHOLDER}} tokens
│ ├── style.css
│ └── app.js
├── package.json # jszip + fontsource deps for build
├── flake.nix
├── module.nix # NixOS module
├── packages.nix # packages
├── dmarc-scanner.py
└── dmarc-server.py
```
---
## Building the frontend
One-time setup:
```bash
npm install
```
After editing anything in `src/`:
```bash
python3 build.py
```
Or use the dev shell:
```bash
nix develop
python3 build.py
```
---
## NixOS module
Add the flake input to your system flake:
```nix
inputs.dmarc.url = "github:Francesco149/dmarc-analyzer";
```
Import the module and configure:
```nix
imports = [ inputs.dmarc.nixosModules.dmarc-analyzer ];
services.dmarc-analyzer = {
enable = true;
mailDir = "/var/vmail/example.com/postmaster/mail";
scanUser = config.mailserver.vmailUserName;
port = 8741;
# Default is 127.0.0.1. Set to a LAN IP if your reverse proxy
# is on a different machine. Restrict with a firewall rule.
listenHost = "0.0.0.0";
scanInterval = "15min"; # systemd OnUnitActiveSec format
maxReports = 200;
};
```
Wire into your reverse proxy — e.g. Caddy:
```Caddyfile
dmarc.box.example.com {
reverse_proxy 10.0.10.x:8741
}
```
### Module options
| Option | Default | Description |
| -------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `enable` | — | Enable the service |
| `mailDir` | — | Path to Maildir or mbox containing DMARC report emails |
| `scanUser` | `dmarc-analyzer` | User to run the scanner as. Must have read access to `mailDir`. With nixos-mailserver, set to `config.mailserver.vmailUserName` |
| `port` | `8741` | Port the HTTP server listens on |
| `listenHost` | `127.0.0.1` | Address to bind to |
| `scanInterval` | `15min` | How often to scan for new reports |
| `maxReports` | `200` | Max reports kept in `reports.json` |
### Notes
- The module does **not** touch Caddy, nginx, `/etc/hosts`, or any other system
config. Reverse proxy wiring is left to you.
- The scanner runs as `scanUser` (e.g. `virtualMail`) so it can read a
`700`-mode Maildir. Output files are written as group `dmarc-analyzer` so the
HTTP server can read them.
- State lives in `/var/lib/dmarc-analyzer/`. Deleting it is safe — systemd will
recreate the directories on next start via `StateDirectory`.
- The HTTP server only ever serves two paths: `/` (the frontend) and
`/data/reports.json`. Everything else returns 404.
---
## Firewall
If `listenHost` is not `127.0.0.1`, restrict access with a firewall rule. With
NixOS iptables:
```nix
networking.firewall.extraCommands = ''
iptables -A nixos-fw -s -d -p tcp --dport 8741 -j nixos-fw-accept
'';
networking.firewall.extraStopCommands = ''
iptables -D nixos-fw -s -d -p tcp --dport 8741 -j nixos-fw-accept || true
'';
```
---
## Frontend — standalone use
The built `dmarc-feed.html` is fully self-contained (fonts and JSZip inlined).
It can be used without the backend — just open it in a browser and upload `.zip`
or `.xml` report files directly. When hosted, it automatically fetches
`/data/reports.json` on load.