Scheduled reports
The report delivery service - report types, cadences, formats, subscriptions, and send-now.
Scheduled reports
The report service replaces Looker’s ~140 scheduled plans. It runs inside the data-plane Worker: a cron wakes every 5 minutes, finds due subscriptions, renders each report from the same data the dashboards use, and emails it via Resend as reports@joe.coffee.
Design properties worth knowing:
- Idempotent. Each send is claimed against a period key (the business date or month), so a retry after a failure cannot double-send.
- No-data suppression. A report with nothing to say (no payouts on a Sunday, no orders yesterday) sends nothing rather than an empty email.
- Branded and linked. Every email uses the shared Joe layout; merchant reports carry a “View live dashboard” button backed by a 90-day, shop-locked
/e/link. Clicking it mints a fresh short-lived Metabase token, so the button no longer inherits the old seven-day embed-token window. Internal reports still link directly into Metabase. - Formats: inline HTML, CSV, XLSX, rendered dashboard PDF (via the joe-render service), and webhooks. Every format Looker used is supported.
Report types
| Type | Cadence | Contents |
|---|---|---|
weekly_sales |
Fridays | Branded email: net sales, orders, average ticket, tips, week-over-week, top items |
weekly_sales_csv |
Fridays | Same week as CSV + XLSX attachments |
shop_sales_pdf |
Fridays | The shop’s live dashboard rendered to a branded PDF |
daily_snapshot |
Daily | Yesterday vs same weekday prior week, top items |
daily_orders_csv |
Daily at each shop’s close | Order-level CSV of today (live path) |
monthly_orders_csv |
1st of month | Last complete month, order counts by store/day/source |
payout_arrivals |
Daily (banking days) | Payouts arriving tomorrow |
ladro_iif_daily_sales_csv |
Daily | Caffe Ladro QuickBooks IIF daily sales journal |
ladro_iif_transfers_csv |
Daily | Caffe Ladro QuickBooks bank-transfer entries (byte-parity with Looker verified) |
New types are added as small code packages (see Requesting a new report).
Cadence model
Three scheduling shapes cover the whole Looker inventory:
- Fixed-hour (default 13:00 UTC) - weekly and daily reports without a specific hour.
- Per-shop send hour (
send_hour_utcon the subscription) - the 5-minute cron gates on the current UTC hour; used for “at store close” reports. - Monthly (
monthly_1st) - fires on the 1st, keyed by month.
Managing subscriptions
Admin page: https://data-plane.joe.coffee/v1/reports/admin (admin token required).
A subscription = shop + report type + cadence + recipients (+ optional send hour). Create, deactivate, and inspect from the admin page, or via the API:
GET /v1/reports/subscriptions list
POST /v1/reports/subscriptions create {shopId, reportType, recipients, sendHourUtc?}
POST /v1/reports/subscriptions/:id/deactivate
Send-log entries record every delivery (or suppression) per subscription per period, which is what the dual-send soak reads to compare against Looker.
Send-now (test a report immediately)
POST /v1/reports/send-now
{"shopId": "...", "reportType": "weekly_sales", "recipients": ["you@joe.coffee"]}
Renders and sends immediately with current data; repeatable (send-now runs are not idempotency-logged). This is the fastest way to check a report against Looker’s version of the same day.
Two operational notes: fire send-nows one at a time (a rapid burst can contend on the Worker’s DB pool), and the first PDF render after idle can take ~20 extra seconds while the render machine wakes.
Failure handling
Failed sends surface in the send log with the error; the claim design means fixing the cause and re-running never double-delivers. If Resend is misconfigured (missing key), the service refuses to claim periods at all, so nothing is silently marked sent.