Repository navigation
docs(workflows): attach files to emails by upload, then send - #22022
Merged
Merged
Conversation
Document how to make a Workflows email template transactional and send it with POST /api/projects/:project_id/messaging_templates/:template_id/send/. The page covers the API key, the request and response, idempotency keys, error codes, rate limits, and how transactional emails behave. The page is marked beta. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-By: PostHog Desktop Task-Id: a0274fa0-d9be-48bf-b601-f153decf241d
…e SDKs - Send through `POST /messaging/send/email/` with `template.id`. The template route still works. - Read an email's status with `GET /messaging/emails/:message_id/`: statuses, reasons, 404 cases, scopes and read limits. - Send and read with the Python and Node.js SDKs. - Add `recipient_unreachable` to the errors table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-By: PostHog Desktop Task-Id: e72a2c10-f145-4f7c-8754-a4fcc06a18be
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-By: PostHog Desktop Task-Id: e72a2c10-f145-4f7c-8754-a4fcc06a18be
- The Node.js example turns off local evaluation, and both examples keep the SDK's error. - The status examples handle the error first and explain retrying a not_found. - The SDK section adds install, client reuse, timeouts and retries, uncertain outcomes, and a no-network testing example. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-By: PostHog Desktop Task-Id: e72a2c10-f145-4f7c-8754-a4fcc06a18be
…m pages The overview keeps the template, key, idempotency, errors, statuses and behavior. Node.js, Python and API pages hold the setup, send, status, retries and testing for each platform, with a sidebar group like push notifications. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-By: PostHog Desktop Task-Id: e72a2c10-f145-4f7c-8754-a4fcc06a18be
…he overview - A quick start links to the Node.js, Python and API pages. - Creating an email follows the Transactional section's wizard, and the hidden workflow explanation is gone. - Agents can set it up with the PostHog MCP server. - Protect sensitive data turns off click tracking and keeps deliveries, opens and statuses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-By: PostHog Desktop Task-Id: e72a2c10-f145-4f7c-8754-a4fcc06a18be
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-By: PostHog Desktop Task-Id: e72a2c10-f145-4f7c-8754-a4fcc06a18be
The send endpoint now takes a flat body (PostHog/posthog#126287), so the field table, the curl example and the engagement events note use template_id and distinct_id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-By: PostHog Desktop Task-Id: e72a2c10-f145-4f7c-8754-a4fcc06a18be
…il API page Document the code form of the send route (template_key with the subject and body), capture_engagement, the checks that only apply to it, and a delivery_unavailable anchor for the 503's docs link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mits Add DKIM-Signature and Received to the reserved headers, the header name and value limits, the tag length, and that reply_to takes bare addresses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…only Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t template_key and ph_ tags do - A retry with the same Idempotency-Key is the same request when every field of the code-form table matches. - template_key groups the email's metrics and sends. - A tag name that starts with ph_, in any case, is refused. - capture_engagement on a template send defaults to the project's setting. - Drop a double blank line after the checks table (fix-mdx). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ell out the ph_ case rule - The template-send table gets a capture_engagement row with the project default, and says a retry sends the field the same way. - The code-form row keeps only its own default. - A ph_ tag name is refused in uppercase or lowercase. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> # Conflicts: # contents/docs/workflows/transactional-emails/api.mdx # contents/docs/workflows/transactional-emails/node.mdx # contents/docs/workflows/transactional-emails/python.mdx
…code send The first release sends content from code with template_key, subject, html and text only. React Email output is rendered by the app and sent as html. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Drop capture_engagement from the template send: the send route refuses it there, with the other fields of content from your code. - Fold sender_not_allowed into sender_not_verified, the code the first release returns for a from address no verified sender can use. - Add "Send content from your code" to the Node.js and Python pages, with a React Email example rendered by the app. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… in a replay - A field error is a 400 with type validation_error, a field code such as required or invalid, and attr naming the field. - A replay also compares the sender PostHog chose, so a retry without from after the default sender changed returns idempotency_key_reused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ntify distinct ID Add "Link emails to your users" to the API, Node.js and Python pages: pass the distinct ID you pass to identify(), what happens without one today (a separate person keyed by the email address), and that invites and signup checks can leave it out. The overview and the API tables point there instead of "always send it". 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>
… Elixir and PHP Add a page for each SDK with the template send, content from code, the status read, and the distinct ID guidance. Copies and attachments are marked as coming in a later version. The API page shows every SDK in its language tabs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use the placeholders the site fills in, pass the client to the Go and Ruby helpers, tell "not yet" from "not bounced" in Go, add the .NET usings and serde_json, and say that flag definitions still load from the ingestion host with the secret key. Ruby's timeout covers each connect, read, and write. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd review Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Node.js and Python client examples used <ph_project_api_key> and a hard-coded ingestion host, which the site doesn't replace. They now use <ph_project_token> and <ph_client_api_host>, as the SDK pages do, so a logged-in reader sees their own project token and host. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…email-all-sdks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ming-soon notes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…atus reads A send needs transactional_email:write and a status read needs transactional_email:read or :write. Keys with a hog_flow scope still work. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Base64 size Refs PostHog/michaelr-context#88 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
@PostHog review |
Contributor
|
@Silthus PostHog Review is reviewing this pull request. |
…email-sdk-attachments Keeps master's tracking and sensitive sections next to this branch's copies and attachments sections on the eight SDK pages. Refs PostHog/michaelr-context#88 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs PostHog/michaelr-context#88 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Rust page needs posthog-rs 0.33.0 for copies and attachments, so the install line now selects 0.33 instead of 0.32. Refs PostHog/michaelr-context#88 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
@PostHog review |
Contributor
|
@Silthus PostHog Review is reviewing this pull request. |
Python 7.91.0 and .NET 2.25.0 are the first releases with the upload route. The .NET install line now pins 2.25.0, so the attachments example compiles. Refs PostHog/michaelr-context#88 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
@PostHog review |
Contributor
|
@Silthus PostHog Review is reviewing this pull request. |
content_type now takes MIME parameters, such as method=REQUEST, so mail apps can show an .ics file as an invitation. A name or filename parameter gets not_mime_type, because the file name comes from filename. Refs PostHog/michaelr-context#88 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
@PostHog review |
Contributor
|
@Silthus PostHog Review is reviewing this pull request. |
…roll The one-line example hid the content_type value past the edge of the code block at every width. The file object now takes one line for each field, and the refusal of a name parameter follows the example. Refs PostHog/michaelr-context#88 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
@PostHog review |
Contributor
|
@Silthus PostHog Review is reviewing this pull request. |
Vale's EnDash rule flags the bold-label bullets that this branch adds or changes. They now use an en dash, as master does since #22166. Refs PostHog/michaelr-context#88 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…email-sdk-attachments Brings in the en dashes of #22166 on the transactional email pages. Refs PostHog/michaelr-context#88 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
@PostHog review |
Contributor
|
@Silthus PostHog Review is reviewing this pull request. |
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.
Merge gate: attachments are open to every project in US and EU, and all eight SDK releases are published. Michael gives the merge word.
Problem
A developer who sends a transactional email from code can now attach files with a new flow: the files go to PostHog first, and the send names them by their hash. The docs still describe the old flow, where the send carries each file as Base64. They also name the old 512 KB limit and say that attachments are temporarily unavailable. A developer who follows them gets a refusal.
This PR rewrites the attachments parts of the transactional email docs for the new flow. It is the docs stack of PostHog/michaelr-context#88.
Impact
Part of the map's goal: developers send larger files from code. The docs teach the flow, so success shows as code sends with files after the feature opens to every project. The map measures it.
Merge Danger
Note
🔵 Merge risk: low · two-way door
contents/docs/workflows/transactional-emails/. No component, navigation, or build change.Summary
attachmentsfield now takes{sha256, filename, content_type?, content_id?}. A new Send files section gives the four steps: hash each file, ask where to upload, upload the missing files, then send. It documents the upload routePOST /api/projects/@current/messaging/attachments/uploads/with its request, itspresentanduploadanswers, and the uploadPUT. Allow the upload host names the Amazon S3 hosts that outgoing-traffic rules must allow. A calendar invite showscontent_typewith parameters,text/calendar; charset=utf-8; method=REQUEST, and that PostHog refuses anameorfilenameparameter. Checks for files lists every refusal code with its cause and fix, with the404that the upload route returns when content from your code is off or the PostHog version has no upload route.attachmentswithout the "temporarily unavailable" note.master: master added "Open and click tracking" to the SDK pages. Each page keeps both sections, attachments first, with master's wording for tracking andsensitive. The bullets that this PR adds use an en dash, as master does since docs(workflows): use en dashes in the transactional email docs #22166.Before the merge
401, and an unknown path next to it answers404. The refusal codes need a call with a key.Evidence
0070f3e: 23 checks, including the preview build, Vale (0 errors), Markdown lint, Spelling, Docs .md redirects, CodeQL, and semgrep.404, both upload hosts, no 512 KB, no "temporarily unavailable", nofile_too_large, no limit for each file, on each SDK page the hash, the upload, and the host link, that the daily upload limit counts when PostHog gives the upload URL, and that the HTML and text count at their Base64 size. Onmaster, 57 checks fail. On0070f3e, all pass.attachment_not_uploadedfor an unknown hash. The only change is the host. Run 1 uploads the file and gets202. Run 2 getspresent, uploads nothing, and gets202.https://<bucket>.s3.us-east-1.amazonaws.com/…for US andhttps://<bucket>.s3.eu-central-1.amazonaws.com/…for EU. The docs leave the bucket name out and name the host suffix.pnpm formatcovers no.mdxfile, so it changes nothing here.0070f3e, all 11 pages load at 1440 px in light and dark mode and at 640 px. Every anchor that the pages link exists, such as#send-files,#allow-the-upload-host,#checks-for-files, and#delivery_unavailable. The calendar invite example renders in the list, with itscontent_typeon its own line. No page shows "temporarily unavailable", "512 KB", orfile_too_large. The console errors are the same kinds as on posthog.com today.Decisions
.s3.us-east-1.amazonaws.com, and "the host ofurl". The alternative is the exact host.sensitive: falsetext that a reviewer flagged on the API page?master, and this PR keeps master's wording forsensitive. It goes to the page owners as a separate fix.429row says to link new files from the email body.Involvement: design pass (GPT-6.1 Sol, 1 finding, fixed); cross-model review, 2 rounds and 1 final read (GPT-6.1 Sol and Claude Opus 5.5: 6 fixed, 1 rejected as a merge-time item); ReviewHog, 4 reviews (1 fixed, 1 answered as a merge-time item, then no findings on
0aa1457,c39f467, and7a46fb2); QA swarm, 7 lenses on0aa1457(1 minor finding, partly fixed, the rest a follow-up), then final reads ofc39f467and7a46fb2with no findings; proof captured on7a46fb2. After the merge withmaster: final reads ofcf97265and1ab42fb(GPT-6.1 Sol: 2 fixed, 1 rejected as out of scope), ReviewHog with no findings oncf97265; proof captured oncf97265and1ab42fb. After the calendar invite and the merge of #22166: final reads ofb811a84,d597bf7, and0070f3ewith no findings, ReviewHog with no findings ond597bf7, and a UI walk that moved the invite'scontent_typeonto its own line; proof captured on0070f3e.History: the original scope of this PR (an exact revert of 8196ea6, inline Base64 attachments)
This PR first added the "Copies and attachments" section back to the eight SDK pages, and the Node.js and Python tabs of the receipt example, as a revert of
8196ea65ffrom #21932. That content described inline Base64 attachments with a 512 KB limit. The API changed to upload, then send, so this PR now rewrites that content. The hold on the eight SDK PRs stays: posthog-js #5514, posthog-python #1172, posthog-go #408, posthog-dotnet #367, posthog-rs #326, posthog-ruby #325, posthog-elixir #239, and posthog-php #292.🤖 Generated with Claude Code