BytesMap
Automate

Scheduled reports

Send dashboard summaries on a schedule, with explicit delivery controls.

Open Markdown

Open a dashboard and choose Schedules → New schedule. The inline editor keeps the dashboard and sidebar in place. Choose the whole dashboard or selected panels, a summary with an optional PDF or CSV attachment, saved notification channels, and an interval, daily, weekly, or monthly delivery time and time zone. The sidebar Schedules page also manages these same deliveries alongside workflow automation, using the shared editor and scheduler.

There is one delivery path: workspace notification channels. Email is one channel type; schedules no longer accept raw email recipients. A summary-only channel cannot be the sole destination for a PDF/CSV schedule. With mixed destinations, Slack and Teams receive the summary/link and file-capable channels receive the attachment too. Capabilities come from the shared core catalog.

Download sample explicitly runs the selected content for a PDF/CSV export; nothing is sent. Opening, editing, saving, and refreshing schedules only read or write metadata. Send now explicitly queues a delivery using the saved settings, including for a paused schedule. Schedules can be edited, paused, resumed, or deleted. History records each channel's outcome, including partial delivery. An attempt being queued or generated cannot be edited or deleted until it finishes.

Filters and exported data

New schedules start with the dashboard's current filters. Each schedule stores its own filter values. Relative values such as -7d are resolved again at delivery time, using one common time across the dashboard. The export includes both saved filters and the resolved parameters used for each panel. The current dashboard layout and query definitions are read on every run. Whole-dashboard schedules include newly added panels. Selected-panel schedules retain stable panel keys and include only those panels. A removed selection fails before any query runs. If a saved filter has been removed from the dashboard, the report fails until its settings are reviewed.

  • Summary contains a bounded excerpt of result values from up to 12 selected panels (first three rows and four encoded fields, or table columns), data notes, and a dashboard link. Provider message limits may shorten it. It never derives new totals from rows.
  • PDF is a printable data report with panel tables, filters, totals, and result notes. It is not a screenshot of dashboard charts. Each panel shows up to 100 rows and 12 columns; larger results and shortened cells are clearly indicated. Fonts are bundled, including Arabic support, so generation needs no browser or external font service. Font licenses are in the report module's assets/OFL.txt.
  • CSV is a ZIP attachment containing a CSV for each panel, a separate totals CSV where available, and report.json with filters and result metadata. Text uses UTF-8 and standard CSV quoting. Spreadsheet formula prefixes in text are escaped; numeric values remain numeric.

Reports use the normal query permissions and query limits. Shared queries are executed once per report. Truncation, sampling, estimates, and partial flow results are disclosed. Live metrics are current measurements and do not use dashboard query filters. A missing or failed panel prevents delivery of the whole report. Exports allow up to 100 panels, 8 MB of collected data, and an 8 MB attachment, with a two-minute generation deadline. Narrow the filters or reduce the dashboard results when a limit is reached.

Database setup

API, native host and report-worker startup apply the shared metadata migrations, including report tables/settings. For an explicit migration, run from the repository root using the same database configuration as the API:

bun --env-file=.env run db:migrate

This creates the report tables and indexes without replacing existing data. When using embedded PGlite, stop the API before running a separate migration process against its data directory. Fresh installations receive the baseline schema and additive upgrades through the same migration entrypoint.

Notification channels and email setup

Workspace owners and admins configure Settings → Notifications. The inline channel editor follows OpenShip's provider picker and verified-channel flow, using shared BytesMap components. Add a channel, save it, then use Send test to deliver a real test notification. Saving or opening settings never sends a message or runs a dashboard query.

ChannelReport delivery
EmailSummary and optional PDF or CSV ZIP through the workspace mail connection
SlackSummary and dashboard link
DiscordSummary and PDF or CSV ZIP attachment
TeamsSummary and dashboard link through an incoming webhook or Workflow
TelegramSummary and file through a bot, with optional forum topic ID
WebhookSigned JSON, with the attachment encoded as base64

Channels belong to the workspace. Report authors can select their labels without access to credentials. Channel URLs, bot tokens, and signing secrets are encrypted with the host's credential codec. The API uses AES-256-GCM backed by BYTESMAP_MASTER_KEY when configured; legacy values and local installations without a master key use BETTER_AUTH_SECRET. See credential encryption and rotation. Webhook HMAC secrets are shown only on creation or replacement. Receivers verify X-BytesMap-Signature-256: sha256=<hex> using HMAC-SHA256 over the exact request body. The JSON includes id, category, organizationId, title, body, url, createdAt, and optional attachment.

Changing a destination resets its verification; renaming or pausing it does not. A test that finishes after the destination changes cannot verify the new config. Every selected channel must exist, be enabled, and have passed a test before the report reads customer data. Delivery resolves current channel settings again. Pausing a channel prevents reports using it from sending; those attempts appear as failed in history. Remove a channel from its schedules before deleting it.

Under Scheduler & channel delivery → Email channel connection, choose the instance connection or save a custom SMTP server for this workspace. Host, port, username, password, sender name, and sender address are editable. Connection security expands to STARTTLS or TLS. Test connection verifies connectivity and login only: it does not send email or save the draft. An omitted password keeps the saved value only for the same host, port, security mode, and username. Switching to instance email preserves the custom connection for later use. Both modes validate TLS.

Instance SMTP remains the fallback for existing installations and still provides account email independently of workspace report settings. Configure it on the API and any separate worker if using Use instance email:

SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=[email protected]
SMTP_PASS=your-smtp-password
SMTP_FROM=BytesMap <[email protected]>

Each email channel receives its own message, without exposing other channel addresses. Only email channels require a mail connection; chat and webhook channels work without SMTP. Reports can still be saved as paused and downloaded as samples. Attachments and credentials are never printed as a delivery fallback.

Webhooks use HTTPS, pinned DNS, bounded responses, and a 15-second deadline. Redirects are not followed. Private destinations are blocked by default. A self-hosted instance can opt into internal generic webhooks with BYTESMAP_NOTIFY_ALLOW_INTERNAL=true; cloud mode always blocks them. This does not relax destination checks for Slack, Discord, Teams, or Telegram. Custom SMTP can reach private relays on self-hosted instances, while cloud requires public hosts.

Running the scheduler

With BYTESMAP_WORKER_MODE=in-process, the API runs the scheduler. The BytesMap server must stay running; the browser may be closed. Settings → Notifications → Scheduler & channel delivery enables or pauses automatic schedules for the workspace. Expand Scheduler options to set the check interval from 10 seconds to one hour (default 30 seconds). These changes apply without restarting the API or worker. Manual Send now requests remain available while automatic reports are paused. The host wakes every five seconds to inspect metadata; a durable per-workspace checkpoint controls automatic checks at the saved interval. This wake-up does not run customer queries. Delivery can start after the selected minute by the configured interval and any time needed for earlier reports. Daily and weekly schedules follow the selected IANA time zone, including daylight saving transitions. Monthly schedules use days 1–28. If the server is offline across several intervals, one catch-up run replaces the missed runs.

For a separate worker, use PostgreSQL for BytesMap's metadata database, set BYTESMAP_WORKER_MODE=worker on the API, and start:

bun run --cwd apps/api reports:worker

Do not share an embedded PGlite directory between the API and a separate worker. Both processes must use the same DATABASE_URL, BETTER_AUTH_SECRET, BYTESMAP_MASTER_KEY when configured (required in cloud mode), and instance SMTP settings if using the fallback. Preserve the legacy auth secret while any old credentials still depend on it. Workspace settings are read from metadata. The distributable package includes the same worker:

bun run build:sdk
bun packages/bytesmap/dist/worker.js

