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:
- Query: one
SELECT(orWITH … SELECT,VALUES,TABLE). Preview shows the first 20 rows. - 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.
- Send to: up to 5 destinations, e-mail (up to 10 addresses each), Slack or a webhook.
- Create report. Use Run now to send it once at once, or Send test on one destination.
-- 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.
| Cron | Runs |
|---|---|
0 9 * * 1-5 | Monday to Friday at 09:00 |
30 7 * * mon | Mondays 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
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.
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:
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.
| Limit | Free | Pro | Scale | Enterprise |
|---|---|---|---|---|
| Scheduled SQL reports per project | 2 | 25 | 100 | 100 |
| Scheduled SQL reports: shortest interval | 1,440 min | 60 min | 15 min | 15 min |
| Scheduled SQL reports: rows per report | 1,000 | 10,000 | 50,000 | 100,000 |
| Scheduled report e-mails per month (per project) | 300 | 440 | 660 | 1,100 |