Scheduled Reports
A scheduled report runs a saved query or summarizes a dashboard on a cron and delivers the result to one or more of your alert channels: SMTP, Resend or Brevo email, and webhooks in generic JSON, Slack or Teams format. Reports are separate from scheduled query jobs: a job only runs SQL, a report sends its result to people.
Available on Pro and Enterprise plans. Find reports under Schedules → Report delivery; the API lives at /api/reports and returns 402 without a license.
Before you start
Section titled “Before you start”- A dedicated report account. Every run, including Send now and Preview, uses the connection’s
reportbackground account. Reports never borrow a signed-in session: without a dedicated account the run fails and nothing is sent. Give the account SELECT on the tables your reported queries and dashboard panels read. - At least one alert channel. An admin creates channels under Governance → Alert channels. Reports reuse them; there is nothing new to configure.
Create a report
Section titled “Create a report”Click New report in the Report delivery section, Subscribe in the header of a dashboard to start with that dashboard, its current time range and its variable values filled in, or Subscribe in a saved query’s menu to start with that query and its connection.
| Field | What it does |
|---|---|
| Source | A saved query result, or a dashboard summary |
| Connection | The connection the queries run on, with its report account |
| Cron and time zone | Five field cron (0 8 * * 1-5 is weekdays at 08:00), evaluated in the IANA time zone you enter |
| Format | Saved queries only: CSV attachment, inline table, or both |
| Row cap | Rows fetched, 1000 by default, at most 50,000 |
| Inline rows | Rows shown in the message body and in webhooks, 50 by default, at most 200 |
| Attachment size | CSV size cap, 1 MiB by default, at most 5 MiB |
| Timeout | 60 seconds by default, 5 to 300 |
| Subject | Optional, with {{name}}, {{date}} and {{rows}}. Defaults to [CH-UI] Report: <name> |
| Deliver to | Up to 5 channels; email channels take up to 20 recipients each |
| Skip when empty | No rows means nothing is sent and the run is recorded as skipped |
A saved query with bind parameters ({name:Type}) needs a saved default for every parameter. Saving a report on a query that lacks one is refused, since nobody is there to fill it in at 08:00.
Preview renders the message with the report account and sends nothing. Send now runs the report at once and delivers it to every channel.
What recipients get
Section titled “What recipients get”Saved query, email: the body states the row count and any truncation, then (inline or both) a table of the first inline rows, as HTML with a plain text alternative. The CSV is attached as <name>-<yyyymmdd-hhmm>.csv.
Dashboard, email: a text summary of up to 25 query panels in grid order, for a fixed range (the last 24 hours unless you pick another) and the saved variable values, or each variable’s default:
| Panel | Summary |
|---|---|
| Stat, gauge | The value, using the panel’s calculation (last by default) |
| Table | The first 20 rows |
| Time series, bar | Last, minimum and maximum per series |
| Pie | The 10 largest slices with their share |
| Histogram | Count, minimum, median, mean and maximum of the column |
| Scatter | The number of points and the correlation of X and Y |
| Heatmap | The number of cells and the 5 largest |
| Text | Skipped |
The summary ends with a link to the dashboard when app_url is configured. There is no image of the dashboard.
Webhooks: generic webhooks get JSON with event_type: "report.delivered", columns, rows (up to the inline row count), row_count, truncated and link. Slack gets a code block table capped at about 3,500 characters plus a link; Teams gets a card of monospace lines. Nothing is uploaded to Slack or Teams.
Truncation is always stated in the message: when the query has more rows than the cap, when the CSV stops early to stay under the size cap, and when the inline table shows fewer rows than the result.
CSV cells that start with =, +, - or @ get a leading apostrophe so spreadsheets do not run them as formulas (plain numbers such as -5 are left alone). Text that is not valid UTF-8 is replaced with the Unicode replacement character.
How runs work
Section titled “How runs work”- Every query runs with
readonly=1,max_execution_time,max_result_rowsandmax_result_byteswithresult_overflow_mode=break, andlog_comment=ch-ui:report, so report queries are easy to find insystem.query_log. Governance guardrails are evaluated first, as for SQL alerts; a blocked query fails the run. - At most 3 report runs execute at once, scheduled and manual together. Send now answers
429when all are busy. - A run renders one snapshot. Each channel is sent that snapshot; a failed send is retried up to 5 times (after 1, 5, 15 and 30 minutes) from the same snapshot, never by running the query again.
- If the query fails, nothing is sent, the run is recorded as failed and a
report.failedalert event goes through your alert rules (fingerprintreport:<id>:error). The next attempt is the next slot. - After 10 failed runs in a row, the report turns itself off and raises
report.disabled. Saving it with Enabled on again resets the count. - While no Pro license is active the runner pauses. Slots missed during the pause are skipped, not sent late.
Delivery history
Section titled “Delivery history”Click a report, or its history button, to see its latest 100 runs with the status per channel and the number of tries. Download returns the CSV a run sent. Stored snapshots are cleared 7 days after a run finishes; history rows are kept for 90 days.
Who can do what
Section titled “Who can do what”| Action | Roles |
|---|---|
| See reports and their history, download CSV | Every signed-in user |
| Create, preview, send now | Admin, analyst |
| Edit or delete | The creator or an admin |
| Set recipient domains | Admin |
Recipient domains: an admin can limit report recipients to a list of email domains with the settings button on the Report delivery toolbar (setting report_recipient_domains). Analysts are then refused any address outside the list; admins are not limited. With no list, any address is accepted.
| Method | Path | Purpose |
|---|---|---|
GET |
/api/reports |
Reports, the channels they can use, the report account mode per connection |
POST |
/api/reports |
Create |
PUT |
/api/reports/{id} |
Replace a report |
DELETE |
/api/reports/{id} |
Delete a report and its history |
POST |
/api/reports/{id}/preview |
Render without sending |
POST |
/api/reports/{id}/send |
Run and send now |
GET |
/api/reports/{id}/deliveries |
Run history |
GET |
/api/reports/deliveries/{id}/download |
The CSV of one run |
GET / PUT |
/api/reports/settings |
Recipient domains (PUT is admin only) |
{ "name": "Daily signups", "source_type": "saved_query", "saved_query_id": "<saved query id>", "connection_id": "<connection id>", "cron": "0 8 * * 1-5", "timezone": "Europe/Madrid", "format": "both", "targets": [{ "channel_id": "<channel id>", "recipients": ["growth@example.com"] }]}Audit trail
Section titled “Audit trail”The Audit Log records report.created, report.updated, report.deleted, report.sent_manual, report.delivered (with the recipient count and rows), report.auto_disabled and report.settings_updated, plus report.credential_use when the runner picks up the dedicated account.
