# How Self-Host Telemetry Works in OpenSEO Deployments

> Learn how OpenSEO's self-host telemetry works in deployments. Discover anonymous data transmission, health checks, and intelligent throttling for efficient usage monitoring.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: internals
- Published: 2026-08-09

---

**OpenSEO's self-host telemetry system transmits anonymous heartbeat events containing aggregate usage counts and health-check statuses from Docker or Cloudflare deployments to PostHog, while honoring opt-out environment variables and implementing intelligent throttling to minimize database overhead.**

The `every-app/open-seo` repository includes a privacy-conscious telemetry implementation designed specifically for self-hosted instances. This system helps maintainers understand deployment patterns and diagnose widespread issues without collecting personally identifiable information or impacting application performance.

## Telemetry Control and Privacy Safeguards

The telemetry system first determines whether it should activate by checking deployment context and user preferences.

### Environment Variable Opt-Outs

In [`src/shared/selfhost-checks.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/selfhost-checks.ts), the `isTelemetryOptOutValue` helper evaluates two environment variables to respect user privacy:

- `OPENSEO_TELEMETRY_DISABLED`
- `DO_NOT_TRACK`

Setting either variable to `1` completely disables the telemetry pipeline. The `telemetryIsDisabled()` function in [`src/server/lib/self-host-telemetry.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/self-host-telemetry.ts) combines these checks with a hosted-mode detection (`isHostedServerAuthMode`) to ensure telemetry only runs on self-hosted installations, never on the managed cloud service.

### Production Build Guards

The system also verifies `isNonProductionBuild` to prevent telemetry transmission from development or preview builds, ensuring that only production deployments contribute to usage statistics.

## Heartbeat Scheduling and State Persistence

OpenSEO implements a sophisticated throttling mechanism to avoid database pressure while maintaining accurate installation tracking.

### The telemetry_state Table

On first run, the system creates a single row in the `telemetry_state` table defined in [`src/db/telemetry.schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/telemetry.schema.ts) (SQLite) or [`src/db/pg/telemetry.schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/pg/telemetry.schema.ts) (Postgres). This record stores:

- A random `installId` (UUID) that serves as the anonymous distinct identifier
- `lastHeartbeatAt` timestamp for cadence enforcement
- Version information and first-run flags
- An MCP tool-call counter

### Cadence and Throttling Logic

The `claimHeartbeat()` function enforces a tiered reporting schedule:

1. **Initial burst**: Every 5 minutes for the first 2 hours after installation
2. **Steady state**: Daily thereafter

To minimize database queries, the implementation uses in-memory variables `lastCheckedAt` and `cachedInstalledAt` as a lightweight memory throttle, preventing table lookups on every request.

## Data Collection and PostHog Integration

When a heartbeat triggers, the system aggregates anonymous metrics and transmits them via the PostHog SDK.

### Aggregate Count Collection

The `collectCounts()` function queries core application tables—including `user`, `projects`, and `audits`—to generate aggregate usage statistics. These counts reveal feature adoption trends without exposing individual user data.

### Setup Issue Reporting

The `collectSetupIssues()` function invokes `getSetupIssueSummary` to compile a compact list of failing health checks. This helps the development team identify common configuration problems across the self-host community.

### Heartbeat Payload Structure

The `sendHeartbeat()` function constructs a `HeartbeatProperties` object containing:

- **Deployment target**: `docker` or `cloudflare`
- **Database backend**: `d1` or `postgres`
- **Application version** and first-run flag
- **MCP tool-call count** (incremented via `incrementSelfHostMcpToolCallCount()`)
- **Setup issue summaries**

The event posts to PostHog with the `installId` UUID as the distinct ID, ensuring anonymous but consistent session tracking per installation.

## Request Lifecycle Integration

The telemetry system hooks into the main request handler in [`src/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server.ts) (line 41). Each incoming request invokes `maybeSendSelfHostHeartbeat()` wrapped in `ctx.waitUntil`, allowing the heartbeat logic to execute asynchronously without delaying the HTTP response. This pattern ensures telemetry collection adds zero latency to user-facing operations.

## Configuring and Debugging Telemetry

### Disabling Telemetry

Add one of the following to your `.env` or `.env.selfhost` file:

```bash

# Option 1: OpenSEO-specific flag

OPENSEO_TELEMETRY_DISABLED=1

# Option 2: Universal do-not-track standard

DO_NOT_TRACK=1

```

Restart your containers or redeploy the Cloudflare Worker to apply the changes.

### Manual Heartbeat Triggering

For debugging purposes, you can manually trigger a heartbeat:

```typescript
import { maybeSendSelfHostHeartbeat } from "@/server/lib/self-host-telemetry";

await maybeSendSelfHostHeartbeat();

```

### Incrementing MCP Tool-Call Counters

When building MCP client integrations, use the provided helper to track tool usage:

```typescript
import { incrementSelfHostMcpToolCallCount } from "@/server/lib/self-host-telemetry";

await incrementSelfHostMcpToolCallCount();

```

## Summary

- **Opt-out by default**: Set `OPENSEO_TELEMETRY_DISABLED=1` or `DO_NOT_TRACK=1` to disable transmission entirely
- **Anonymous identifiers**: Each installation receives a random UUID stored in the `telemetry_state` table, never linked to user accounts
- **Adaptive cadence**: Heartbeats send every 5 minutes initially, then daily after the first 2 hours
- **Zero-latency design**: Asynchronous execution via `ctx.waitUntil` ensures telemetry never blocks requests
- **Rich metadata**: Payloads include deployment target (Docker/Cloudflare), database type (D1/Postgres), aggregate counts, and health-check summaries

## Frequently Asked Questions

### What data does OpenSEO's self-host telemetry collect?

The system collects only anonymous aggregate counts—such as the number of users, projects, and audits—and health-check failure summaries. It explicitly excludes personally identifiable information, content data, or individual user actions. According to the source code in [`src/server/lib/self-host-telemetry.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/self-host-telemetry.ts), the `collectCounts()` function runs aggregation queries that return totals rather than individual records.

### How do I completely disable telemetry in my Docker deployment?

Set the environment variable `OPENSEO_TELEMETRY_DISABLED=1` in your `.env` file and restart the containers. Alternatively, use the universal `DO_NOT_TRACK=1` standard. The `isTelemetryOptOutValue` helper in [`src/shared/selfhost-checks.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/selfhost-checks.ts) evaluates both variables, and the `telemetryIsDisabled()` function ensures no data transmits when either is present.

### Why does telemetry send frequently at first but then slow down?

The `claimHeartbeat()` function implements a graduated cadence: every 5 minutes for the first 2 hours to capture initial setup patterns, then daily thereafter. This design, found in [`src/server/lib/self-host-telemetry.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/self-host-telemetry.ts), provides immediate feedback on new installations while minimizing long-term database load and network traffic for stable deployments.

### Can telemetry run in development or preview builds?

No. The `maybeSendSelfHostHeartbeat()` function checks `isNonProductionBuild` and exits early if the code runs outside a production environment. This prevents test data from polluting the analytics, as implemented in the telemetry control logic of [`src/server/lib/self-host-telemetry.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/self-host-telemetry.ts).