Telemetry
Browse docs
GuidesUpdated July 28, 2026Reviewed by the Telemetry editorial and product teams5 min read

Use this doc with your coding agent

Copy a Telemetry prompt and ask Claude Code, Codex, or Cursor to instrument the workflow covered here.

On this page
  1. What you will build
  2. 1. Start with report SQL
  3. 2. Query Telemetry and post the result
  4. 3. Schedule the report
  5. Make failures visible
  6. Design a report people will keep reading

Send Scheduled SQL Reports to Slack

A scheduled SQL report turns a reviewed query into a recurring team habit. The useful version does more than paste rows into chat: it names the time window, includes the denominator, links back to the durable dashboard, and records whether delivery succeeded.

This guide uses the Telemetry Query API, a Slack incoming webhook, and a scheduled GitHub Actions workflow. It is an integration pattern, not a claim that Telemetry has a native Slack-reporting button.

What you will build

The reporting job will:

  1. run a read-only SQL query against api_request_completed;
  2. format the result into a small Slack Block Kit message;
  3. post it through an incoming webhook;
  4. fail on query or delivery errors so the scheduler records the problem.

You need a Telemetry API key, a Slack incoming webhook URL, Node.js 20 or newer, and a repository where encrypted workflow secrets can be configured.

Slack documents incoming webhooks as unique secret URLs that accept JSON message payloads. Keep the URL out of source control and rotate it if it is exposed. See Slack’s incoming webhook guide.

1. Start with report SQL

Keep the first report small and decision-oriented:

SELECT
  route,
  COUNT(*) AS requests,
  SUM(CASE WHEN status_code >= 500 THEN 1 ELSE 0 END) AS errors,
  ROUND(
    100.0 * SUM(CASE WHEN status_code >= 500 THEN 1 ELSE 0 END)
      / NULLIF(COUNT(*), 0),
    2
  ) AS error_rate_pct
FROM api_request_completed
WHERE timestamp_utc >= now() - INTERVAL '24 hours'
GROUP BY route
ORDER BY error_rate_pct DESC, requests DESC
LIMIT 5;

The message should say “last 24 hours,” not “today,” because the scheduler and reader may use different timezones. The requests column keeps a high error rate on two requests from looking like a high-volume incident.

Test the query in Telemetry first. Confirm field types and decide whether retries count as separate requests. The API error rate recipe includes a typed contract, synthetic output, chart, and operational edge cases.

2. Query Telemetry and post the result

Save this as scripts/post-telemetry-report.mjs in the repository that will run the report:

const { TELEMETRY_API_KEY, SLACK_WEBHOOK_URL, TELEMETRY_DASHBOARD_URL } =
  process.env;

if (!TELEMETRY_API_KEY || !SLACK_WEBHOOK_URL) {
  throw new Error(
    "TELEMETRY_API_KEY and SLACK_WEBHOOK_URL are required to run this report"
  );
}

const sql = `
SELECT
  route,
  COUNT(*) AS requests,
  SUM(CASE WHEN status_code >= 500 THEN 1 ELSE 0 END) AS errors,
  ROUND(
    100.0 * SUM(CASE WHEN status_code >= 500 THEN 1 ELSE 0 END)
      / NULLIF(COUNT(*), 0),
    2
  ) AS error_rate_pct
FROM api_request_completed
WHERE timestamp_utc >= now() - INTERVAL '24 hours'
GROUP BY route
ORDER BY error_rate_pct DESC, requests DESC
LIMIT 5
`;

const queryResponse = await fetch("https://api.telemetry.sh/query", {
  method: "POST",
  headers: {
    Authorization: TELEMETRY_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ query: sql, realtime: true, json: true }),
});

if (!queryResponse.ok) {
  throw new Error(
    `Telemetry query failed: ${queryResponse.status} ${await queryResponse.text()}`
  );
}

const queryResult = await queryResponse.json();
const rows = Array.isArray(queryResult.data) ? queryResult.data : [];
const lines =
  rows.length > 0
    ? rows.map(
        (row) =>
          `\`${row.route}\`${row.error_rate_pct}% errors ` +
          `(${row.errors}/${row.requests})`
      )
    : ["• No matching requests in the last 24 hours"];

const dashboardLink = TELEMETRY_DASHBOARD_URL
  ? `\n<${TELEMETRY_DASHBOARD_URL}|Open the Telemetry dashboard>`
  : "";

const slackResponse = await fetch(SLACK_WEBHOOK_URL, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    text: "Daily API reliability report",
    blocks: [
      {
        type: "header",
        text: { type: "plain_text", text: "Daily API reliability" },
      },
      {
        type: "section",
        text: {
          type: "mrkdwn",
          text:
            "*Window:* trailing 24 hours\n" +
            lines.join("\n") +
            dashboardLink,
        },
      },
      {
        type: "context",
        elements: [
          {
            type: "mrkdwn",
            text: "Synthetic example format — validate thresholds and counting rules.",
          },
        ],
      },
    ],
  }),
});

if (!slackResponse.ok) {
  throw new Error(
    `Slack delivery failed: ${slackResponse.status} ${await slackResponse.text()}`
  );
}

console.log(`Posted ${rows.length} report rows to Slack.`);

Environment checks happen only when this script is executed. They do not add a new requirement to your application’s import or startup path.

Avoid including raw error messages, customer identifiers, prompts, or request payloads in the Slack message. Link authorized readers to the dashboard for deeper investigation.

3. Schedule the report

Create .github/workflows/telemetry-report.yml:

name: telemetry reliability report

on:
  schedule:
    - cron: "15 16 * * 1-5"
      timezone: "America/Los_Angeles"
  workflow_dispatch:

jobs:
  post-report:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: node scripts/post-telemetry-report.mjs
        env:
          TELEMETRY_API_KEY: ${{ secrets.TELEMETRY_API_KEY }}
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
          TELEMETRY_DASHBOARD_URL: ${{ vars.TELEMETRY_DASHBOARD_URL }}

GitHub scheduled workflows run from the default branch. The workflow syntax supports POSIX cron and an IANA timezone; review the current behavior in GitHub’s workflow syntax reference.

Add TELEMETRY_API_KEY and SLACK_WEBHOOK_URL as encrypted Actions secrets. A dashboard URL is not normally a secret, so it can be a repository variable if the URL itself reveals no sensitive identifier.

Keep workflow_dispatch while rolling out the report. It lets a reviewer run the exact job manually before waiting for the first scheduled execution.

Make failures visible

A silent scheduled job is not an operational system. At minimum:

  • let non-2xx query and webhook responses fail the workflow;
  • keep the scheduler’s failure notifications enabled;
  • record report_name, window_start, window_end, row_count, delivery_status, and latency_ms in a bounded reporting event;
  • avoid automatic retries for malformed payloads or revoked webhooks;
  • use a stable idempotency key if you later move to an API that supports it.

Slack can return different 4xx responses for invalid payloads, disabled hooks, archived channels, or policy restrictions. Do not retry every 4xx blindly. Treat a timeout or 5xx as a potentially transient delivery failure and cap retries with backoff.

Design a report people will keep reading

Put the decision first. A useful report names what changed, shows the denominator, and links to evidence. Ten carefully chosen rows are usually better than a CSV pasted into chat.

Use a table or dashboard instead of chat when readers need arbitrary filtering. Use an alert instead of a daily report when the response cannot wait for the scheduled window. Use the export guide when the result is too large for a message.

Possible follow-on reports include LLM cost by feature, background job retry outcomes, webhook retry recovery, and customer impact during an incident.