Dashboard for DMARC statistics with ingestion backend for indexing DMARC mails.
  • Java 74.7%
  • HTML 22.4%
  • Dockerfile 2.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Martin Kofoed 9627f7742b
All checks were successful
ci / verify (push) Successful in 2m7s
Release image / build (release) Successful in 3m54s
Set timezone in runtime image
2026-09-02 15:12:33 +02:00
.forgejo/workflows Created new Dockerfile and removed quarkus stock Dockerfiles 2026-09-02 15:05:47 +02:00
gradle/wrapper Refactored the folder structure of the project 2026-08-29 14:07:54 +02:00
grafana Refactored the folder structure of the project 2026-08-29 14:07:54 +02:00
src Set timezone in runtime image 2026-09-02 15:12:33 +02:00
.gitignore Refactored the folder structure of the project 2026-08-29 14:07:54 +02:00
AGENTS.md Refactored the folder structure of the project 2026-08-29 14:07:54 +02:00
build.gradle Refactored the folder structure of the project 2026-08-29 14:07:54 +02:00
docker-compose.yml Refactored the folder structure of the project 2026-08-29 14:07:54 +02:00
gradle.properties Bumped quarkus to latest 2026-08-29 14:09:16 +02:00
gradlew Refactored the folder structure of the project 2026-08-29 14:07:54 +02:00
gradlew.bat Refactored the folder structure of the project 2026-08-29 14:07:54 +02:00
README.md Refactored the folder structure of the project 2026-08-29 14:07:54 +02:00
settings.gradle Refactored the folder structure of the project 2026-08-29 14:07:54 +02:00

DMARC Stats

Ingest DMARC aggregate reports (RUA) from email, store normalized results in a database, visualize via built-in dashboard or Grafana.

  • App: Quarkus (Java 25), Gradle, Hibernate + Panache, Flyway. Dashboard is served by the same process.
  • Modes:
    • Scheduled poller (pull from IMAP mailbox on a cron)
    • Trigger on arrival (POST raw .eml to /api/ingest — ideal for mail server webhooks / Sieve / procmail style)
  • DB: H2 (dev) / PostgreSQL (prod)
  • Frontend: Zero-dependency single-file dashboard served at / (Tailwind + Chart.js via CDN)
  • Observability: Grafana + Postgres pre-provisioned

Project Layout

dmarc-stats/
├── src/main/java/dk/kofoed/dmarc/   # Quarkus application
├── src/main/resources/
│   ├── META-INF/resources/index.html  # Dashboard (served at /)
│   ├── db/migration/                  # Flyway
│   └── dmarc-samples/                 # Bundled sample .eml reports
├── src/test/
├── grafana/                           # Grafana provisioning + dashboard JSON
├── docker-compose.yml
├── build.gradle
└── README.md

Quick Start (Local Dev)

  1. Prerequisites

    • Java 25+
    • (Optional) Docker for full stack
  2. Easiest: one-command self-contained demo (embedded H2, no Postgres needed)

    ./gradlew demoUberJar
    java -jar build/dmarc-stats-0.1.0-SNAPSHOT-runner.jar
    

    This builds a single ~46 MB executable jar (dev profile + H2) that bundles the two sample reports. The reports are imported automatically on startup.

    Then open http://localhost:8080 (dashboard + APIs on the same origin).

  3. Normal development (recommended for daily work)

    ./gradlew quarkusDev
    

    The app starts on http://localhost:8080. Sample reports are auto-loaded on first start if the DB is empty. The dashboard is at /. It has a "Load sample data" button and file upload for more .eml.

  4. Useful API endpoints

    • POST /api/ingest (application/octet-stream) — send a raw .eml
    • GET /api/reports
    • GET /api/stats/summary?days=30
    • GET /api/stats/daily
    • GET /api/dev/load-samples — force reload the two test emails
    • GET /q/health

    Example curl from project root:

    curl -X POST --data-binary @'src/main/resources/dmarc-samples/[Preview] Report Domain kofoed.dk Submitter enterprise.protection.outlook.com Report-ID 01ab5df40e9b49c0a8ce3574417074cc.eml' \
         -H 'Content-Type: application/octet-stream' \
         http://localhost:8080/api/ingest
    

Scheduled Job vs. Trigger on Arrival

Scheduled (pull): Set in application.properties (or env):

dmarc.mail.enabled=true
dmarc.mail.host=imap.yourcompany.com
dmarc.mail.user=dmarc-reports@...
dmarc.mail.password=${DMARC_MAIL_PASSWORD}
dmarc.mail.poll-cron=0 */10 * * * ?
# optional: dmarc.mail.processed-folder=Processed

The poller looks for subjects containing "Report domain" / "Report Domain" and ingests them. Processed messages are either moved or marked SEEN.

Trigger on arrival (push / webhook):

  • Many mail systems (Google Workspace, Microsoft 365, self-hosted) can deliver a copy of the DMARC report email via HTTP POST of the raw message.
  • Point the webhook at POST http://your-host/api/ingest with Content-Type: application/octet-stream (body = full RFC822 email).
  • This gives near real-time ingestion without polling.

Both modes are supported simultaneously.

Production (PostgreSQL)

  • Use %prod profile (or set QUARKUS_PROFILE=prod)
  • Configure datasource (or env vars QUARKUS_DATASOURCE_*)
  • Set dmarc.mail.* as needed
  • Run Flyway migrations automatically (quarkus.flyway.migrate-at-start=true)

Example env for container:

QUARKUS_DATASOURCE_JDBC_URL=jdbc:postgresql://db:5432/dmarcstats
QUARKUS_DATASOURCE_USERNAME=dmarc
QUARKUS_DATASOURCE_PASSWORD=...
DMARC_MAIL_ENABLED=true
...

From project root:

docker compose up --build
  • Postgres on 5432
  • App on 8080 (waits for DB) — dashboard at http://localhost:8080
  • Grafana on 3000 (admin/admin)

Grafana will automatically have:

  • "DMARC Postgres" datasource
  • "DMARC Overview" dashboard (import uid dmarc-overview if needed)

You can also point any Grafana at the Postgres dmarc_report + dmarc_record tables or the dmarc_stats view.

Database Schema

  • dmarc_report: one row per received aggregate report (deduped by org_name + report_id)
  • dmarc_record: the individual <record> rows (source IP, count, policy disposition, dkim/spf results, identifiers)
  • dmarc_stats: convenience view joining the two

Common queries for Grafana / BI:

  • Volume by day / by reporter
  • Pass vs fail rates
  • Top source IPs causing issues
  • Policy enforcement (disposition) trends

Development Notes

  • To add more parsers or fields: look in parser/EmailParser.java + DmarcXmlParser.java
  • Idempotency is based on (org_name, report_id) — re-sending the same report is a no-op
  • Quarkus dev mode: live reload on Java + resources changes
  • Tests live in src/test/...

For AI agents and future contributors: please read AGENTS.md first. It captures the non-obvious packaging, Flyway/H2, sequence naming, and sample-loading decisions made during the original implementation.

Adding Tests / More Samples

Copy additional .eml files into src/test/resources/dmarc-samples/ (and src/main/resources/dmarc-samples/ if they should ship with the app) and they can be loaded via the dev endpoint or tests.

License

MIT or whatever you prefer for internal tooling.