Skip to main content

Dashboard reporting and scheduled email delivery

Outcome

Dashboards reflect current data via the materialized views; users can export PDFs on demand and subscribe to scheduled email delivery on a saved view.

Prerequisites

  • reports.read for view + PDF export.
  • reports.write for manual refresh.
  • SMTP env vars set when scheduled email is enabled.

On-demand PDF export

GET /api/v1/ledger/dashboard/export/pdf?viewId=<uuid> renders a saved view and streams a PDF. Any reports.read JWT can call it.

PlaywrightPdfRenderer loads playwright-core lazily and opens Chromium. Failures surface the underlying error. Setting RENDER_PDF_STUB=true swaps in the stub renderer (useful for containers without a browser install).

Scheduled email delivery

User-owned rows in security.dashboard_email_schedule pair a dashboard_view with a cron expression, a subject, and up to 20 recipients.

The DashboardEmailScheduler (process-level loop) ticks every DASHBOARD_EMAIL_INTERVAL_MS (default 60 s). Each tick:

  1. Lists active tenants from master.
  2. Skips tenants this worker doesn't own (rendezvous-hash ownership via TenantJobSupervisor.ownsTenantId — multi-instance safe).
  3. Resolves each owned tenant's Knex via TenantConnectionResolver.get.
  4. Asks DashboardEmailScheduleService.listDue(now) for rows whose cron-next-fire (anchored on last_fired_at or created_at) has elapsed.
  5. For each due row: build report → render PDF → send via SmtpDashboardMailer. Stamp last_fired_at + last_status + last_error.

Cadence control lives on the schedule row — the dispatcher is idempotent against missed ticks. active=false quiesces without deletion. Malformed crons are skipped silently (the row can still be edited; the next write re-validates).

Required environment variables

VariableDefaultNotes
DASHBOARD_EMAIL_ENABLEDfalseMust be true to start the scheduler.
DASHBOARD_EMAIL_INTERVAL_MS60000Dispatch cadence (clamp 15s–30min).
RENDER_PDF_STUBfalseSwap Playwright for stub renderer.
MAIL_FROMRequired when enabled. e.g. reports@medsuite.com.
MAIL_SMTP_HOSTRequired when enabled. SMTP relay host.
MAIL_SMTP_PORT587STARTTLS on 587, implicit TLS on 465.
MAIL_SMTP_USEROptional SMTP auth.
MAIL_SMTP_PASSOptional SMTP auth.
MAIL_SMTP_SECUREfalseForce implicit TLS.

Mailgun cheat sheet: MAIL_SMTP_HOST=smtp.mailgun.org, MAIL_SMTP_PORT=587, MAIL_SMTP_USER=postmaster@<domain>, MAIL_SMTP_PASS=<sending key>.

Operator playbook

SymptomAction
Schedule not firingCheck last_fired_at + last_status on the row. If last_status='ERROR', last_error carries the message (SMTP timeout, PDF render failure). Verify active flag and cron string (cron-parser semantics, UTC).
PDF render fails consistentlyConfirm rcm-core deploy includes Chromium binaries for playwright-core (same toolchain as @playwright/test). As a stopgap, set RENDER_PDF_STUB=true to keep scheduled sends going with placeholder PDFs while you fix the image.
Delivery flood on startupThe scheduler fires any row whose cron anchor has elapsed since the last tick. After a long outage you may see a burst. Pause noisy rows (active=false) or advance last_fired_at manually if necessary.

Validation

CheckExpected
Manual PDF export200 with PDF
Schedule row createdVisible in user's dashboard settings
last_fired_at advances on cron anchorYes
last_status='SUCCESS' after deliveryYes

Cross-references

Next

7.5 — Drill-through & per-widget RBAC