Skip to content

docs(workflows): both send forms, idempotency and the send checks on the transactional email API page - #1

Draft
Silthus wants to merge 8 commits into
docs/transactional-email-apifrom
docs/transactional-email-api-code-form
Draft

Silthus wants to merge 8 commits into
docs/transactional-email-apifrom
docs/transactional-email-api-code-form

Conversation

@Silthus

@Silthus Silthus commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

No upstream PR: this page change lives only in the fork. It stacks on Silthus/posthog.com#2, which moved here from PostHog/posthog.com#21306 (closed on 2026-10-25).

Changes

Engineers who move transactional email off Resend need one page that shows how to send from code with the PostHog client they already use. The page taught the earlier posthog-node draft (templateFile(), uploadTemplate(), "sends are not idempotent"). It now teaches the final send contract of PostHog/posthog#120921, in Node.js and Python, and every error code that the server and the SDKs emit has an anchor.

Slice 13 of Silthus/posthog#461, strand Silthus/posthog#538.

Impact

Pays into Grow Workflows through billed workflows_emails from API sends, read as Code-sending orgs and Switched orgs (not measured yet: the send attribution of PostHog/posthog#120628 plus its insight on a dedicated dashboard). In scope: Messaging, email. Expected move: small, up. Every docsUrl an SDK returns opens a section of this page.

Note

🔵 Merge risk: low · two-way door

  • Docs only: one page. No component, sidebar, or build change.
  • The page documents the contract as decided. Several behaviors are not merged yet, so hold the merge until they ship (list below).

New in aed49fa8c to 641b3bd85: the final contract

The 2026-10-22 and 2026-10-23 entries of the contract, against the server and SDK branches of the PRs below.

  • Copies, headers, tags, and attachments on the code form: a new section with a Node.js and Python example, the field table, and the rules (reserved headers, tag characters, attachment limits and blocked file types, inline images by content ID, suppressed cc and bcc addresses). None of them work with template_id.
  • Unknown fields: a field the send route doesn't take answers 400 invalid_request, with the server's attr, detail, why, and fix. The HTTP section and the invalid_request section quote it.
  • transactional_email_paused: quotes the new server text. A paused workflow is checked after the idempotency key lookup, so a retry of a queued send still gets its 202. When a stored template's workflow is deleted, the send refuses before the key lookup. Rendered content whose key names no stored template gets a new workflow instead, so a retry answers 422 idempotency_key_reused.
  • The replay scope: a replay survives a paused workflow and a changed default sender. A deleted template, a changed local template, and a missing default sender still refuse.
  • idempotency_key_reused is the 422 code on both send routes. invalid_idempotent_request is its SDK name. One set of keys covers both routes.
  • HTML-only content: PostHog derives text from html. When the HTML has no text, such as an email that is only an image, the email goes out without a plain-text part.
  • The sender: Python names it from_. sender_not_allowed (the address isn't on a verified domain) and sender_not_verified (no from, and no verified default sender) quote the server text.
  • Read an email's status: GET messaging/emails/{id}/ and posthog.emails.get(id) in Node.js, the statuses in order, the optional reason, 30 days of retention, and the 404 for a new email. posthog-python has no read yet.
  • Find the emails sent to an address: GET messaging/emails/?to=<address>, short. Newest first and paged, as the facade of PostHog/posthog#121898 returns them. The API route and its contract amendment are pending (see Open).
  • SDK local checks: invalid_form, invalid_template_id, invalid_attachment, and invalid_message_id get anchors, with posthog-node's messages. network_error gets the name service_unavailable.
  • Scopes: a key with hog_flow:write also reads an email's status. A project secret API key uploads templates, sends them, and reads an email's status.

What the page covers

  • How it works: the template form by template_id with template_data, and the code form by template_key: rendered content, and local templates that the SDK uploads when they change.
  • Set up the client: secretKey / secret_key, appHost / app_host for any host that isn't PostHog Cloud (and why the secret key never goes to an ingestion proxy), maxRetries / max_retries.
  • Send a template made in PostHog and Send an email from your code: Node.js and Python side by side (MultiLanguage tabs), rendered content, React Email rendered to html, local templates and their sync loop, rolling deploys, the sender, and copies, headers, tags, and attachments.
  • Always pass distinct_id, and why. Every send example passes it.
  • What a successful send means: 202 is queued, not delivered. The checks that would skip a send run first.
  • Idempotency: the key on every call, the replay and its scope, the in-flight 409, the 422, what the SDKs retry, max_retries=0 with a key from the email's purpose for job runners, and the promise: one send per key on retries, not exactly-once delivery.
  • Engagement capture, results and errors in the SDKs (the shared error fields, { data, error } in Node.js, EmailError in Python, the status to name table with Resend's names), read an email's status, and send over HTTP (cURL with the wire fields and the Idempotency-Key header, and a parameter-to-request table).
  • Error sections for every code the server and the SDKs emit.
  • Removed: the templateFile() and uploadTemplate() sections, posthog-node's source detection, template_file_unreadable, template_missing_subject, and "Sends are not idempotent".

Not merged yet

The page documents these as decided. Hold this PR until they ship:

  • PostHog/posthog.com#21306, the base of this PR.
  • Server, PostHog/posthog:
    • #118924: upload by key, the send route, the content hash.
    • #121083 and #121084: the send policy and the send command.
    • #121272: the code form, template_edited_in_posthog, sender_not_verified, derived text.
    • #121850: the send checks before the 202 (sender_not_allowed, recipient_suppressed, sending_suspended, template_paused).
    • #121867: idempotency on the send route.
    • #121851 and #121852: copies, headers, tags, and attachments.
    • #121896, #121897, and #121898: the email status and its read by ID. The lookup by recipient isn't built yet.
  • posthog-node, PostHog/posthog-js: #5312 (template form), #5371 (code form), #5373 (copies, headers, tags, attachments), #5372 (emails.get).
  • posthog-python: #1093 (template form) and #1101 (code form).

Evidence

  • Test seam: the anchor check, a scratch Playwright script kept out of the repo, as on docs(workflows): add the transactional email API page PostHog/posthog.com#21306. It opens the page and asserts:
    • every code the server and the SDKs emit has an element with that id, and its section quotes the server's or the SDK's strings;
    • every error name of the contract is on the page;
    • no prose names a removed or superseded behavior, such as templateFile or "so every email has a text version" (quoted server text in code blocks is exempt);
    • every send example passes a distinct ID;
    • every in-page # link resolves.
  • Strings come from the server branches of the PRs below at origin/feat/email-status-read (2cb033ad613) and origin/feat/email-send-attachments, posthog-node at feat/node-emails-get, and posthog-python at feat/emails-code-form.
  • Red on 9e73da0bf (the page before this revision), checked on the page source: 20 failures. The new invalid_request, sender_not_verified, not_found, and transactional_email_paused texts were missing, the four local check codes had no heading, and the page still said "so every email has a text version".
  • Green on the production build of 931fdb8f1, served with gatsby serve: 48/48 codes anchored with matching text, 14 send examples all pass a distinct ID, 142 in-page links resolve. 641b3bd85 changes three sentences of prose only.
  • Build: pnpm build:minimal passes: 3,982 pages, exit 0.
  • Lint: Vale 3.13.1 with the repo's config: 0 errors on the page. Its warnings on new lines name packages or quote SDK text. codespell as CI runs it (-q 8 -I .codespellignore): no change. markdownlint passed in the pre-commit hook. No em or en dashes.
  • Gaps:
    • No live send against a server with the stack deployed. The page follows the code on the branches above.
    • The red run checks the old page's source, not a render, to save a second build. The green run checks the render.
    • Screenshots are light mode only, for the reason in the earlier set.

Screenshots

Production build (pnpm build:minimal, gatsby serve) at 931fdb8f1, light, 1440 px. Example data only: example.com addresses and invented IDs.

Section Screenshot
Add copies, headers, tags, and attachments Copies, headers, tags, and attachments
Idempotency: the replay scope and one set of keys Idempotency
Read an email's status, find the emails sent to an address Read an email's status
invalid_request: unknown fields, copies, headers, tags, attachments invalid_request
transactional_email_paused (before 641b3bd85 scoped the deleted-workflow sentence) transactional_email_paused
The SDK's local checks Local checks
docs_url for invalid_attachment, opened by plain navigation Error anchor
Anchor check output (green, production build of 931fdb8)
GET http://localhost:8538/docs/workflows/transactional-email-api -> 200
ok   #template_outdated (H3, 5 API strings)
ok   #template_not_found (H3, 2 API strings)
ok   #sender_not_verified (H3, 11 API strings)
ok   #recipient_suppressed (H3, 2 API strings)
ok   #sending_suspended (H3, 4 API strings)
ok   #template_paused (H3, 4 API strings)
ok   #template_edited_in_posthog (H3, 3 API strings)
ok   #idempotency_key_in_flight (H3, 1 API strings)
ok   #idempotency_key_reused (H3, 1 API strings)
ok   #invalid_idempotency_key (H3, 1 API strings)
ok   #invalid_recipient (H3, 1 API strings)
ok   #quota_limited (H3, 1 API strings)
ok   #delivery_unavailable (H3, 1 API strings)
ok   #invalid_request (H3, 9 API strings)
ok   #template_superseded (H3, 2 API strings)
ok   #invalid_template_key (H3, 0 API strings)
ok   #template_not_sendable (H3, 0 API strings)
ok   #sender_unavailable (H3, 0 API strings)
ok   #marketing_template (H3, 0 API strings)
ok   #key_already_set (H3, 0 API strings)
ok   #key_in_use (H3, 0 API strings)
ok   #immutable (H3, 0 API strings)
ok   #not_found (H3, 2 API strings)
ok   #authentication_failed (H3, 0 API strings)
ok   #permission_denied (H3, 0 API strings)
ok   #throttled (H3, 0 API strings)
ok   #template_not_transactional (H3, 0 API strings)
ok   #transactional_email_paused (H3, 4 API strings)
ok   #instruction_missing (H3, 0 API strings)
ok   #template_not_managed_by_code (H3, 0 API strings)
ok   #template_repository_missing (H3, 0 API strings)
ok   #template_repository_unsupported (H3, 0 API strings)
ok   #github_repository_not_connected (H3, 0 API strings)
ok   #posthog_code_not_available (H3, 0 API strings)
ok   #posthog_code_run_failed (H3, 0 API strings)
ok   #sender_not_allowed (H3, 3 API strings)
ok   #invalid_idempotent_request (H3, 0 API strings)
ok   #missing_secret_key (H4, 0 API strings)
ok   #missing_app_host (H4, 0 API strings)
ok   #app_host_required (H4, 0 API strings)
ok   #client_disabled (H4, 0 API strings)
ok   #network_error (H4, 0 API strings)
ok   #api_error (H4, 0 API strings)
ok   #unexpected_error (H4, 0 API strings)
ok   #invalid_form (H4, 0 API strings)
ok   #invalid_template_id (H4, 0 API strings)
ok   #invalid_message_id (H4, 0 API strings)
ok   #invalid_attachment (H4, 0 API strings)
14 send examples, 142 in-page links
48/48 codes anchored, all checks green
Anchor check output (red, source of 9e73da0)
== 9e73da0bf: 20 failures
  FAIL #sender_not_verified: not quoted: "The send names no `from`, and the project has no verified default sender to send from."
  FAIL #sender_not_verified: not quoted: "Pass `from` with an address on a verified domain, or set a default sender in the email cha"
  FAIL #invalid_request: not quoted: "PostHog doesn't take this field on this route."
  FAIL #invalid_request: not quoted: "so it never sends the email without part of what you asked for."
  FAIL #invalid_request: not quoted: "is not a bare address. Send it as `[email protected]`."
  FAIL #invalid_request: not quoted: "itself. Use the send's own fields, such as `from`, `cc` or `subject`, instead."
  FAIL #invalid_request: not quoted: "Use only ASCII letters, numbers, `_` and `-`."
  FAIL #invalid_request: not quoted: "Attachments can hold 512KB in total. Send a link to larger files."
  FAIL #invalid_request: not quoted: "files. Send a link to the file instead."
  FAIL #not_found: not quoted: "PostHog keeps an email's status for 30 days. A new email can take a few seconds to show up"
  FAIL #not_found: not quoted: "Check the `message_id` the send answered and the project, or read again in a few seconds."
  FAIL #transactional_email_paused: not quoted: "The email's workflow isn't an active transactional email."
  FAIL #transactional_email_paused: not quoted: "PostHog sends a transactional email only while its workflow is active."
  FAIL #transactional_email_paused: not quoted: "Turn the email's workflow back on, then send again."
  FAIL #invalid_form: no heading
  FAIL #invalid_template_id: no heading
  FAIL #invalid_message_id: no heading
  FAIL #invalid_attachment: no heading
  FAIL still says "templateFile"
  FAIL still says "so every email has a text version"
Earlier evidence, before the final contract (ce1b5ea to 6c55cf4)

Local dev server (GATSBY_MINIMAL=true pnpm start) at ce1b5ea56, before the review fixes, light, 1440 px. Example data only: example.com addresses and invented IDs.

Section Screenshot
Set up the client Set up the client
Send an email from your code: rendered content and React Email Send rendered content
Send a local template, its sync loop, rolling deploys Send a local template
Always pass distinct_id, what a successful send means distinct_id and 202
Idempotency Idempotency
Engagement capture, results and errors in the SDKs Errors in the SDKs
sender_not_verified to sending_suspended Send check errors
template_paused to idempotency_key_reused Idempotency errors
Errors from the SDKs SDK errors
docs_url for template_edited_in_posthog, opened by plain navigation Error anchor
Anchor check output (green, b7c65ae)
GET http://localhost:8538/docs/workflows/transactional-email-api -> 200
ok   #template_outdated (H3, 5 API strings)
ok   #template_not_found (H3, 2 API strings)
ok   #sender_not_verified (H3, 12 API strings)
ok   #recipient_suppressed (H3, 2 API strings)
ok   #sending_suspended (H3, 4 API strings)
ok   #template_paused (H3, 4 API strings)
ok   #template_edited_in_posthog (H3, 3 API strings)
ok   #idempotency_key_in_flight (H3, 1 API strings)
ok   #idempotency_key_reused (H3, 1 API strings)
ok   #invalid_idempotency_key (H3, 1 API strings)
ok   #invalid_recipient (H3, 1 API strings)
ok   #quota_limited (H3, 1 API strings)
ok   #delivery_unavailable (H3, 1 API strings)
ok   #invalid_request (H3, 2 API strings)
ok   #template_superseded (H3, 2 API strings)
ok   #invalid_template_key (H3, 0 API strings)
ok   #template_not_sendable (H3, 0 API strings)
ok   #sender_unavailable (H3, 0 API strings)
ok   #marketing_template (H3, 0 API strings)
ok   #key_already_set (H3, 0 API strings)
ok   #key_in_use (H3, 0 API strings)
ok   #immutable (H3, 0 API strings)
ok   #not_found (H3, 0 API strings)
ok   #authentication_failed (H3, 0 API strings)
ok   #permission_denied (H3, 0 API strings)
ok   #throttled (H3, 0 API strings)
ok   #template_not_transactional (H3, 0 API strings)
ok   #transactional_email_paused (H3, 0 API strings)
ok   #instruction_missing (H3, 0 API strings)
ok   #template_not_managed_by_code (H3, 0 API strings)
ok   #template_repository_missing (H3, 0 API strings)
ok   #template_repository_unsupported (H3, 0 API strings)
ok   #github_repository_not_connected (H3, 0 API strings)
ok   #posthog_code_not_available (H3, 0 API strings)
ok   #posthog_code_run_failed (H3, 0 API strings)
ok   #sender_not_allowed (H3, 0 API strings)
ok   #invalid_idempotent_request (H3, 0 API strings)
ok   #missing_secret_key (H4, 0 API strings)
ok   #missing_app_host (H4, 0 API strings)
ok   #app_host_required (H4, 0 API strings)
ok   #client_disabled (H4, 0 API strings)
ok   #network_error (H4, 0 API strings)
ok   #api_error (H4, 0 API strings)
ok   #unexpected_error (H4, 0 API strings)
12 send examples, 112 in-page links
44/44 codes anchored, all checks green
Anchor check output (red, 2b0a7a2)
GET http://localhost:8538/docs/workflows/transactional-email-api -> 200
FAIL #template_outdated: not quoted: "PostHog holds no content for this key yet."
FAIL #template_outdated: not quoted: "The template never had this content, so sending its current content could send the wrong email."
FAIL #template_not_found: not quoted: "A send names a template of this project that is not deleted."
FAIL #sender_not_verified: no element with this id
FAIL #recipient_suppressed: no element with this id
FAIL #sending_suspended: no element with this id
FAIL #template_paused: no element with this id
FAIL #template_edited_in_posthog: no element with this id
FAIL #idempotency_key_in_flight: no element with this id
FAIL #idempotency_key_reused: no element with this id
FAIL #invalid_idempotency_key: no element with this id
ok   #invalid_recipient (H3, 1 API strings)
ok   #quota_limited (H3, 1 API strings)
ok   #delivery_unavailable (H3, 1 API strings)
FAIL #invalid_request: not quoted: "A send needs a template, a recipient address, and data as an object."
FAIL #invalid_request: not quoted: "Fix `template` and send again."
ok   #template_superseded (H3, 2 API strings)
ok   #invalid_template_key (H3, 0 API strings)
ok   #template_not_sendable (H3, 0 API strings)
ok   #sender_unavailable (H3, 0 API strings)
ok   #marketing_template (H3, 0 API strings)
ok   #key_already_set (H3, 0 API strings)
ok   #key_in_use (H3, 0 API strings)
ok   #immutable (H3, 0 API strings)
ok   #not_found (H3, 0 API strings)
ok   #authentication_failed (H3, 0 API strings)
ok   #permission_denied (H3, 0 API strings)
ok   #throttled (H3, 0 API strings)
ok   #template_not_transactional (H3, 0 API strings)
ok   #transactional_email_paused (H3, 0 API strings)
ok   #instruction_missing (H3, 0 API strings)
ok   #template_not_managed_by_code (H3, 0 API strings)
ok   #template_repository_missing (H3, 0 API strings)
ok   #template_repository_unsupported (H3, 0 API strings)
ok   #github_repository_not_connected (H3, 0 API strings)
ok   #posthog_code_not_available (H3, 0 API strings)
ok   #posthog_code_run_failed (H3, 0 API strings)
FAIL #sender_not_allowed: no element with this id
FAIL #invalid_idempotent_request: no element with this id
ok   #missing_secret_key (H4, 0 API strings)
FAIL #missing_app_host: no element with this id
FAIL #app_host_required: no element with this id
ok   #client_disabled (H4, 0 API strings)
ok   #network_error (H4, 0 API strings)
ok   #api_error (H4, 0 API strings)
ok   #unexpected_error (H4, 0 API strings)
FAIL error name `validation_error` not on the page
FAIL error name `missing_api_key` not on the page
FAIL error name `invalid_permission` not on the page
FAIL error name `concurrent_idempotent_requests` not on the page
FAIL error name `invalid_idempotent_request` not on the page
FAIL error name `rate_limit_exceeded` not on the page
FAIL error name `monthly_quota_exceeded` not on the page
FAIL error name `service_unavailable` not on the page
FAIL error name `application_error` not on the page
FAIL prose still mentions "templateFile"
FAIL prose still mentions "uploadTemplate"
FAIL prose still mentions "Sends are not idempotent"
FAIL prose still mentions "template_file_unreadable"
2 send examples, 63 in-page links
30 failures

Open contract questions

Each is a place where the page, the contract, and the code don't agree yet. The page follows the contract unless the line says otherwise.

  1. posthog-python vs the contract (PostHog/posthog-python#1101): it answers app_host_required, not missing_app_host, the client's retry option is email_request_max_retries, send takes no per-send max_retries (so the job runner example doesn't run there yet), and the result field is id, where the contract says message_id. The page documents the contract names, uses result.id, and anchors app_host_required too.
  2. No source on SDK uploads. The spec says the SDK's upload "carries no link to the code", so the page drops posthog-node's source detection. A local template that only an SDK uploaded then has no repository, and Run with PostHog Code refuses it with template_repository_missing. Confirm, or the code form keeps source detection.
  3. Server texts that still name the old flow (quoted verbatim, so the page can't fix them): the template_repository_missing fix says "posthog-node detects it from the git checkout or the CI environment", and the change prompt says posthog.emails.templateFile(). Both need a server change in the change request stack.
  4. replayed in Node.js. The server returns replayed, and posthog-python exposes it. posthog-node's data is { id, warnings }, as in the contract. The page mentions replayed for Python and HTTP only.
  5. Copy HTML file. The copied file holds the subject in <title>. Sending it as a local template needs the app to pass subject and html separately, and whether html keeps the <title> changes the hash. The page says to pass the subject as subject. The byte-for-byte round trip isn't proven.
  6. The lookup by recipient (pending). The contract still lists the lookup by address under "Not in this contract", and no API route serves it yet. PostHog/posthog#121898 has the facade function (newest first, cursor pages). The page gives the route, the scope and rate limits of the read by ID, and one cURL example. Its paging parameters and errors follow once the contract amendment lands.
Closed since the last revision
  • The 422 code: idempotency_key_reused on both send routes, with the SDK name invalid_idempotent_request.
  • from in Python: from_.
  • sender_not_allowed: the send checks define it, with server text. The page quotes it.
  • Overlapping codes: transactional_email_paused (the workflow) and template_paused (the send policy) both stay, with separate meanings. So do sender_unavailable (the upload) and sender_not_verified (the send).
  • The name of network_error: service_unavailable.
  • posthog-python's app_host: the client takes it now. The rest of that question stays open as question 1.

Decisions

Question Answer Who Where
Where does the page change land? A draft PR on the fork, stacked on the fork mirror of PostHog#21306, instead of commits on PostHog#21306 itself. 🤖 Agent pending approval
What does the page document before the code ships? The contract of PostHog/posthog#120921 as decided. Unmerged PRs are listed here, not on the page, and the merge waits for them. 🤖 Agent pending approval
Which code does an error section use when the contract and the server differ? Both get an anchor, so every docs_url the server builds today and after the contract lands opens a section. 🤖 Agent pending approval
Does the page keep posthog-node's source detection? No. The spec says an SDK upload carries no link to the code. Open question 2 asks to confirm. 🤖 Agent pending approval
What does the page call the Python sender parameter? from_, as the contract now names it. Replaces from with no Python example. 🤖 Agent pending approval
How much of the lookup by recipient does the page document? The route and one cURL example, marked as being built, until the contract amendment lands. 🤖 Agent pending approval

Open: the 6 contract questions above.

Involvement: 0 grilling rounds, 0 prototypes, 2 GPT-6.1 Sol review rounds (3 findings in the first, 2 in the second, all fixed), 6 final reads of the fixes (reads 1, 2, 4, and 5 found 1 finding each, all fixed; read 3 ran after Michael overrode the arbiter and was clean; read 6 was clean), proof captured (anchor check red and green, production build, Vale, codespell, markdownlint, screenshots).

Checklist

  • I've read the docs and/or content style guides.
  • Words are spelled using American English
  • Use relative URLs for internal links
  • I've checked the pages added or changed in the Vercel preview build
  • If I moved a page, I added a redirect in vercel.json

Written by Claude Opus 5.5 (claude-opus-5-5) as the docs-api build lane of the map. Reviewed by GPT-6.1 Sol (high).

🤖 Generated with Claude Code

Silthus added a commit that referenced this pull request Oct 22, 2026
Silthus added a commit that referenced this pull request Oct 22, 2026
Silthus added a commit that referenced this pull request Oct 22, 2026
Silthus added a commit that referenced this pull request Oct 22, 2026
Silthus added a commit that referenced this pull request Oct 22, 2026
Silthus added a commit that referenced this pull request Oct 22, 2026
Silthus added a commit that referenced this pull request Oct 22, 2026
Silthus added a commit that referenced this pull request Oct 22, 2026
Silthus added a commit that referenced this pull request Oct 22, 2026
Silthus added a commit that referenced this pull request Oct 22, 2026
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Vale prose linter → found 0 errors, 38 warnings, 1 suggestions in your markdown

Full report → Copy the linter results into an LLM to batch-fix issues.

Linter being weird? Update the rules!

contents/docs/workflows/transactional-email-api.mdx — 0 errors, 38 warnings, 1 suggestions
Line Severity Message Rule
22:170 warning Capitalize 'Logs' for PostHog's product. Use 'logs' for the general industry concept. PostHogBase.ProductNames
57:1 warning Use 'PostHog' instead of 'posthog'. Vale.Terms
76:68 warning 'awaitable' is a possible misspelling. PostHogBase.Spelling
120:273 warning Capitalize 'Logs' for PostHog's product. Use 'logs' for the general industry concept. PostHogBase.ProductNames
221:190 warning Capitalize 'Logs' for PostHog's product. Use 'logs' for the general industry concept. PostHogBase.ProductNames
304:98 suggestion Address the reader directly. Use 'you' instead of 'the user'. PostHogDocs.DirectAddress
308:139 warning 'admin's' is a possible misspelling. PostHogBase.Spelling
319:31 warning Use 'ID' instead of 'id'. Vale.Terms
412:45 warning 'Resend's' is a possible misspelling. PostHogBase.Spelling
421:86 warning 'Resend's' is a possible misspelling. PostHogBase.Spelling
421:170 warning 'Resend's' is a possible misspelling. PostHogBase.Spelling
472:207 warning 'Resend's' is a possible misspelling. PostHogBase.Spelling
476:268 warning Use 'PostHog' instead of 'posthog'. Vale.Terms
482:208 warning 'send's' is a possible misspelling. PostHogBase.Spelling
508:32 warning Use 'ID' instead of 'id'. Vale.Terms
561:4 warning Use 'ID' instead of 'id'. Vale.Terms
561:54 warning 'send's' is a possible misspelling. PostHogBase.Spelling
588:60 warning 'preheader' is a possible misspelling. PostHogBase.Spelling
588:192 warning 'preheader' is a possible misspelling. PostHogBase.Spelling
593:42 warning 'preheader' is a possible misspelling. PostHogBase.Spelling
596:131 warning 'preheader' is a possible misspelling. PostHogBase.Spelling
676:55 warning 'preheader' is a possible misspelling. PostHogBase.Spelling
763:54 warning 'preheader' is a possible misspelling. PostHogBase.Spelling
773:4 warning 'upload's' is a possible misspelling. PostHogBase.Spelling
841:44 warning 'send's' is a possible misspelling. PostHogBase.Spelling
902:134 warning Use 'ID' instead of 'id'. Vale.Terms
915:256 warning Use 'PostHog' instead of 'posthog'. Vale.Terms
925:404 warning Use 'ID' instead of 'id'. Vale.Terms
944:30 warning Capitalize 'Workflows' for PostHog's product. Use 'workflows' for the general industry concept. PostHogBase.ProductNames
945:30 warning Capitalize 'Workflows' for PostHog's product. Use 'workflows' for the general industry concept. PostHogBase.ProductNames
1083:80 warning 'Resend's' is a possible misspelling. PostHogBase.Spelling
1121:97 warning Use 'PostHog' instead of 'posthog'. Vale.Terms
1190:311 warning Capitalize 'Logs' for PostHog's product. Use 'logs' for the general industry concept. PostHogBase.ProductNames
1224:21 warning Use 'PostHog' instead of 'posthog'. Vale.Terms
1228:93 warning 'templateId' is a possible misspelling. PostHogBase.Spelling
1228:108 warning 'templateKey' is a possible misspelling. PostHogBase.Spelling
1228:137 warning Use 'HTML' instead of 'html'. Vale.Terms
1232:46 warning 'templateId' is a possible misspelling. PostHogBase.Spelling
1240:82 warning Use 'ID' instead of 'id'. Vale.Terms

Silthus and others added 8 commits October 23, 2026 13:39
…hecks

The page now follows the flat send contract of PostHog/posthog#120921 for
posthog-node and posthog-python: the template form by template_id, the code
form by template_key with rendered content, React Email and local templates,
client setup with secret_key, app_host and max_retries, what a 202 means,
idempotency and the SDK retries, engagement capture, and the error names.

New anchored sections cover the send checks, the idempotency codes and
missing_app_host. The file helpers of the earlier posthog-node draft are gone.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fail the job in the job runner example, match the cURL hash to the upload
example, and say that adoption needs an upload without if_code_managed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A send whose content PostHog already holds keeps working after people
edit the template in PostHog. Only a send with changed content fails
with template_edited_in_posthog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Before an upload claims a template that people edit in PostHog, a local
template send whose hash PostHog remembers still goes out, with
template_superseded. Only content PostHog never published needs an
upload and fails with template_edited_in_posthog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…l email page

Copies, headers, tags and attachments on the code form, the status read by
ID and by recipient, the unknown-field `invalid_request`, the paused-workflow
`transactional_email_paused` after the key lookup, the replay scope, `422
idempotency_key_reused` on both routes, HTML without text, `from_` in Python,
`sender_not_allowed`, and the SDK's local check codes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Silthus added a commit that referenced this pull request Oct 23, 2026
Silthus added a commit that referenced this pull request Oct 23, 2026
Silthus added a commit that referenced this pull request Oct 23, 2026
Silthus added a commit that referenced this pull request Oct 23, 2026
Silthus added a commit that referenced this pull request Oct 23, 2026
Silthus added a commit that referenced this pull request Oct 23, 2026
Silthus added a commit that referenced this pull request Oct 23, 2026
@Silthus
Silthus force-pushed the docs/transactional-email-api branch from 2b0a7a2 to 8ee6d24 Compare October 23, 2026 14:25
@Silthus
Silthus force-pushed the docs/transactional-email-api-code-form branch from 6c55cf4 to 641b3bd Compare October 23, 2026 14:25
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