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.readfor view + PDF export.reports.writefor 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:
- Lists active tenants from master.
- Skips tenants this worker doesn't own (rendezvous-hash ownership via
TenantJobSupervisor.ownsTenantId— multi-instance safe). - Resolves each owned tenant's Knex via
TenantConnectionResolver.get. - Asks
DashboardEmailScheduleService.listDue(now)for rows whose cron-next-fire (anchored onlast_fired_atorcreated_at) has elapsed. - For each due row: build report → render PDF → send via
SmtpDashboardMailer. Stamplast_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
| Variable | Default | Notes |
|---|---|---|
DASHBOARD_EMAIL_ENABLED | false | Must be true to start the scheduler. |
DASHBOARD_EMAIL_INTERVAL_MS | 60000 | Dispatch cadence (clamp 15s–30min). |
RENDER_PDF_STUB | false | Swap Playwright for stub renderer. |
MAIL_FROM | — | Required when enabled. e.g. reports@medsuite.com. |
MAIL_SMTP_HOST | — | Required when enabled. SMTP relay host. |
MAIL_SMTP_PORT | 587 | STARTTLS on 587, implicit TLS on 465. |
MAIL_SMTP_USER | — | Optional SMTP auth. |
MAIL_SMTP_PASS | — | Optional SMTP auth. |
MAIL_SMTP_SECURE | false | Force 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
| Symptom | Action |
|---|---|
| Schedule not firing | Check 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 consistently | Confirm 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 startup | The 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
| Check | Expected |
|---|---|
| Manual PDF export | 200 with PDF |
| Schedule row created | Visible in user's dashboard settings |
last_fired_at advances on cron anchor | Yes |
last_status='SUCCESS' after delivery | Yes |
Cross-references
- Refresh reporting views — feeds the dashboard.
- Drill-through & per-widget RBAC for click behavior.
- Payer contract registry shares the same SMTP relay.