Repository navigation
Conversation
This was referenced Oct 22, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es and comments Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The upload after `template_outdated` now sets `if_not_superseded`. When a newer deploy replaced the file's content between the send and the upload, PostHog changes nothing and the one resend returns the `template_superseded` warning. `uploadTemplate` still publishes replaced content, so it stays the rollback path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…plate file An upload of a template file now sends source.repository, source.path and source.ref without configuration. Each part comes from the explicit source option, then from GitHub Actions, GitLab CI, Vercel or Railway variables, then from the nearest git checkout above the file. The checkout is read as files, without running git, and credentials in a remote URL never leave the process. Detection never fails a send. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ments Source detection read any file named like a git file, so a pipe in a .git directory could hang a send. It now reads only regular files up to 4 MiB. A Vercel provider named like an object property, such as "constructor", no longer throws, and a quoted origin URL followed by a comment is read the way git reads it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
posthog.emails.send now takes the flat template form of the shared SDK contract: templateId with templateData, posted to the template send route. Every send carries an Idempotency-Key, a UUID v4 unless the caller gives one, and retries network errors, an in-flight key, throttling and 503 with the same key. A monthly quota is never retried. Errors carry Resend's names, why and fix. New client options appHost, maxRetries and migratedFrom. The secret key only goes to the app host: derived for PostHog Cloud, required for any other host. The unreleased template field, templateFile() and uploadTemplate() go away. The content hash stays for local templates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…refuses A send to a workflow that isn't active answers 400 transactional_email_paused, a validation_error. The idempotency key docs now say a replay survives a paused workflow or a changed default sender, and still refuses a deleted or changed template or a project with no default sender (PostHog/posthog#120921). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A send with options that are not an object returns invalid_form instead of throwing. - A non-string idempotencyKey returns invalid_idempotency_key. - A maxRetries that is not a whole number from 0 falls back to the client option, then to 2, so NaN or Infinity can't retry forever. - A queued 202 whose body breaks off while it is read is a network error, so the send retries with the same key and returns the first id. - A network error keeps its cause in the message. - The secretKey docs say to turn off local evaluation for a key that can't read feature flags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 of 10 tasks
Silthus
force-pushed
the
feat/node-emails-code-fields
branch
from
October 23, 2026 14:32
db5d59a to
af5dd7e
Compare
…fused connection's cause - A send with an option posthog-node doesn't take, such as Resend's scheduledAt, returns invalid_form on that field instead of sending without it. - A refused connection to a host with two addresses keeps both causes in the network error message. - Core parses a JSON answer with trySafe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Silthus
force-pushed
the
feat/node-emails-code-fields
branch
from
October 23, 2026 14:53
af5dd7e to
a5c25ec
Compare
A send after shutdown(), or while it drains, returns client_closed, named application_error, before any request, as the shared contract's 2026-10-24 entry asks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y hash posthog.emails.send takes the code form of the shared SDK contract: templateKey, subject, html and/or text, with optional from, replyTo and captureEngagement. The types refuse a send that mixes the two forms. Without templateData the content is rendered and goes to the send route as is, never uploaded. With templateData it is a local template: the SDK sends its content hash, and on 409 template_outdated uploads it by key with if_code_managed and sends once more with the same idempotency key. An upload refused because the key was edited in PostHog returns its error and is never retried. Template sends by id move to the send route as template.id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A local template send read its options after hashing the content, so a caller that changed the same options object before the send finished sent to the new recipient. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d's data - A template send with from, replyTo, subject, html or text returns invalid_form on that field, instead of sending without it. - A code send without a subject, or without html or text, returns invalid_form before any request. - A local template send copies its request body before it hashes the content, so a caller that changes templateData during the send changes nothing. - The round-trip tests expect a fixed content hash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The code form of emails.send takes the fields the contract added on 2026-10-22. Addresses become lists, attachment bytes become Base64 once per send, and a Resend attachment path returns invalid_attachment before any request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A PostHog server that doesn't take cc, bcc, headers, tags or attachments yet answers 400 invalid_request with the field in attr, so the send fails as a validation_error on that field instead of going out without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e send A template send with one of these fields returns invalid_form on that field, instead of sending without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Silthus
force-pushed
the
feat/node-emails-code-fields
branch
from
October 24, 2026 12:43
a5c25ec to
abeb197
Compare
This was referenced Oct 24, 2026
Closed
Closed
This was referenced Oct 25, 2026
Silthus
added a commit
to Silthus/posthog-js
that referenced
this pull request
Oct 25, 2026
Moves PostHog#5373 (feat/node-emails-code-fields at abeb197) onto the local template slice: both code-form cases take copies, custom headers, tags and attachments, an older server's refusal names the field, and the template form refuses them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A Resend call site that sends a copy, a custom header, a tag or a file can't move to
posthog.emails.send(): the code form takes only the content, the sender andreplyTo.This PR adds
cc,bcc,headers,tagsandattachmentsto the code form, as the shared SDK contract PostHog/posthog#120921 added them on 2026-10-22:Follow-up slice of strand Silthus/posthog#536, map Silthus/posthog#461.
Stacked on #5371 (code form), on #5312. #5372 (
emails.get) now stacks on this PR. This fork PR targetsmain, so its diff includes the PRs below it. This layer alone:feat/node-emails-code-form...feat/node-emails-code-fields.Server dependency, not merged: the fields come from PostHog/posthog#121851 (
cc,bcc,headers,tags) and PostHog/posthog#121852 (attachments), stacked on #121272. This PR is built against the contract and those serializers at the HTTP seam.Impact
Pays into Grow Workflows through billed
workflows_emailsfrom API sends, read as orgs that switched from Resend (not measured yet: the send attribution of PostHog/posthog#120628 plus its insight). In scope: Messaging, email. Expected move: small, up: it removes a blocker for Resend call sites that use these fields. Run cost: n/a.Merge Danger
Caution
🔴 Merge risk: high · one-way door (merge-risk rubric)
cc,bcc,headers,tagsandattachmentsto the public code form. Once released, the field names and attachment encoding are a contract, and a send delivers a real email, with its copies, to real recipients.Changes
Code form only. The new fields work with rendered content and with a local template. The template form's types refuse them, and at runtime a template send with one of them returns
invalid_formon that field instead of sending without it.On the wire. Each field is left out when absent.
cc,bccheaders,tagsattachments{ filename, content, content_type?, content_id? }. ABufferorUint8Arraybecomes Base64; a string goes as isOnce per send. The SDK encodes attachments when it builds the body, before the retry loop and before a local template's hash. Retries and the resend after a local-template upload send the same body. The upload never carries these fields.
An older server. A server that doesn't take one of these fields yet answers
400 invalid_requeston it; the error names it infield, so the caller sees which field to drop.A Resend
path(a URL or file path to fetch) returnsinvalid_attachment, namedvalidation_error, withfield: attachments__<n>__path, before any request. So does an attachment withoutBuffer,Uint8Arrayor string content, orattachmentsthat is not a list. The types refusepathtoo.Content stays private. The SDK never logs attachment content, and no error repeats it.
Limits stay on the server. The SDK checks types only; the server refuses more than 10 copies, reserved headers, bad tag names, blocked file types and more than 512 KB.
Parity with the contract
cc,bcc: string or listheaders: maptags:{ name, value }[]attachments:{ filename, content, content_type?, content_id? }contentBase64 from aBufferorUint8Array, a string as isinvalid_formEvidence
emailsnamespace on a real client,fetchmocked byFakeEmailsApi(emails.spec.ts).tscwith@ts-expect-errorcases (__tests__/types/emails-forms.ts).Buffer, aUint8Array, a Base64 string, and a 40 KB file that crosses the encoder's chunk size.invalid_attachmentwithout a request, for apath, for content of another type, and for anattachmentsobject that is not a list.invalid_formwith itsfieldfor each new field on the template form, without a request.400 invalid_requestnamedvalidation_error, with itsfield.ccandattachments.path.abeb1979(on the leased compute box): nodevitest(1336 passed), the edge build tests (1336 passed),tscfor the build and the type tests,oxlint,oxfmt, build andgenerate-references; corevitest.invalid_request): gap. feat(messaging): send cc, bcc, headers and tags with a code send posthog#121851 and feat(messaging): send attachments with a code send posthog#121852 are drafts being restacked; the end-to-end lane runs it.Release info Sub-libraries affected
Libraries affected
Checklist
If releasing new changes
pnpm changesetto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Michael (@Silthus) approved adding the fields to the contract and to posthog-node. Built by Claude Code (Claude Opus 5.5) test-first, as one build lane of the map.
cc,bcc,headers,tags,attachments, code form onlycontenttypesBuffer,Uint8Arrayor a Base64 string, snake_case on the wirepathinvalid_attachmentbefore any requestbtoaover byte chunks, notBuffer, so the edge build works tooOpen: none.
Involvement: decisions by Michael through the contract; one review by GPT-6.1 Sol with no findings; a QA swarm over the restacked stack in two rounds (Review lane GPT-6.1 Sol with seven lenses, Fresh-eyes lane Claude Opus 5.5) with its fix in this PR; no live run yet.
🤖 Generated with Claude Code