Skip to content

Docs: transactional email API page for code-managed templates, an anchor per error code #475

Closed

Description

@Silthus

Map: Map: send email from code with one PostHog call
Spec: Spec: code-managed email templates, sections "Docs page" and "Error catalog" (retargeted from the superseded #467)

Cast: Opus 5.5, high
Repository: PostHog/posthog.com

Question

Can an engineer or agent learn the whole code-managed template flow from one page, and does every docs_url the API returns open the fix for that error?

Delivers end to end. One beta page at /docs/workflows/transactional-email-api: what it is for, the project secret API key with hog_flow:write, the flag (in beta, enabled per project), the template format (Liquid, {{ variables.* }}, no person properties: sends carry variables only), sending from a file with posthog-node (the content hash flow, the rolling-deploy rule and the template_superseded warning, uploadTemplate for a rollback, how to ship the file with serverless bundles), upload by key, the send route /messaging/send/email/ by key and by id, the UUID route, "Managed by code" and the lock, adoption of an existing template, a cURL example, a plain statement that sends are not idempotent, and one anchored section per error code and warning in the spec's catalog, old and new. Every example sends from the reader's own domain. Its markdown twin serves the agent skill's shared docs. The copyable agent prompt and the llms.txt line stay out, as the map says.

Test seams. The site build. A link check that every code in the spec's catalog has an anchor on the page.

Write scope. A new page under contents/docs/workflows/ and its sidebar entry in PostHog/posthog.com.

Proof.

  • Red: a docs_url from a local error response (template_not_found) opens a 404.
  • Green: it opens the matching section on the local site build.
  • Local: screenshot of the page and of one error anchor in the PR.

Acceptance criteria

  • Every catalog code has an anchor whose fix text matches the API message.
  • The page says sends are not idempotent and carry variables only.
  • The page says the feature is in beta and needs to be enabled for the project.

How to build (map #461)

  • /tdd red-green-refactor, outside-in, at the seams named above. Real Postgres, Valkey and ClickHouse; mock only what the seam section allows.
  • Repo skills apply where the diff touches their area: /writing-tests, /improving-drf-endpoints, /django-migrations, /adding-api-scopes, /implementing-mcp-tools, /writing-ui-components, /writing-kea-logics, /writing-user-facing-copy, /writing-code-comments.
  • Read code from upstream/master (PostHog/posthog). The fork's master is stale.
  • Public repo: no usage numbers, customer names, internal metrics, Slack content or security reasoning in code, commits or the PR. Fixture addresses use example.com. The private note the map points at is background only; never quote it.
  • Shipped means a draft PR handed off for review (see the map's Notes for remotes and branches). Never mark ready, enqueue or merge.

Activity

  1. changed the title [-]Docs: email API reference page with an anchor per error code[/-] [+]Docs: transactional email API page for code-managed templates, an anchor per error code[/+] on Oct 16, 2026
  2. self-assigned this
    on Oct 17, 2026
  3. Silthus commented on Oct 17, 2026

    @Silthus
    OwnerAuthor

    Runtime: T3 child task
    Task: node:delegated-task:command%3Amcp%3A955e36b3-3986-469c-b816-09cf656780af%3Adelegate-task%3Awf-email-impl-475-2
    Cast: Opus 5.5, high. Cap 180 minutes.

  4. Silthus commented on Oct 17, 2026

    @Silthus
    OwnerAuthor

    Resolution

    Shipped: draft PostHog/posthog.com#21306, branch Silthus:docs/transactional-email-api against master. Reviewed head c86c7a6a9894864505bb828b5df228a1523ff47b. fleet proof gate passes on it (live-run evidence at HEAD, HEAD reviewed in final read 3).

    What shipped

    • contents/docs/workflows/transactional-email-api.mdx: one beta page at /docs/workflows/transactional-email-api.
      • The beta note (enabled per project, ask in-app), the project secret API key with hog_flow:write, and the template format (Liquid, {{ variables.* }}, no person properties).
      • posthog-node: templateFile, send, the content hash flow, rolling deploys and template_superseded, uploadTemplate for a rollback, serverless bundles (Next.js, Vercel, Netlify), and edge runtimes.
      • The routes: upload by key (with if_not_superseded and the superseded result), the send route by key and by ID, the content hash algorithm with the spec's test vector, and the older ID route.
      • "Sends are not idempotent", rate limits, templates managed by code (tag, read-only preview, lock, release), adoption, and a cURL example for each route.
      • 27 anchored sections: every catalog code, the template_superseded warning, and every posthog-node code.
    • src/navs/index.js: Workflows › Transactional email › Send email from code, with a beta badge.
    • The site builds the markdown twin (.md) at build time, so the skill can read it. No copyable agent prompt and no llms.txt line, as the map says.

    Proof

    • Red: before the page existed, the docs_url from a real template_not_found response, opened on the local dev server, answered 404 (the Gatsby development 404 page). The anchor check found 0/27.
    • Green on c86c7a6a9: 200, and a plain navigation lands on the #template_not_found section. The anchor check finds 27/27 codes, each section quoting the server's detail and fix strings verbatim from 7845707ba33.
    • Mutation: changing the quoted key_in_use fix sentence made the check fail with 26/27.
    • Lint: Vale 3.13.1 with the repo config: no errors. Left: dictionary gaps and verbatim API quotes. markdownlint-cli2: 0 errors. The pre-commit hooks passed. All commits are signed (G).
    • Screenshots of the page, the anchor, and the sidebar before and after (light and dark, wide and narrow) are in the PR body.
    • Evidence: ~/.local/state/fleet/proof/PostHog/posthog.com/21306/c86c7a6a9894864505bb828b5df228a1523ff47b/live-run.

    What I ran: the docs dev server with GATSBY_MINIMAL=true pnpm start through fleet heavy (pnpm, as the repo prescribes), not a full production build.

    Review

    Opus 5.5, high effort, fresh read-only agents. Each round was recorded with fleet proof review-start and review-finish.

    • Round 1: 10 findings (2 medium, 8 low). All held and are fixed.
    • Final read 1: 3 new low findings in the fix lines. Fixed.
    • Final read 2: 1 new low finding. Fixed.
    • Final read 3: PASS.

    Calls made

    • The code wins over the spec catalog. Each difference:
      • immutable is live now, with a read-only preview and Stop managing in code (Email templates: refuse edits to code-managed templates on the server #491, Email templates: read-only view of code-managed templates #492).
      • Only seven codes carry why, fix, and docs_url. The existing send-pipeline codes and the shared API codes have only type, code, detail, and attr, so the page gives their fix.
      • invalid_request also covers hash with id, and key sent with other fields on adoption.
      • authentication_failed and permission_denied are two sections. A missing header answers not_authenticated.
      • template_superseded uses the server's wording. In posthog-node it is { code, message }, without fix.
      • The SDK adds api_error and unexpected_error.
      • The beta flag gates only the upload and send routes, not adoption or release.
      • The per-project rate limit counts only project secret API key requests.
    • Email templates: an automatic upload never republishes a superseded hash #499 landed during this ticket (7845707ba33). The page documents if_not_superseded and superseded as landed, so nothing in the PR is marked pending.
    • The anchor check stays out of the repo. A posthog.com content PR changes only contents/ and the nav. The script and its output are in the PR body and the proof storage.
    • SDK-only codes get anchors too, so every error.code from posthog-node has a fix.
    • The Node setup recommends enableLocalEvaluation: false, because secretKey also starts feature flag polling, which needs feature_flag:read.
    • Personal API key fallback: project secret API keys are in beta, so the page documents personal API keys for the HTTP routes, with the project ID in the URL. posthog-node always calls @current.
    • Screenshots: gh pr-assets and gh --attach need write access to PostHog repos, which this account lacks. They are published to the public fork with fleet proof publish instead.

    Local setup (nothing in the diff)

    • Node 22 (the repo's engine) in a private directory, because the box has Node 24.
    • PHANTOMJS_PLATFORM=linux PHANTOMJS_ARCH=x64, because phantomjs-prebuilt has no arm64 build.
    • DEBUG unset, because webpack inlines process.env.DEBUG into the SSR bundle.
    • A private GIT_CONFIG_GLOBAL for the dev server only, because the global url.insteadOf rewrite breaks the gatsby-source-git "already cloned" check on a restart.

    Follow-ups

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

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions