Skip to content

feat(node): read an email's status with emails.get - #9

Draft
Silthus wants to merge 3 commits into
emails-v2/04-code-fieldsfrom
emails-v2/05-emails-get
Draft

Silthus wants to merge 3 commits into
emails-v2/04-code-fieldsfrom
emails-v2/05-emails-get

Conversation

@Silthus

@Silthus Silthus commented Oct 25, 2026 •

Copy link
Copy Markdown
Owner

Part of the code-first rebuild of posthog-node's email API; replaces PostHog#5372.

Problem

After a send, a server wants to know whether the email went out, bounced or was dropped, without a webhook. This PR adds posthog.emails.get(id), which reads an email's status by the id that send returned.

Top of the stack, on #8. Needs the server's email status read.

Impact

Same as the stack: Grow Workflows. A status read is part of the Resend surface a caller expects before switching.

Merge Danger

Warning

🟠 Merge risk: medium · one-way door (merge-risk rubric)

  • A new public read method and result shape. It never retries and writes nothing, so a wrong answer costs a caller a read, not an email.

Changes

Kind Files Lines (this PR, against #8)
Production 3 +128 −19
Tests 2 +128 −1
References and changesets 2 +51 −1
  • emails.get(id) reads GET /api/projects/@current/messaging/emails/{id}/ on the app host with the secret key, the id as one encoded path segment.
  • Returns { data: { id, status, createdAt, statusAt, reason? }, error: null } or { data: null, error }, with the same error names as send.
  • Never retries. A new email answers 404 not_found for its first seconds; the docs say so, and that status is not Resend's last_event.
  • Refuses before any request: missing_secret_key, missing_app_host, client_disabled, client_closed, and invalid_message_id for an empty id or one that can't be encoded.
  • Equal to the reviewed feat(node): read an email's status with emails.get PostHog/posthog-js#5372, plus the capture_engagement default below it.

Evidence

  • Test seams: posthog.emails.get() on a real client with FakeEmailsApi, which keeps each sent email's status.
  • What the tests pin: the request path and encoding, each result field, each error row without a retry, each refusal before any request.
  • Local gate on f08bfdb4: node vitest (1356 passed), edge (1356 passed), tsc, oxlint, oxfmt, build, references committed; core vitest.
  • Owed proofs:

Decisions

No load-bearing decisions.

Involvement: carries the PostHog#5372 review rounds; a fresh HEAD read (1 fixed: . and .. ids are refused before any request) and a final read (clean), no live proof yet.

Release info Sub-libraries affected

Libraries affected

  • All of them
  • posthog-js (web)
  • posthog-js-lite (web lite)
  • posthog-node
  • posthog-react-native
  • @posthog/react-native-plugin
  • @posthog/react
  • @posthog/ai
  • @posthog/convex
  • @posthog/next
  • @posthog/nextjs-config
  • @posthog/nuxt
  • @posthog/openfeature-node-provider
  • @posthog/openfeature-web-provider
  • @posthog/rollup-plugin
  • @posthog/webpack-plugin
  • @posthog/types
  • @posthog/browser-common

Checklist

  • All commits are signed (see Commit signing)
  • Tests for new code
  • Accounted for the impact of any changes across different platforms
  • Accounted for backwards compatibility of any changes (no breaking changes!)
  • Took care not to unnecessarily increase the bundle size

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

🤖 Generated with Claude Code

@Silthus
Silthus force-pushed the emails-v2/04-code-fields branch from a370487 to 989b6c5 Compare October 25, 2026 03:24
@Silthus
Silthus force-pushed the emails-v2/05-emails-get branch from 5e040ec to 03baabd Compare October 25, 2026 03:24
@Silthus
Silthus force-pushed the emails-v2/04-code-fields branch from 989b6c5 to d509711 Compare October 25, 2026 03:39
@Silthus
Silthus force-pushed the emails-v2/05-emails-get branch from 03baabd to f08bfdb Compare October 25, 2026 03:39
@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Node SDK compliance v2

Commit: 8d8d65b
Workflow run and artifacts

Capture v0 — acceptance

✅ 28 passed / ❌ 3 non-passing / ⚠️ 853 not selected

Drill-down: run artifacts → node-v2-v0 → acceptance/report.json.diagnostics.json (match case_id below).

❌ server_snapshot_payload_false — failed_assertion

Expected native data result

Code: snapshot_result

Failed step: acceptance/public/evaluate-flags.feature:868

the public snapshot outcomes should have these semantics:
❌ server_snapshot_payload_zero — failed_assertion

Expected native data result

Code: snapshot_result

Failed step: acceptance/public/evaluate-flags.feature:868

the public snapshot outcomes should have these semantics:
❌ server_snapshot_payload_null — failed_assertion

Expected native data result

Code: snapshot_result

Failed step: acceptance/public/evaluate-flags.feature:868

the public snapshot outcomes should have these semantics:

Harness exit: 1; report validation exit: 1.

Capture v1 — acceptance

✅ 28 passed / ❌ 3 non-passing / ⚠️ 853 not selected

Drill-down: run artifacts → node-v2-v1 → acceptance/report.json.diagnostics.json (match case_id below).

❌ server_snapshot_payload_false — failed_assertion

Expected native data result

Code: snapshot_result

Failed step: acceptance/public/evaluate-flags.feature:868

the public snapshot outcomes should have these semantics:
❌ server_snapshot_payload_zero — failed_assertion

Expected native data result

Code: snapshot_result

Failed step: acceptance/public/evaluate-flags.feature:868

the public snapshot outcomes should have these semantics:
❌ server_snapshot_payload_null — failed_assertion

Expected native data result

Code: snapshot_result

Failed step: acceptance/public/evaluate-flags.feature:868

the public snapshot outcomes should have these semantics:

Harness exit: 1; report validation exit: 1.

Capture v0 — migration

✅ 55 passed / ❌ 1 non-passing / ⚠️ 101 not selected

Drill-down: run artifacts → node-v2-v0 → reports/report.json.diagnostics.json (match case_id below).

❌ migration/yaml-parity-v1/remote-flags-v1.feature::Omitted GeoIP settings default to enabled in flag requests — failed_assertion

Flags field differs

Code: flag_request_field

Failed step: migration/yaml-parity-v1/remote-flags-v1.feature:134

the first flags request field "geoip_disable" should equal JSON false

Operation: /flags/

Field: geoip_disable

Expected:

false

Actual value:

true

Harness exit: 1; report validation exit: 1.

Capture v1 — migration

✅ 120 passed / ❌ 1 non-passing / ⚠️ 36 not selected

Drill-down: run artifacts → node-v2-v1 → reports/report.json.diagnostics.json (match case_id below).

❌ migration/yaml-parity-v1/remote-flags-v1.feature::Omitted GeoIP settings default to enabled in flag requests — failed_assertion

Flags field differs

Code: flag_request_field

Failed step: migration/yaml-parity-v1/remote-flags-v1.feature:134

the first flags request field "geoip_disable" should equal JSON false

Operation: /flags/

Field: geoip_disable

Expected:

false

Actual value:

true

Harness exit: 1; report validation exit: 1.

@github-actions

Copy link
Copy Markdown

Coherence

4 spec problems · 0 failing chokepoints · 2 spec gaps · 0 guard failures

44 components, 7 bullets (5 invariants, 2 requirements), 4 problems

Spec problems (spec --check)

  • packages/ai/src/otel/Otel.spec.md:7 an invariant bullet starts with <name>: <sentence>
  • packages/ai/src/otel/Otel.spec.md:8 an invariant bullet starts with <name>: <sentence>
  • Posthog-js.practice.md:20 practice add an invariant cites 187a5c5, which is no commit in this repository
  • Posthog-js.practice.md:20 practice add an invariant cites 8922ab2, which is no commit in this repository

Chokepoints: none declared.

Spec gaps: 2 entrances with outside or unknown trust and no traced control, and 10 more held by the adoption baseline

  • packages/web/Web.spec.md entrance public api (trust host-app): packages/web
  • packages/webpack-plugin/Webpack-plugin.spec.md entrance webpack plugin (trust build): packages/webpack-plugin

Close one with guard: <chokepoint>, an invariant that names it on its entrances: line, or control: none — <reason>; scaffold control proposes it.

@Silthus
Silthus force-pushed the emails-v2/04-code-fields branch from d509711 to f98f32c Compare October 25, 2026 10:44
@Silthus
Silthus force-pushed the emails-v2/05-emails-get branch from f08bfdb to 5539cb1 Compare October 25, 2026 10:44
Silthus and others added 3 commits October 25, 2026 19:10
Moves PostHog#5372 (feat/node-emails-get at fc5dc17) onto the code
fields slice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`.` and `..` survive `encodeURIComponent`, and URL parsing would resolve them to a
route other than the email's status. They now return `invalid_message_id` before
any request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Silthus
Silthus force-pushed the emails-v2/04-code-fields branch from f98f32c to fb9640e Compare October 25, 2026 19:11
@Silthus
Silthus force-pushed the emails-v2/05-emails-get branch from 5539cb1 to 8d8d65b Compare October 25, 2026 19:11
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