Skip to content

feat: PostHog telemetry, error tracking, docs analytics, and release annotations#141

Merged
leoisadev1 merged 5 commits into
mainfrom
posthog-code/telemetry-combined
Jul 6, 2026
Merged

feat: PostHog telemetry, error tracking, docs analytics, and release annotations#141
leoisadev1 merged 5 commits into
mainfrom
posthog-code/telemetry-combined

Conversation

@leoisadev1

@leoisadev1 leoisadev1 commented Jul 6, 2026

Copy link
Copy Markdown
Member

Replaces #86 (and the closed #80) as a clean replay of the entire PostHog telemetry feature onto the current core. Since those PRs were opened, main gained batch sends with per-recipient variable substitution (#125) and scheduled sends via sendAt (#127), which reworked core.ts, types.ts, utils.ts, and payloads.ts — so instead of a conflict-riddled rebase, this PR re-applies #86's head (which contained all of #80 plus the error-tracking delta) file by file onto the new architecture.

What's included

SDK/CLI telemetry (telemetry.ts, ported byte-for-byte from #86):

  • Anonymous events: client created, email sent, email batch sent, cli command run
  • Redacted error reporting (PostHog error tracking): error type, Email SDK error code, package-relative stack frames; messages scrubbed of email addresses, URLs, quoted text, long tokens, and home directories — the hardened redaction pipeline from Add PostHog error tracking, richer telemetry, docs analytics, and release annotations #86's review rounds is unchanged
  • CI vendor detection (GitHub Actions, GitLab, CircleCI, Jenkins, Travis, Buildkite, generic; Vercel production runtime deliberately not treated as CI)
  • All opt-outs preserved: EMAIL_SDK_TELEMETRY=0/false/off, DO_NOT_TRACK=1/true, NODE_ENV=test auto-off, createEmailClient({ telemetry: false }), one-time stderr notice, $process_person_profile: false, fire-and-forget captures with a 3s timeout, CLI flush before exit

Re-instrumentation for the new core (the part that differs from #86):

  • Add PostHog error tracking, richer telemetry, docs analytics, and release annotations #86 instrumented the old send/sendBatch. Main's send path now fans out three ways: plain single sends, native provider.sendBulk for recipientVariables, and client-side per-recipient expansion when the adapter has no native batch. Telemetry now fires exactly one email sent event per user-facing send() call — expanded per-recipient sends run through sendWithRetry internally and are never counted individually, so no double-counting
  • email sent gained used_recipient_variables, used_send_at, and delivery_path (single | bulk_native | bulk_expanded). On success the delivering adapter decides the path; on failure the primary resolved adapter does (the path that would have run)
  • email batch sent summary for sendBatch is unchanged from Add PostHog error tracking, richer telemetry, docs analytics, and release annotations #86 (message_count, succeeded, failed, recipients, uniform adapter or mixed, first failure code) and reflects the adapter that actually delivered after fallbacks

telemetrySource moved off the public type (review item from #86):

Docs site (email-sdk.dev):

  • posthog-js initialized once on hydration in __root.tsx (defaults: "2026-01-30", capture_exceptions, web vitals, person_profiles: "identified_only", session recording disabled)
  • Privacy page discloses PostHog (US cloud) and links the README telemetry section
  • CSP pins the exact PostHog hosts — us-assets.i.posthog.com in script-src, us.i.posthog.com + us-assets.i.posthog.com in connect-src, no wildcards — rebased onto main's current CSP (which had since gained the Notra image host)

Release pipeline:

  • release.yml posts a project-scoped release annotation to PostHog project 468042 after changesets publishes; skips with a notice when POSTHOG_PERSONAL_API_KEY is absent and never fails the job (jq-built body, non-blocking curl)

Disclosures:

  • Telemetry sections in both READMEs (updated to mention the new recipient-variables/scheduling/delivery-path booleans) and one combined minor changeset for @opencoredev/email-sdk covering both original changesets

Adversarial review round (c271794)

Four verified findings fixed:

  • error.name bypassed redaction (major): a writable Error.name flowed verbatim into $exception_list[].type, error_name, the class dedupe key, and $exception_fingerprint. Names now pass through the same redaction pipeline as messages, with an allowlist so SDK error classes and JS built-ins still report verbatim (keeping fingerprints stable). Covered by hostile-name tests asserting the full payload carries no emails or paths.
  • Test-suite network isolation relied on Bun's default NODE_ENV=test (major): env EMAIL_SDK_TELEMETRY=1 NODE_ENV=production bun test could capture live. A Bun [test] preload (root bunfig.toml + a package-level mirror, since Bun only reads the cwd's bunfig) now forces EMAIL_SDK_TELEMETRY=0 for every in-process run. Proven with that exact hostile env plus a global fetch spy: 182 pass, zero PostHog requests, no telemetry state file written.
  • Path tails after home-dir collapse leaked filenames (minor): C:\Users\jsmith\Documents\payroll.xlsx previously became C:~\Documents\payroll.xlsx. The redaction now consumes the remaining segments on both separators, yielding ~<path-redacted>. Tests for POSIX and Windows tails.
  • No key=value secret pattern (minor): password=hunter2-style values (password/passwd/pwd/pass/secret/token/api_key, case-insensitive, value up to whitespace/quote) now redact to key=<redacted>, applied before the token-length pattern so short secrets are caught too.

Two review observations accepted as known behavior (not fixed here):

  • On a read-only filesystem (e.g. serverless), the one-time telemetry notice re-prints per cold start because the noticeShown marker cannot persist; telemetry itself still works with a per-process identity.
  • On send failure, email sent attributes adapter/delivery_path to the primary resolved adapter (the path that would have run), not whichever fallback failed last.

Validation

  • bun run build + bun test: 182 pass / 0 fail (8 new redaction/isolation tests from the review round) (116 pre-existing tests untouched and green; 47 telemetry unit tests ported unchanged; 11 client-telemetry integration tests, including new coverage for delivery_path, used_send_at, no double-counting on expansion, and telemetry: false)
  • Verified NODE_ENV=test auto-disables the shared telemetry singleton under bun test; all enabled test instances use an injected fetch, and CLI subprocess tests additionally set EMAIL_SDK_TELEMETRY=0, so the suite makes no live network calls
  • bunx oxlint clean on all touched files
  • apps/fumadocs: bun run types:check and bun run build green

Note for #128 (README restyle): main's READMEs were not rewritten here — only the Telemetry sections were added, so #128 rebases over this cleanly.


Created with PostHog Code

…g for SDK and CLI

Replay of PRs #80 + #86 onto the new core (post batch recipient
variables #125 and scheduled sends #127).

- telemetry.ts / telemetry.test.ts ported byte-for-byte from #86,
  including the reviewed redaction pipeline, opt-outs
  (EMAIL_SDK_TELEMETRY=0/false/off, DO_NOT_TRACK, NODE_ENV=test,
  telemetry: false), first-run notice, exception dedupe/budget, and
  fire-and-forget delivery with a 3s timeout.
- core.ts re-instruments the new send architecture: one "email sent"
  event per user-facing send() call (never per expanded recipient),
  with used_recipient_variables, used_send_at, and delivery_path
  (single | bulk_native | bulk_expanded) alongside the #86 fields;
  "email batch sent" summary for sendBatch; captureException for
  reportable send errors.
- telemetrySource moved off the public EmailClientOptions type onto an
  internal module seam (setTelemetrySource) that only the bundled CLI
  flips; the public surface gains only `telemetry?: boolean`.
- CLI reports "cli command run" and flushes captures before exit.
- README telemetry disclosure sections and a combined minor changeset.

Generated-By: PostHog Code
Task-Id: ec2538f2-5c80-4142-b4b3-b1a53394862e
Port of the docs-site half of #86: posthog-js initialized once on
hydration in __root.tsx (2026-01-30 defaults, capture_exceptions, web
vitals, identified_only person profiles, session recording disabled),
privacy page disclosure updates, and CSP pinned to the exact PostHog
hosts (us-assets.i.posthog.com in script-src; us.i.posthog.com and
us-assets.i.posthog.com in connect-src — no wildcards).

Generated-By: PostHog Code
Task-Id: ec2538f2-5c80-4142-b4b3-b1a53394862e
Posts a project-scoped annotation to PostHog project 468042 when
changesets publishes, so release markers line up with telemetry
charts. Skips gracefully when POSTHOG_PERSONAL_API_KEY is absent and
never fails the release job.

Generated-By: PostHog Code
Task-Id: ec2538f2-5c80-4142-b4b3-b1a53394862e
@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
email-sdk-fumadocs Ready Ready Preview, Comment Jul 6, 2026 4:51pm

Request Review

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds end-to-end PostHog telemetry to the Email SDK — anonymous event capture in the SDK and CLI, redacted error reporting via a custom HTTP telemetry client, docs-site analytics via posthog-js, and a release annotation step in CI. It re-applies the telemetry feature from the abandoned #86 PR onto the new batching and sendAt architecture introduced since then, and addresses four adversarial-review findings around error name sanitisation, test isolation, home-dir tail leakage, and key=value credential patterns.

  • SDK/CLI telemetry (telemetry.ts): A self-contained fire-and-forget HTTP client sends typed events (client created, email sent, email batch sent, cli command run) with a hardened multi-pass redaction pipeline for error messages; the delivery_path field distinguishes single / bulk_native / bulk_expanded sends.
  • Test isolation (test-preload.ts + bunfig.toml): Forces EMAIL_SDK_TELEMETRY=0 at process start via Bun's [test] preload so the suite never makes live PostHog calls regardless of the caller's NODE_ENV.
  • Docs site (posthog.ts, __root.tsx, vercel.json): posthog-js is initialised once with exception autocapture, web vitals, and session recording disabled; CSP pins the exact PostHog hosts with no wildcards.

Confidence Score: 5/5

Safe to merge — the telemetry path is fully fire-and-forget and can never interrupt email delivery, all opt-outs work correctly, test isolation is enforced at the preload level, and the CSP is tightly scoped.

The implementation is careful and well-defended: the redaction pipeline covers all the adversarially-tested patterns, the deliver() call wraps every network error in a silent catch, flush() is called before CLI process exit so no events are dropped, and the test preload guarantees network isolation unconditionally. The one new observation (PostHog initialisation deferred to a React effect on the docs site) is a coverage gap for error autocapture before hydration, not a correctness or security issue.

apps/fumadocs/src/routes/__root.tsx — PostHog initialisation timing relative to hydration-phase errors.

Important Files Changed

Filename Overview
packages/email-sdk/src/telemetry.ts Core telemetry module: anonymous event capture, redacted exception reporting, CI detection, opt-out logic, and state persistence. Well-structured with robust redaction pipeline (emails, URLs, tokens, home dirs, key=value secrets) and per-process deduplication for exceptions. All previous review issues addressed.
packages/email-sdk/src/core.ts Re-instrumented for the new fan-out send paths: single, bulk_native, and bulk_expanded. Fires exactly one "email sent" event per user-facing send() call; batchAdapter handling correctly uses undefined-guard for empty batch.
apps/fumadocs/src/lib/posthog.ts PostHog browser init for docs site. capture_console_errors correctly disabled. Initialisation deferred to useEffect in __root.tsx may miss early unhandled errors before hydration.
apps/fumadocs/src/routes/__root.tsx Root route initialises PostHog via useEffect — deferred after hydration, creating a gap for capture_unhandled_errors before handlers are installed. Otherwise correct: guard for SSR window check and single-init flag.
.github/workflows/release.yml Adds a non-blocking PostHog release annotation step after changeset publish. Key stored as secret, payload built safely with jq --arg, curl failure falls back to a warning not a job failure.
vercel.json CSP updated to pin PostHog hosts exactly — us-assets.i.posthog.com in script-src, both PostHog hosts in connect-src, no wildcards. Correctly rebased onto existing CSP with Notra image host.
packages/email-sdk/src/cli.ts setTelemetrySource("cli") called before any client creation; flush() correctly settles all fire-and-forget captures before process.exit. CLI failure and unexpected crash paths both report telemetry correctly.
packages/email-sdk/test-preload.ts Forces EMAIL_SDK_TELEMETRY=0 at process start via Bun preload, ensuring no live PostHog calls regardless of the caller's NODE_ENV — addresses the hostile-env isolation gap noted in the adversarial review round.
bunfig.toml Root bunfig wires the test-preload for repo-root bun test runs. Mirrors the package-level bunfig so preload fires regardless of which directory tests are invoked from.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant CLI
    participant Client
    participant Telemetry
    participant PostHog API

    User->>CLI: email-sdk send --adapter resend
    CLI->>Telemetry: setTelemetrySource("cli")
    CLI->>Client: "createEmailClient({ adapters })"
    Client->>Telemetry: capture("client created")
    Telemetry-->>PostHog API: POST /capture/ (fire-and-forget, 3s timeout)
    CLI->>Client: client.send(message)
    Client->>Client: sendWithAdapters()
    alt "delivery_path = single"
        Client->>Client: sendWithRetry()
    else "delivery_path = bulk_native"
        Client->>Client: provider.sendBulk()
    else "delivery_path = bulk_expanded"
        Client->>Client: sendBulkViaFallback() per recipient
    end
    alt Success
        Client->>Telemetry: "capture("email sent", {success:true, delivery_path})"
    else Failure
        Client->>Telemetry: "capture("email sent", {success:false})"
        Client->>Telemetry: captureException(error)
    end
    CLI->>Telemetry: capture("cli command run")
    CLI->>Telemetry: flush()
    Telemetry->>PostHog API: await all pending captures
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant CLI
    participant Client
    participant Telemetry
    participant PostHog API

    User->>CLI: email-sdk send --adapter resend
    CLI->>Telemetry: setTelemetrySource("cli")
    CLI->>Client: "createEmailClient({ adapters })"
    Client->>Telemetry: capture("client created")
    Telemetry-->>PostHog API: POST /capture/ (fire-and-forget, 3s timeout)
    CLI->>Client: client.send(message)
    Client->>Client: sendWithAdapters()
    alt "delivery_path = single"
        Client->>Client: sendWithRetry()
    else "delivery_path = bulk_native"
        Client->>Client: provider.sendBulk()
    else "delivery_path = bulk_expanded"
        Client->>Client: sendBulkViaFallback() per recipient
    end
    alt Success
        Client->>Telemetry: "capture("email sent", {success:true, delivery_path})"
    else Failure
        Client->>Telemetry: "capture("email sent", {success:false})"
        Client->>Telemetry: captureException(error)
    end
    CLI->>Telemetry: capture("cli command run")
    CLI->>Telemetry: flush()
    Telemetry->>PostHog API: await all pending captures
Loading

Reviews (3): Last reviewed commit: "fix(telemetry): address review nits — ex..." | Re-trigger Greptile

Comment thread apps/fumadocs/src/lib/posthog.ts Outdated
Comment thread packages/email-sdk/src/core.ts
Comment thread packages/email-sdk/src/telemetry.ts
…ction gaps

Adversarial-review fixes:
- error.name now runs through the redaction pipeline (allowlisted SDK/builtin
  names pass verbatim) before it reaches $exception_list type, error_name,
  the class dedupe key, or $exception_fingerprint
- bun test preload forces EMAIL_SDK_TELEMETRY=0 so the in-process suite can
  never capture live or write telemetry state, regardless of NODE_ENV
- home-dir redaction now consumes the remaining path segments on both
  separators (~<path-redacted>) instead of leaking filenames after the collapse
- key=value secrets (password/passwd/pwd/pass/secret/token/api_key) redact
  before the token-length pattern so short values are caught too

Generated-By: PostHog Code
Task-Id: ec2538f2-5c80-4142-b4b3-b1a53394862e
@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

… narrow batchAdapter type, add resetTelemetrySource seam

Generated-By: PostHog Code
Task-Id: ec2538f2-5c80-4142-b4b3-b1a53394862e
@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant