Repository navigation
feat: send email content from code with posthog.emails.send - #1171
Conversation
Squash of Silthus#2 at e5fdedf, re-signed for required signatures. Co-Authored-By: Claude Opus 5.5 <[email protected]>
posthog-python Compliance ReportDate: 2026-11-05T11:05:05.680101+00:00 ✅ All Tests Passed!121/121 tests passed Capture_V1 Tests✅ 95/95 tests passed View Details
Capture_Ai Tests✅ 5/5 tests passed View Details
Feature_Flags Tests✅ 17/17 tests passed View Details
Feature_Flags_Local_Evaluation Tests✅ 4/4 tests passed View Details
|
Takes the flat send body from #1155, and sends a flat distinct_id from the local-template form too. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Parks the local-template form (template_data on a code send, the content hash, the upload by key and its retry) and the cc, bcc, headers, tags and attachments fields, to match posthog-node. A code send now takes rendered html and/or text only. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
There was a problem hiding this comment.
PostHog Review · Standard · reviewed 9a4dd48: 1 Should fix.
See this review and its findings in PostHog Review. Comment @posthog review --deep for a Deep review. A Deep review takes longer and looks deeper than a Standard review.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
marandaneto
left a comment
There was a problem hiding this comment.
The code looks good; no qualifying findings. Verdict: correct.
… reads Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SDK performance: posthog-pythonBenchmarks completed. No changes flagged for review. Flags require at least a 5% change in the median and all six pairs agreeing on direction. This is a noise filter, not proof of a performance change or a CI gate. View all results
Values are medians of paired measurements. Not flagged does not mean unchanged. Unavailable means a measurement could not be provided, not zero. Estimated allocation measurements retain their instrumentation uncertainty. RSS measures the whole process, not just SDK allocations; observed peaks are sampled checkpoints. Paired ranges in the diagnostics describe variability, not confidence intervals. Each shard uses its own runner; compare Before PR against With PR within a row, not across shards. Compared 102 metric rows; 102 rows shown in all results. Omitted or shortened data remain in |
Co-Authored-By: Claude Opus 5.5 <[email protected]>
💡 Motivation and Context
A Python backend that renders its own email can't send it through PostHog today.
emails.sendtakes only a template made in PostHog, by its ID. This PR adds the code form toemails.send, beside the template form from #1093. It matches posthog-node in PostHog/posthog-js#5509. The SDK contract is PostHog/sdk-specsopenspec/specs/emails/spec.md, plus the draft changeadd-emails-code-form.Moved from Silthus#2. Part of map Silthus/posthog#461.
Caution
Merge risk: medium, one-way door once released. A release makes the new keyword names and the
migrated_fromclient option a public contract.Do not merge before the server deploys. The code form depends on the code arm of
send/email/in PostHog/posthog #127122 and #127123. Until both deploy, a code send gets a400. Merge and release this after them.Changes:
send, typed withUnpackand one@overloadeach, onEmailsandAsyncEmails. A mix of the two forms raisesTypeErrorbefore any request.POST /messaging/send/email/:template_key,subject,htmland/ortext, and a flatdistinct_id, as master's flat body (feat: send a flat body from emails.send #1155) has it.Trueby default. The template form refuses it.from_,reply_toandmax_retries.migrated_fromis a client option.Nonecounts as left out.template_dataon a code send, the content hash, the upload by key),cc,bcc,headers,tagsandattachments. A code send with any of them raisesTypeError.💚 How did you test it?
Test seams:
Emails.sendandAsyncEmails.sendon a real client with the HTTP transport faked, and.github/scripts/check_strict_types.shunder pyright strict. The tests pin each code-form body and each refusal, including the parked fields.Proof at
b93ea5c, throughfleet proof(the gate passes):20fad8c, delivered into a mail catcher. Atb93ea5c, a code send withreply_to=[]is accepted and delivered, and the old body withreply_to: []gets a400.400.mainand at this head, each difference classified as intended.📝 Checklist
If releasing new changes
sampo addto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
claude-opus-5-5), as lane sdk3 of map Map: send code-managed email templates with one PostHog call Silthus/posthog#461, directed by Michael.e654aefmergesmainwith the flat body.20fad8ccuts the local-template form and the extra fields, per Michael's scope cut.9a4dd48adds tests for the async code-form send andmigrated_from.b93ea5cleaves an emptyreply_tolist out of the body, because the server refuses one.b93ea5c.from_namesfrom;distinct_idis flat in both forms; argument errors stayTypeErrorandValueError, as 7.81.0 has them; local templates and the extra fields wait for their server slice.🤖 Generated with Claude Code