Run the installed bytesmap-report-worker with the same environment supplied by your process manager. Durable database claims coordinate multiple schedulers and prevent concurrent delivery of the same run. Manual requests remain queued through restarts. A running attempt has a ten-minute lease; an expired lease is recorded as interrupted. Interrupted, failed, or partially accepted deliveries are not automatically retried, because a provider may already have accepted a delivery. Check destinations and history before choosing Send now again. A successful status means the provider accepted the message, not that a recipient read it. An export is collected and rendered once, then sent to each destination. If only some destinations accept it, history records a partial delivery.

Access and history

Managing reports requires dashboard write access. Every run checks current organization membership, dashboard access, and access to all saved queries and flow sources. Automatic runs use the person who last saved or resumed the schedule; manual runs use the person who requested delivery. Browser sessions, local mode and scoped tokens use the same operations. Each run rechecks the originating membership and credential against current grants; it cannot borrow broader owner access after a token is revoked.

Managing channels and report delivery settings requires an account session with workspace owner/admin access. API keys and MCP cannot configure outbound destinations. The shared SDK exposes identical HTTP and trusted native operations.

Each workspace allows up to 50 channels. Each dashboard allows up to 20 schedules, each with up to 20 selected channels. The latest 100 attempts per schedule are retained, with up to 25 shown in history. History records settings, channel IDs, provider labels, per-channel outcomes, and timestamps. Generated data and attachments are not stored in the metadata database.

HTTP endpoints

All paths below are under /api/dashboards/:id.

MethodPathPurpose
GET/reportsSchedules, available channels, SMTP/scheduler state, and worker mode
POST/reportsCreate a schedule
PATCH/reports/:reportIdReplace settings, or pause with { "enabled": false }
DELETE/reports/:reportIdDelete an idle schedule and its history
GET/reports/:reportId/runsRecent delivery attempts
POST/reports/:reportId/sendQueue a manual delivery; returns HTTP 202
POST/report-exportDownload a sample using { "format": "pdf", "params": {} }

Create and full update bodies have this shape:

{
  "name": "Weekly operations",
  "channelIds": ["saved-notification-channel-id"],
  "panelIds": ["chart:active-users-chart-id"],
  "format": "pdf",
  "params": { "since": "-7d" },
  "enabled": true,
  "recurrence": {
    "frequency": "weekly",
    "time": "09:00",
    "timezone": "Africa/Cairo",
    "weekday": 1,
    "monthDay": 1
  }
}

weekday uses Sunday = 0 and is used for weekly schedules. monthDay is used for monthly schedules. Both fields are present regardless of frequency. Use "format": "csv" for a ZIP of CSV files. Only parameters declared by the dashboard may be supplied.

channelIds is required and non-empty. recipients is rejected. Omit panelIds for the whole dashboard; otherwise send a non-empty array of the shared panelKey() identities. The sample export endpoint accepts the same optional panelIds. Use format: "summary" for a message without an attachment. The existing reports HTTP/SDK operation names remain compatible. The unified schedules operations delegate dashboard deliveries to the same services and durable queue; there is no separate client execution engine.

Workspace operations are defined once in @bytesmap/contracts and implemented by @bytesmap/platform:

MethodPathPurpose
GET / PUT/api/settings/reportsRead/save workspace scheduler and mail configuration
POST/api/settings/reports/test-connectionTest a draft SMTP connection without saving or sending
GET / POST/api/notifications/channelsList/create channels
PATCH / DELETE/api/notifications/channels/:idEdit/pause/delete a channel
POST/api/notifications/channels/:id/testSend a test and verify the unchanged destination

API/worker/native startup applies an additive migration. Legacy direct recipients become saved email channels, reused by address within the same workspace. New channels require an explicit test; migration never marks them verified. Affected schedules pause with Review needed, and pending legacy sends are interrupted. Test the converted channels, then edit and enable the schedule. Historical recipient labels remain readable, but cannot be used for new delivery. Existing channel-only schedules keep their cadence. Existing Slack/Teams-only schedules are normalized to summary delivery, which is what those webhooks already received. Workspace SMTP settings and credentials are preserved.

On this page