On this page

Scheduled reports

Run a query on a schedule and send the result by e-mail (a table and a CSV file), to Slack or to a webhook.

A scheduled report runs one of your queries on a schedule and sends the result where your team looks: an e-mail with the rows as a table and as a CSV file, a Slack channel, or a webhook of your own. Use it for a daily sign-up count, open orders every morning, a weekly revenue summary for finance, or an alert that only goes out when a query finds something (orders stuck for an hour, failed payments).

  • Reports only read: the query runs in a read-only transaction, so a report can never change data.
  • Schedules run in the time zone you choose, so “every weekday at 9:00” stays at 9:00 when the clocks change.
  • Every run is kept in the report's history with how many rows it found and what each destination answered.

#Create a report

In Studio open Integrations › Scheduled reports › New report, or start from the SQL editor: write the query, open the menu next to Run and choose Schedule as report…. Then:

  1. Query: one SELECT (or WITH … SELECT, VALUES, TABLE). Preview shows the first 20 rows.
  2. Schedule: every hour, every day, Monday to Friday, every week, every month, or a cron expression, and a time zone. The next runs are shown as you change it.
  3. Send to: up to 5 destinations, e-mail (up to 10 addresses each), Slack or a webhook.
  4. Create report. Use Run now to send it once at once, or Send test on one destination.
SQL
-- sign-ups in the last 24 hours, by provider
select coalesce(raw_app_meta_data->>'provider', 'email') as provider, count(*) as signups
from auth.users
where created_at > now() - interval '24 hours'
group by 1 order by 2 desc

#Schedules

A custom schedule is a five-field cron expression: minute hour day-of-month month day-of-week. Lists (1,15), ranges (1-5), steps (*/30) and names (mon, jan) work, and @daily, @weekly, @monthly. As in cron, when both day fields are set a day matching either one runs.

CronRuns
0 9 * * 1-5Monday to Friday at 09:00
30 7 * * monMondays at 07:30
0 8 1 * *On the 1st of every month at 08:00
0 */6 * * *Every 6 hours

Your plan sets how often a report can run (see the limits below). On the day clocks change, a time that does not exist (02:30 when clocks jump forward) is skipped.

#What gets sent

#E-mail

The message shows the report's name and description, how many rows the query returned and the first 50 rows as a table; the attached CSV has every row (up to 10 MB). Choose Table in the message only or CSV file only under E-mail format. On the Free plan reports go only to members of the project's team; on paid plans to any address.

#Slack

In Slack add an Incoming Webhook to the channel (Slack › Apps › Incoming WebHooks) and paste its URL (https://hooks.slack.com/services/…). The channel gets the row count and the first 20 rows as a table, with a link to the report. Slack webhooks cannot carry files; add an e-mail or a webhook for the full result.

#Webhook

A POST to your HTTPS URL with every row, as JSON or as CSV, signed like the database webhooks: check StitchDB-Signature (v1= HMAC-SHA256 of <timestamp>.<body> with the signing secret shown on the destination) and StitchDB-Webhook-Timestamp.

http
POST /hooks/report
StitchDB-Event: sql_report.run
StitchDB-Report-Id: 5f0c…   StitchDB-Run-Id: 812   StitchDB-Row-Count: 2

{
  "type": "sql_report.run",
  "report_id": "5f0c…", "report": "Daily sign-ups", "run_id": "812", "project_ref": "<ref>",
  "ran_at": "2026-10-26T08:00:00.000Z", "row_count": 2, "truncated": false,
  "columns": ["provider", "signups"],
  "rows": [{ "provider": "google", "signups": 41 }, { "provider": "email", "signups": 17 }]
}

A destination that answers 5xx or 429 is tried 3 times. With the CSV body the headers are the same and the body is the CSV file.

#Alerts: only when there are rows

Tick Only send when the query returns rows and write the query so it finds the problem; the report stays quiet until it does:

SQL
select id, customer_id, total, created_at
from orders
where status = 'pending' and created_at < now() - interval '1 hour'

Runs with no rows are recorded as No rows, not sent. Run now and Send test always send.

#Runs and failures

The report page lists its last runs: when, whether it was scheduled, run now or a test, the rows, how long it took and what each destination answered. A query that fails (a renamed column, a timeout) is recorded with the error. After 5 failed runs in a row the report is turned off and the project owner gets an e-mail; fix it and turn it on again. A run that was missed while the platform was restarting runs once when it is back.

#Limits

A run reads at most your plan's rows per report (more are cut, and the message says so) and stops after 60 seconds.

Report e-mails have a monthly allowance per project (one e-mail per address). When it is used, e-mail destinations are skipped until the 1st of next month (UTC) and the run says so; Slack and webhook destinations keep delivering, and a skipped e-mail never counts as a failure. Scheduled reports shows how many of this month's e-mails are used.

LimitFreeProScaleEnterprise
Scheduled SQL reports per project225100100
Scheduled SQL reports: shortest interval1,440 min60 min15 min15 min
Scheduled SQL reports: rows per report1,00010,00050,000100,000
Scheduled report e-mails per month (per project)3004406601,100