Repository navigation
Conversation
emails.send takes an optional category on the template_key form. Left out or TRANSACTIONAL_CATEGORY sends transactional email; any other value is the key of a marketing message category in the project. The SDK sends the field only when the caller sets it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
posthog-python Compliance ReportDate: 2026-11-09T19:28:50.810921+00:00 ✅ All Tests Passed!270/270 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_V4 Tests✅ 149/149 tests passed View Details
Feature_Flags_Local_Evaluation Tests✅ 4/4 tests passed View Details
|
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 |
|
Note 🤖 Claude Opus 5.5 responding on behalf of Michael Decisions made while building this PR:
|
💡 Motivation and Context
Developers who send email from code with
emails.sendcan only send transactional email. A team that moves a marketing email from a workflow to a code send loses its message category, so recipients' opt-outs and unsubscribe links stop applying.emails.sendnow takes an optionalcategoryon the code form (the one withtemplate_key). Left out, orTRANSACTIONAL_CATEGORY, sends transactional email as before. Any other value is the key of one of the project's marketing message categories.Depends on PostHog/posthog#132118, which pins the API contract and adds the server side. Release once customers can use it.
Merge risk: low, two-way door. One optional field, sent only when the caller sets it. Callers that leave it out send the same request body as before.
category: NotRequired[Optional[EmailCategory]]onCodeEmailArgs, whereEmailCategory = Union[Literal["transactional"], str]. That is as close as Python typing gets to "the built-in value plus any string": type checkers widen it tostr, and it documents the built-in value.TRANSACTIONAL_CATEGORY: Final = "transactional"andEmailCategoryare exported fromposthog.emails.Nonecounts as left out, like the other optional code-send fields. A non-string raisesTypeError, and an empty string raisesValueError, before any request.template_id) doesn't takecategory. A template send follows the template's own category on the server.EmailError.code:unknown_category(400),unsubscribe_link_required(400) andrecipient_opted_out(422). All three havename == "validation_error", likerecipient_suppressed. The SDK has no list of codes, so the docstrings name them.CodeEmailArgsdocstring. A minor changeset is included.💚 How did you test it?
Lane gate on
60d6d30, all green:ruff format --check .,ruff check ., mypy against the baseline, the fullpytestsuite (6241 passed),python -W error -c "import posthog",make public_api_checkand.github/scripts/check_strict_types.sh(pyright strict).Emails.sendthrough a fakerequestsadapter, at the public surface where the existing email tests work. Tests check that the body carriescategorywhen set, includingTRANSACTIONAL_CATEGORY, and leaves it out when unset orNone. They also check that a non-string, an empty string, orcategoryon the template form fails before a request.category="product-updates"andcategory=TRANSACTIONAL_CATEGORY. It rejectscategory=1andcategoryon the template form.AsyncEmailsand a standaloneEmails, sending over HTTP to a local stub server that answers like the contract. The raw wire bodies:None: nocategorykeyTRANSACTIONAL_CATEGORY:"category":"transactional""product-updates":"category":"product-updates"EmailErrorwith codesunknown_category,unsubscribe_link_requiredandrecipient_opted_out, andname="validation_error"Live run output
📝 Checklist
If releasing new changes
sampo addto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Claude Code, Claude Opus 5.5 (
claude-opus-5-5), built test-first. This PR is one of several SDK PRs that build to the contract pinned in PostHog/posthog#132118.emails.sendform takescategory?category_mismatch.Union[Literal["transactional"], str]asEmailCategory, with aFinalconstantTRANSACTIONAL_CATEGORYInvolvement: no grilling rounds, no prototypes; one GPT-6.1 Sol review round (no findings) and PostHog Review (no findings); live-run proof captured.
🤖 Generated with Claude Code