Skip to content

feat(emails): name the sending integration with an integration option - #304

Draft
Silthus wants to merge 1 commit into
feat/emails-cc-bcc-attachmentsfrom
feat/emails-integration-option
Draft

Silthus wants to merge 1 commit into
feat/emails-cc-bcc-attachmentsfrom
feat/emails-integration-option

Conversation

@Silthus

@Silthus Silthus commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

A mailer integration built on posthog-php, such as the Symfony Mailer transport that stacks on this PR or the Laravel mail driver, can't tell PostHog that it sent an email. Every send looks like a plain posthog-php call, so PostHog can't count framework sends per integration.

emails()->send() now takes one more option, 'integration' => '<name>/<version>', such as laravel/1.4.0. The client checks it against ^[a-z0-9-]{1,40}/[0-9A-Za-z.+-]{1,40}$ and sends it as the X-PostHog-Integration header of that send, on every retry. A bad value fails locally with invalid_integration and sends nothing. The User-Agent and the analytics client don't change. Apps leave the option out; integrations set it.

Stacked on #292 (merge that first).

Seams for reviewers. The same option, with the same pattern and header, lands in posthog-node and posthog-ruby; the server reads the header's name into the send event's client. The Symfony Mailer transport stacked on this PR passes posthog-php-mailer/<version>, and the Laravel driver will pass laravel/<version> through that transport. A break here is a value one of those accepts and this one refuses, or the reverse. Ticket: https://github.com/PostHog/michaelr-context/issues/42

Note

🔵 Merge risk: low · two-way door until released (merge-risk rubric)

  • The send path only gains an optional header when the new option is set; a send without it builds the same request as before.
  • Tests cover the header with and without the option, on every retry, and each invalid value refused with no request.
  • Revert: git revert the merge commit before the release; after a release, the option is public API.

💚 How did you test it?

  • Red on the base library: the new tests fail because the base refuses integration as an unknown option (invalid_form), 7 tests, 2 errors and 5 failures.
  • Green on this head: the same 7 tests pass with every assertion, none skipped.
  • Lane gate on PHP 8.2 and 8.4 in Docker, the CI commands: composer validate --strict, PHPUnit (2684 tests, OK; the skips are the existing environment skips), the adapter Python tests, PHPCS (no errors), composer api:check (snapshot up to date: send() takes an array, so its signature is unchanged).
  • Test seam: Emails::send() with the HTTP transport stubbed, asserting the request's headers and body.
  • Live run on US Cloud: one real send through the Symfony Mailer transport stacked on this PR, which passes integration. The request carried X-PostHog-Integration: posthog-php-mailer/4.20.0 and the same User-Agent. PostHog answered 202, and Emails::get() read the send back as delivered.

📝 Checklist

  • All commits are signed (see Commit signing)
  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm change to generate a change intent file

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Built by Claude Code (Opus 5.5) in a T3 Code stack thread. Hold as a draft: it releases only together with the other SDKs' email send.

Question Answer Who Where
Where does the marker go? One X-PostHog-Integration header per send, from a per-send option; the User-Agent stays the same 👍 Accepted default https://github.com/PostHog/michaelr-context/issues/39
What does a bad value do? Fails locally as invalid_integration with the field integration, like every other bad option 🤖 Agent this PR

Involvement: no grilling for this PR; reviews: one GPT-6.1 Sol round (no findings) and PostHog Review (no findings); proof captured (live run, red, green, lane gate).

🤖 Generated with Claude Code

A mailer integration passes 'integration' => '<name>/<version>' to
emails()->send(), and the client sends it as the X-PostHog-Integration
header of that send. The value is checked locally and a bad one sends
nothing. The User-Agent stays the same.

Ticket: PostHog/michaelr-context#42

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Silthus Silthus self-assigned this Nov 5, 2026

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostHog Review · Standard · reviewed 39aaf25: no findings.

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

posthog-php-socket Compliance Report

Date: 2026-11-05T12:36:09.845085+00:00
Duration: 116202ms

⚠️ Some Tests Failed

46/47 tests passed, 1 failed


Capture Tests

⚠️ 29/30 tests passed, 1 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 30ms
Format Validation.Event Has Uuid ✅ 525ms
Format Validation.Event Has Lib Properties ✅ 526ms
Format Validation.Distinct Id Is String ✅ 526ms
Format Validation.Token Is Present ✅ 527ms
Format Validation.Custom Properties Preserved ✅ 525ms
Format Validation.Event Has Timestamp ✅ 526ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 526ms
Retry Behavior.Retries On 503 ✅ 5835ms
Retry Behavior.Does Not Retry On 400 ✅ 2528ms
Retry Behavior.Does Not Retry On 401 ✅ 2528ms
Retry Behavior.Respects Retry After Header ❌ 6331ms
Retry Behavior.Implements Backoff ✅ 16250ms
Retry Behavior.Retries On 500 ✅ 5632ms
Retry Behavior.Retries On 502 ✅ 5633ms
Retry Behavior.Retries On 504 ✅ 5634ms
Retry Behavior.Max Retries Respected ✅ 16246ms
Deduplication.Generates Unique Uuids ✅ 532ms
Deduplication.Preserves Uuid On Retry ✅ 5631ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10839ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5634ms
Deduplication.No Duplicate Events In Batch ✅ 532ms
Deduplication.Different Events Have Different Uuids ✅ 528ms
Compression.Sends Gzip When Enabled ✅ 526ms
Batch Format.Uses Proper Batch Structure ✅ 525ms
Batch Format.Flush With No Events Sends Nothing ✅ 521ms
Batch Format.Multiple Events Batched Together ✅ 513ms
Error Handling.Does Not Retry On 403 ✅ 2528ms
Error Handling.Does Not Retry On 413 ✅ 2528ms
Error Handling.Retries On 408 ✅ 5633ms

Failures

retry_behavior.respects_retry_after_header

Retry delay too short: 803ms < 2500ms

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 526ms
Request Payload.Flags Request Uses V2 Query Param ✅ 521ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 521ms
Request Payload.Flags Request Omits Authorization Header ✅ 521ms
Request Payload.Token In Flags Body Matches Init ✅ 521ms
Request Payload.Groups Round Trip ✅ 522ms
Request Payload.Groups Default To Empty Object ✅ 521ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 522ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 521ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 521ms
Request Lifecycle.No Flags Request On Init Alone ✅ 516ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 508ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 525ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 525ms
Retry Behavior.Retries Flags On 502 ✅ 625ms
Retry Behavior.Retries Flags On 504 ✅ 626ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 527ms

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

posthog-php-fork_curl Compliance Report

Date: 2026-11-05T12:36:12.473487+00:00
Duration: 116676ms

⚠️ Some Tests Failed

46/47 tests passed, 1 failed


Capture Tests

⚠️ 29/30 tests passed, 1 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 42ms
Format Validation.Event Has Uuid ✅ 531ms
Format Validation.Event Has Lib Properties ✅ 535ms
Format Validation.Distinct Id Is String ✅ 535ms
Format Validation.Token Is Present ✅ 534ms
Format Validation.Custom Properties Preserved ✅ 535ms
Format Validation.Event Has Timestamp ✅ 531ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 538ms
Retry Behavior.Retries On 503 ✅ 5860ms
Retry Behavior.Does Not Retry On 400 ✅ 2536ms
Retry Behavior.Does Not Retry On 401 ✅ 2535ms
Retry Behavior.Respects Retry After Header ❌ 6351ms
Retry Behavior.Implements Backoff ✅ 16281ms
Retry Behavior.Retries On 500 ✅ 5652ms
Retry Behavior.Retries On 502 ✅ 5647ms
Retry Behavior.Retries On 504 ✅ 5651ms
Retry Behavior.Max Retries Respected ✅ 16280ms
Deduplication.Generates Unique Uuids ✅ 542ms
Deduplication.Preserves Uuid On Retry ✅ 5647ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10869ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5653ms
Deduplication.No Duplicate Events In Batch ✅ 538ms
Deduplication.Different Events Have Different Uuids ✅ 534ms
Compression.Sends Gzip When Enabled ✅ 535ms
Batch Format.Uses Proper Batch Structure ✅ 536ms
Batch Format.Flush With No Events Sends Nothing ✅ 520ms
Batch Format.Multiple Events Batched Together ✅ 524ms
Error Handling.Does Not Retry On 403 ✅ 2537ms
Error Handling.Does Not Retry On 413 ✅ 2538ms
Error Handling.Retries On 408 ✅ 5648ms

Failures

retry_behavior.respects_retry_after_header

Retry delay too short: 811ms < 2500ms

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 527ms
Request Payload.Flags Request Uses V2 Query Param ✅ 524ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 525ms
Request Payload.Flags Request Omits Authorization Header ✅ 523ms
Request Payload.Token In Flags Body Matches Init ✅ 525ms
Request Payload.Groups Round Trip ✅ 524ms
Request Payload.Groups Default To Empty Object ✅ 524ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 526ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 524ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 526ms
Request Lifecycle.No Flags Request On Init Alone ✅ 519ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 518ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 527ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 527ms
Retry Behavior.Retries Flags On 502 ✅ 628ms
Retry Behavior.Retries Flags On 504 ✅ 626ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 536ms

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

posthog-php-lib_curl Compliance Report

Date: 2026-11-05T12:36:23.188775+00:00
Duration: 116816ms

⚠️ Some Tests Failed

46/47 tests passed, 1 failed


Capture Tests

⚠️ 29/30 tests passed, 1 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 31ms
Format Validation.Event Has Uuid ✅ 524ms
Format Validation.Event Has Lib Properties ✅ 527ms
Format Validation.Distinct Id Is String ✅ 527ms
Format Validation.Token Is Present ✅ 527ms
Format Validation.Custom Properties Preserved ✅ 528ms
Format Validation.Event Has Timestamp ✅ 529ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 527ms
Retry Behavior.Retries On 503 ✅ 5837ms
Retry Behavior.Does Not Retry On 400 ✅ 2528ms
Retry Behavior.Does Not Retry On 401 ✅ 2529ms
Retry Behavior.Respects Retry After Header ❌ 6335ms
Retry Behavior.Implements Backoff ✅ 16253ms
Retry Behavior.Retries On 500 ✅ 5636ms
Retry Behavior.Retries On 502 ✅ 5636ms
Retry Behavior.Retries On 504 ✅ 5636ms
Retry Behavior.Max Retries Respected ✅ 17053ms
Deduplication.Generates Unique Uuids ✅ 236ms
Deduplication.Preserves Uuid On Retry ✅ 5636ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10842ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5640ms
Deduplication.No Duplicate Events In Batch ✅ 532ms
Deduplication.Different Events Have Different Uuids ✅ 530ms
Compression.Sends Gzip When Enabled ✅ 534ms
Batch Format.Uses Proper Batch Structure ✅ 527ms
Batch Format.Flush With No Events Sends Nothing ✅ 522ms
Batch Format.Multiple Events Batched Together ✅ 514ms
Error Handling.Does Not Retry On 403 ✅ 2532ms
Error Handling.Does Not Retry On 413 ✅ 2530ms
Error Handling.Retries On 408 ✅ 5636ms

Failures

retry_behavior.respects_retry_after_header

Retry delay too short: 804ms < 2500ms

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 527ms
Request Payload.Flags Request Uses V2 Query Param ✅ 525ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 525ms
Request Payload.Flags Request Omits Authorization Header ✅ 525ms
Request Payload.Token In Flags Body Matches Init ✅ 525ms
Request Payload.Groups Round Trip ✅ 524ms
Request Payload.Groups Default To Empty Object ✅ 526ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 524ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 526ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 525ms
Request Lifecycle.No Flags Request On Init Alone ✅ 519ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 510ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 530ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 526ms
Retry Behavior.Retries Flags On 502 ✅ 628ms
Retry Behavior.Retries Flags On 504 ✅ 627ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 529ms

This branch has not been deployed

No deployments
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