Scheduled reports

Scheduled reports are recurring data exports that Distribu generates and emails on a fixed cadence. Four report types, three cadences, any number of recipients per report.

Manage them at /dashboard/reports/scheduled.

The four report types

TypeWhat it contains
SALESDaily revenue totals over the configured range: orders placed, gross revenue, average order value, broken down by status.
CUSTOMER_SPENDPer-customer spend summary: total orders, total revenue, last order date. One row per customer with activity in the range.
INVENTORY_VALUATIONCurrent stock × current price per product — a snapshot of inventory value at the moment the report runs.
ORDERS_EXPORTThe full orders table over the range. Two modes: summary (one row per order) or line-items (one row per line item).

Output format

Each report is generated as either CSV or PDF based on the format field. CSV is the default — pick PDF when you want something you can hand to a colleague without further processing.

ORDERS_EXPORT also has a mode field that's only meaningful for orders export:

  • summary — one row per order.
  • line-items — one row per line item.

Cadence

CadenceFiresExtra config
DAILYOnce per day at the configured time
WEEKLYOnce per weekdayOfWeek (1 = Mon … 7 = Sun)
MONTHLYOnce per monthdayOfMonth (1–28)

Monthly reports are capped at day 28 so the report always fires — no missed runs in February or on short months.

Recipients

A report has a list of recipient email addresses. Any number — usually one or two (you and the owner of a downstream system), but there's no enforced cap.

Recipients don't need to be Distribu users. Send reports to your accountant, your ERP's mailbox, your team Slack's email-bridge, whatever.

Creating a report

From /dashboard/reports/scheduled, click + New scheduled report. The form collects:

FieldTypeNotes
reportTypeenumOne of the four types above.
formatcsv / pdfDefault csv.
modesummary / line-itemsRequired for ORDERS_EXPORT; ignored otherwise.
cadenceenumDAILY, WEEKLY, MONTHLY.
dayOfWeekinteger 1–7Required for WEEKLY.
dayOfMonthinteger 1–28Required for MONTHLY.
recipientsstring[]At least one valid email address.
isActivebooleanDefaults to true.

On save, nextRunAt is computed forward from now based on the cadence.

Cron dispatch

Reports are fired by a daily cron at GET /api/cron/scheduled-reports:

0 0 * * *     # daily at 00:00 UTC

The cron loads every active report with nextRunAt <= now and dispatches each one:

  1. Generate the report output (buildReportOutput → CSV bytes or PDF bytes, filename, content type).
  2. Email the output as an attachment to the recipient list, with a branded Distribu email shell and a short summary in the body.
  3. On success: set lastRunAt = now, lastRunStatus = "ok", failureCount = 0, and recompute nextRunAt forward.
  4. On failure: set lastRunStatus = "failed", store the error on lastRunError, increment failureCount, and fire a SCHEDULED_REPORT_FAILED notification to every OWNER / ADMIN on the company.

Audit entries are written in both cases: ScheduledReportSent on success, ScheduledReportFailed on failure.

Anatomy of a scheduled report

FieldTypeNotes
idstringPrimary key.
reportTypeenumSALES, CUSTOMER_SPEND, INVENTORY_VALUATION, ORDERS_EXPORT.
cadenceenumDAILY, WEEKLY, MONTHLY.
formatstring"csv" or "pdf".
modestring, optional"summary" or "line-items"ORDERS_EXPORT only.
dayOfWeekint, optional1–7 for WEEKLY.
dayOfMonthint, optional1–28 for MONTHLY.
recipientsstring[]Destination email addresses.
isActivebooleanPaused reports are skipped by the cron.
nextRunAttimestampWhen the next dispatch will fire.
lastRunAttimestamp, optionalWhen the last dispatch ran.
lastRunStatusstring, optional"ok" or "failed".
lastRunErrorstring, optionalError message on the last failure.
failureCountintConsecutive failures. Resets to 0 on success.
createdByIdreference, optionalThe staff member who created it.

Failure handling

If a dispatch fails (email provider rejects, PDF renderer errors, etc.), Distribu doesn't immediately retry — the next attempt is on the next scheduled run. A persistent failure means:

  • Each run increments failureCount and re-fires SCHEDULED_REPORT_FAILED to OWNER + ADMIN.
  • The report stays isActive = true until a human disables it.
  • The error message is visible on the report detail page — use it to debug (bad email address, Resend config, etc.).

There's no auto-disable after N failures today. If you need that, keep an eye on the notification stream.

Pausing or deleting

From the report detail page:

  • Pause flips isActive to false. nextRunAt is preserved; when you resume, scheduling continues from where it was (or the next valid future time).
  • Delete is permanent — the report row and its history go with it.

Manual runs

The scheduled-report system only fires on its cron schedule. For one-off report generation, use the on-demand report pages under /dashboard/reports (sales, customer spend, inventory valuation) — these render immediately and offer a Download CSV / PDF button without any scheduling.

What's not here (yet)

  • Custom date ranges. Report windows are fixed relative to the run time (e.g., daily = "yesterday"; weekly = "last 7 days"). No "report the last 14 days every Monday" today.
  • Custom column picking. Each report type's columns are fixed.
  • Report templates or presets. You configure each report from scratch.
  • Webhooks on report completion. Reports fire audit entries and (on failure) notifications, but don't currently post to a webhook.

Related: