Hello PMG Admins,
I've been running PMG for a while, and the one thing that always bugged me was managing the spam quarantine from my phone. The admin interface works fine, but it's not built for a small screen - and there are days I need to check something quickly without sitting down at my computer.
It's grown a fair bit since I first posted this, so here's the full picture.
It's a single Docker container, talks to PMG over the API only - doesn't touch the appliance itself. Runs alongside PMG, not on it.
Features
Live demo
https://pmg-quarantine-demo.mcs.net.tr/
Login: demo / demo
Runs against an in-memory fake PMG, not a real server - every screen works, quarantine actions really mutate the mock data, and it resets whenever the container restarts. Separate, isolated instance, has nothing to do with any real deployment.
Security notes
Tech stack
Node.js/Express backend (auth + PMG API proxy, since the PMG API has no CORS and the ticket/CSRF flow needs to stay server-side), React + Tailwind frontend, single multi-stage Dockerfile.
MIT licensed, open source:
https://github.com/ikrsdo/pmg-quarantine-admin
Feedback welcome, especially if you're running PMG and this is missing something you'd want.
I've been running PMG for a while, and the one thing that always bugged me was managing the spam quarantine from my phone. The admin interface works fine, but it's not built for a small screen - and there are days I need to check something quickly without sitting down at my computer.
It's grown a fair bit since I first posted this, so here's the full picture.
It's a single Docker container, talks to PMG over the API only - doesn't touch the appliance itself. Runs alongside PMG, not on it.
Features
- Dashboard - 7-day overview on login: quarantine volume, delivery status, top senders/receivers
- Quarantine management - all three types (Spam, Virus, Attachment), list/search/filter, deliver/whitelist/block, swipe-able cards and multi-select bulk actions on mobile, dense table on desktop. The block action requires confirmation.
- Tracking Center - read-only lookup by sender/recipient/status. Each entry's syslog trail is shown as a structured, expandable Message Events timeline (Received/Processed/Queued/Policy Match/Delivered/etc.) instead of raw log lines.
- Cross-linking between a Quarantine message and its best-effort matching Tracking Center entry, and back - the two PMG APIs share no common identifier, so this is sender/recipient + a time window, not guaranteed.
- Saved filter presets and CSV export on both list pages.
- Per-admin PMG login (Help Desk role) - no shared service account, PMG's own audit log attributes actions to the right admin.
- Dark/light theme, installable as a PWA.
- Update-check banner - notifies in-app when a newer release is on GitHub, dismissible per version.
- Demo mode - see below.
Live demo
https://pmg-quarantine-demo.mcs.net.tr/
Login: demo / demo
Runs against an in-memory fake PMG, not a real server - every screen works, quarantine actions really mutate the mock data, and it resets whenever the container restarts. Separate, isolated instance, has nothing to do with any real deployment.
Security notes
- No PMG credentials are ever stored, in .env, on disk, or in the browser - only the short-lived session ticket, kept server-side.
- /api/login is rate-limited against credential brute-forcing.
- Every quarantine action is checked against a fixed whitelist before being forwarded to PMG - the UI only exposes deliver/whitelist/block today, the whitelist itself covers the full set of valid PMG actions.
- Security headers set on every response (Helmet), Docker image runs as non-root, dependencies audited before each release.
- CSV export is sanitized against formula/CSV injection.
Tech stack
Node.js/Express backend (auth + PMG API proxy, since the PMG API has no CORS and the ticket/CSRF flow needs to stay server-side), React + Tailwind frontend, single multi-stage Dockerfile.
MIT licensed, open source:
https://github.com/ikrsdo/pmg-quarantine-admin
Feedback welcome, especially if you're running PMG and this is missing something you'd want.
Last edited: