A self-hosted dashboard that reads DMARC aggregate reports from a Microsoft 365 mailbox via the Microsoft Graph API and visualises results with interactive charts.
- Reads DMARC report emails (
.xml,.xml.gz,.zip) automatically from an Office 365 mailbox - Parses RFC 7489 aggregate reports and stores them in a local SQLite database
- Interactive dashboard — pass/fail rate, daily volume trend, failure breakdown, top IPs and organisations
- Multi-tenant support with per-tenant Microsoft SSO login
- Background scheduler with global and per-tenant fetch intervals
- Manual "Fetch now" button
- DMARC DNS health checks including RFC 7489 §7.1 cross-domain
ruaauthorisation - Two-factor authentication for the local admin account
- Glassmorphism UI with customisable colour themes
Choose the method that fits your setup:
| Method | Best for |
|---|---|
| Docker CLI | Linux servers, NAS devices, quick installs |
| Portainer | Portainer users who prefer a web UI |
| Node.js | Development or servers without Docker |
Session secret — a cryptographically random secret is auto-generated on first start and saved to the persistent data volume. No manual setup required.
mkdir dmarc-dashboard && cd dmarc-dashboard
curl -O https://raw.githubusercontent.com/Mischa323/dmarc-dashboard/master/docker-compose.ymlPORT=3443Skip this step to use the default port 3443.
docker compose up -dDocker pulls the pre-built image from GHCR — no compilation needed.
Go to https://<your-host>:3443 and follow the 3-step wizard to create the admin account and connect your first Microsoft 365 tenant.
Self-signed certificate — the dashboard generates a self-signed TLS certificate on first start. Your browser will warn you; add a permanent exception or put the container behind a reverse proxy.
docker compose pull && docker compose up -dAdd the following service to docker-compose.yml to have Watchtower automatically pull and restart the container whenever a new image is published:
watchtower:
image: containrrr/watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
WATCHTOWER_LABEL_ENABLE: "true"
WATCHTOWER_CLEANUP: "true"
WATCHTOWER_POLL_INTERVAL: "86400" # seconds — 86400 = every 24 hours
command: --label-enableThe dmarc-dashboard service already has the watchtower.enable=true label, so Watchtower will only manage this container.
Set HTTP_MODE=1 in .env to listen on plain HTTP (for nginx / Traefik terminating TLS):
PORT=3000
HTTP_MODE=1In Portainer, go to Stacks → Add stack.
Enter a name such as dmarc-dashboard.
Copy the entire block below and paste it into the Web editor:
services:
dmarc-dashboard:
image: ghcr.io/mischa323/dmarc-dashboard:latest
restart: unless-stopped
ports:
- "3443:3443"
volumes:
- dmarc_data:/data
environment:
PORT: "3443"
DATABASE_URL: "/data/dmarc.db"
CERTS_DIR: "/data/certs"
# Uncomment the line below when running behind a reverse proxy (nginx/Traefik):
# HTTP_MODE: "1"
labels:
- "com.centurylinklabs.watchtower.enable=true"
volumes:
dmarc_data:To use a different port, change both
3443:3443andPORT: "3443"to the same value.
Click Deploy the stack. Portainer pulls the image, creates the dmarc_data volume, and starts the container.
Go to https://<your-host>:3443 and follow the 3-step wizard to create the admin account and connect your first Microsoft 365 tenant.
Go to Stacks → dmarc-dashboard, click Pull and redeploy. The dmarc_data volume is preserved.
Add the Watchtower service to your stack in the Web editor so it sits alongside dmarc-dashboard:
watchtower:
image: containrrr/watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
WATCHTOWER_LABEL_ENABLE: "true"
WATCHTOWER_CLEANUP: "true"
WATCHTOWER_POLL_INTERVAL: "86400" # seconds — 86400 = every 24 hours
command: --label-enableThen click Update the stack. Watchtower will check for a new image every 24 hours and restart the container automatically.
Change the check interval:
| Frequency | WATCHTOWER_POLL_INTERVAL |
|---|---|
| Every hour | 3600 |
| Every 6 hours | 21600 |
| Every 24 hours | 86400 |
- Node.js 20+
- A Microsoft Entra (Azure AD) app registration
# 1. Clone and install
git clone https://github.com/Mischa323/dmarc-dashboard.git
cd dmarc-dashboard
npm install
# 2. Start — .env and a self-signed TLS certificate are generated automatically
node server.js
# 3. Open https://localhost:3443 and follow the setup wizardTo use a different port:
PORT=8443 node server.jsOr add PORT=8443 to the .env file generated on first run.
- Go to Entra admin centre → App registrations → New registration
- Name it (e.g.
DMARC Dashboard), single-tenant, no redirect URI needed for mail fetching - API permissions → Add → Microsoft Graph → Application permissions
Mail.Read— to read report emailsUser.Read.All— required for SSO user lookup (optional if SSO is not used)
- Grant admin consent
- Certificates & secrets → New client secret — copy the value immediately
- Note the Tenant ID and Application (client) ID from the Overview page
- Enter these values in the dashboard's tenant configuration screen
The app uses client-credentials flow — no interactive user login required for mail fetching.
| Variable | Description | Default |
|---|---|---|
PORT |
Listening port | 3443 |
SECRET |
Session secret | auto-generated, saved to /data/.secret |
DATABASE_URL |
Path to the SQLite database | dmarc.db / /data/dmarc.db |
CERTS_DIR |
Directory for TLS cert and key | ./certs / /data/certs |
HTTP_MODE |
Set to 1 for plain HTTP (reverse proxy mode) |
— |
Tenant credentials (Tenant ID, Client ID, Client Secret, mailbox) are stored in the database and managed via the Admin UI — not environment variables.
Point your domain's rua tag to the configured mailbox:
_dmarc.yourdomain.com TXT "v=DMARC1; p=reject; rua=mailto:dmarc@yourdomain.com"
If the reporting mailbox is on a different domain than the monitored domain, add an authorisation record (RFC 7489 §7.1):
yourdomain.com._report._dmarc.mailboxdomain.com TXT "v=DMARC1;"
The dashboard's DNS health check detects missing authorisation records and shows the exact record to add.
dmarc-dashboard/
├── server.js Entry point — starts HTTPS/HTTP server
├── src/
│ ├── app.js Express app factory
│ ├── config.js Environment config, secret and .env generation
│ ├── db.js SQLite schema, migrations, helpers
│ ├── dmarcParser.js RFC 7489 XML parser
│ ├── fetcher.js Fetch and persist reports
│ ├── graphClient.js Microsoft Graph API client
│ ├── msalHelper.js MSAL token acquisition
│ ├── scheduler.js Background fetch scheduler
│ ├── tenantTest.js DNS and connectivity health checks
│ └── routes/
│ ├── admin.js Admin UI routes
│ ├── api.js JSON API for the dashboard
│ ├── auth.js Local login, SSO, 2FA
│ ├── main.js Dashboard and reports pages
│ └── setup.js First-run setup wizard
├── views/ EJS templates
├── Dockerfile
├── docker-compose.yml
└── .env.example
MIT