Skip to main content
PathMon

Chapter 20 of 26

Scheduled Reports

Updated Read the full guide

Overview

A scheduled report is a periodic fleet summary that PatchMon renders to HTML (with a CSV attachment) and delivers through one or more notification destinations on a cron schedule. Use them to keep leadership and on-call teams informed about compliance posture, patching throughput, pending updates, and open alerts, without anyone needing to log into the UI.

Scheduled reports are managed under Reporting → Scheduled Reports. They share the same destinations as event-driven notifications, so any email, webhook, or ntfy destination you have already set up can receive reports too.

Permissions

Creating, editing, running, and deleting scheduled reports requires can_manage_notifications. Admins and superadmins bypass the check. Users without the permission do not see the tab.

To include host-group scoping, the user must also have can_view_hosts (so the group picker can populate).

Creating a report

  1. Open Reporting → Scheduled Reports.
  2. Click New report. (Disabled until at least one destination exists. Create one under Notification Destinations.)
  3. Fill in the modal:
Field Notes
Report name Required. Shown in the table and as the email subject prefix.
Schedule Frequency + time of day. See Schedule options.
Sections Which blocks to include in the rendered report. See Report sections.
Deliver to Tick every destination that should receive this report. You can send the same report to multiple destinations.
Scope to host groups Optional. Limit the report's per-host sections to the selected host groups. Leave empty for fleet-wide.
Top rows per section Numeric cap on per-host lists, defaults to 20.
Enabled On by default. Disable to keep the report saved but paused.
  1. Click Create.

After creation, the report appears in the table with its next run time, status badge, and action buttons.

Schedule options

The modal composes a standard five-field cron expression for you, so you rarely see cron syntax directly. Frequencies and the resulting cron:

Frequency Cron produced What it means
Daily M H * * * Every day at the chosen time.
Weekdays (Mon to Fri) M H * * 1-5 Mondays to Fridays at the chosen time.
Weekly M H * * D,D,… The chosen days of the week. Pick one or more via the Mon/Tue/… toggle buttons.
Monthly M H D * * The chosen day of the month (1st, 15th, Last day, or a custom day 1–31).

All schedules evaluate in the server timezone configured in PatchMon settings; the modal labels this next to the time picker. Changes to the server timezone after the report is saved do not automatically re-schedule existing reports. Edit the report and save again to re-evaluate.

The schedule is displayed on the table in plain English ("Daily at 08:00", "Weekdays at 09:30", "15th of month at 06:00"), computed from the underlying cron.

Report sections

Each report is a composition of sections, ticked independently:

Section Content
Executive summary Total hosts, average compliance score, critical hosts, compliant hosts, plus a patching overview (runs, completed, failed, running).
Compliance summary Passed rules, failed rules, critical hosts, hosts with no recent scan.
Recent patch runs Latest patch runs by status with timestamps and target counts.
Hosts / status Host status rollup: offline, stale, active.
Open alerts Currently active alerts grouped by severity.
Hosts by outstanding updates Top hosts sorted by pending updates (respects the Top rows per section cap).
Top outdated security packages Packages with the most hosts needing a security update.

New reports default to Executive summary + Compliance summary + Recent patch runs unless you customise the selection.

Delivering a report

Every tick in Deliver to adds a destination to the report's fan-out. At run-time, PatchMon:

  1. Resolves the destinations (skips disabled ones).
  2. Renders the HTML body and CSV attachment once.
  3. Sends the same payload to each destination in parallel.

For each channel type the payload adapts:

Destination What the recipient sees
Email HTML email rendered inline; CSV attached. Subject contains the report name and timestamp.
Webhook JSON POST with report metadata, a summary, and the HTML body in a field. Use this to fan reports into a downstream system (data warehouse, Google Sheets ingester, etc.).
ntfy Short push notification with a link back to the latest report in the UI. The full HTML does not fit ntfy, so it is summarised.
Internal Alerts A system record under the Alerts tab, useful when you want a run history inside PatchMon without email.

A report's appearance in the Delivery Log uses event_type: scheduled_report. Filter the log by the report's destinations to audit deliveries.

Running a report manually

Click the green Play button in the report's row to run it immediately. The report is queued for instant execution and delivered to the configured destinations.

Manual runs respect the same destination state: disabled destinations are skipped, and rate limits still apply.

Disabled reports show the play button greyed out. Enable the report (or edit and tick Enabled) before running. The button tooltip tells you why it is unavailable.

Editing and deleting

  • Edit reopens the same modal pre-filled with the current schedule, sections, and destinations. Saving re-computes the next run time.
  • Delete removes the report permanently. Past deliveries in the log remain.
  • Enabled switch: edit the report and toggle Enabled in the modal. Disabled reports keep their schedule but do not fire until re-enabled; their next-run time is still displayed.

How scheduling works internally

Scheduled reports are stored in the scheduled_reports table. On create or update, PatchMon computes the next run via the cron expression in the server's timezone and writes it to next_run_at. The scheduler enqueues the report task to asynq at exactly that time, with no background polling loop.

When the task executes, the worker:

  1. Re-reads the report row.
  2. Aborts if it has been disabled since enqueue.
  3. Renders HTML + CSV via the server's report renderer (see internal/notifications/report_render.go).
  4. Fans out to each destination with the same fingerprint + rate-limit + retry semantics as regular notifications.
  5. Updates last_run_at and queues the next occurrence.

Because the schedule is stored as a cron string plus a timezone, daylight-saving transitions are handled by the cron library. Jobs that would fall in a skipped hour are pushed to the next valid slot; jobs repeated in a duplicate hour fire once.

Known limits

  • The scheduled-report pipeline does not attempt full re-delivery of a whole report's fan-out on transient failure. A delivery that fails retries per-destination (up to 5 times via asynq), but the render is not re-done. In practice this means a report either reached each destination successfully (with retries covering transient issues) or ended up in the delivery log as failed for that destination.
  • There is no "skip next run" option. To skip a single run, disable the report before its scheduled time, then re-enable it afterwards.
  • Report templates are not customisable from the UI in 2.0. The rendered HTML layout is fixed; customise by choosing sections and host-group scope. Custom templates are a candidate for a future release.