[PHP-WASM] Expose sendmail stdin as a live event stream - #3996
Merged
Conversation
bgrgicak
commented
Jul 10, 2026
This was referenced Jul 10, 2026
bgrgicak
marked this pull request as ready for review
July 10, 2026 11:29
bgrgicak
requested review from
a team and
brandonpayton
and removed request for
a team
July 10, 2026 11:29
Member
|
I think it'd be easier to find the code that mimicks sendmail if we put it into a |
bgrgicak
commented
Jul 13, 2026
bgrgicak
commented
Jul 13, 2026
adamziel
reviewed
Jul 14, 2026
adamziel
reviewed
Jul 14, 2026
adamziel
reviewed
Jul 14, 2026
adamziel
force-pushed
the
codex/sendmail-mail-capture
branch
from
July 14, 2026 16:03
8f0db93 to
9dd1d4d
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds deterministic outbound email capture for PHP-WASM by intercepting sendmail spawns and emitting email.received events, with WordPress boot registering the handler by default.
Changes:
- Introduces a
sendmailcommand-specific spawn handler that buffers stdin and dispatches anemail.receivedevent. - Adds per-command spawn handler routing in
PHP(generic vs command-specific precedence; survives runtime rotation). - Adds Node + WordPress integration tests covering capture behavior, error paths, and runtime rotation.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/playground/wordpress/src/test/email-capture.spec.ts | Adds a WordPress-level test asserting wp_mail() triggers email.received. |
| packages/playground/wordpress/src/boot.ts | Registers the sendmail command-specific spawn handler during WordPress boot. |
| packages/php-wasm/util/src/lib/spawn-handlers/sendmail.ts | Implements the sendmail spawn handler and defines PHPEmailReceivedEvent. |
| packages/php-wasm/util/src/lib/index.ts | Exports sendmail handler APIs and moves byte-concat exports to a dedicated module. |
| packages/php-wasm/util/src/lib/concat-bytes.ts | Adds reusable byte/ArrayBuffer concatenation helpers. |
| packages/php-wasm/universal/src/lib/universal-php.ts | Extends the PHPEvent union to include PHPEmailReceivedEvent. |
| packages/php-wasm/universal/src/lib/php.ts | Adds dispatcher-based spawn routing with command-specific overrides. |
| packages/php-wasm/universal/src/lib/php.spec.ts | Adds unit test coverage for the new spawn routing behavior. |
| packages/php-wasm/universal/src/lib/index.ts | Re-exports PHPEmailReceivedEvent type for consumers. |
| packages/php-wasm/node/src/test/php-email-capture.spec.ts | Adds Node runtime tests for sendmail capture, size limits, and runtime rotation. |
| packages/php-wasm/node/project.json | Registers the new Node test file in project test targets. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
adamziel
reviewed
Jul 14, 2026
adamziel
force-pushed
the
codex/sendmail-mail-capture
branch
from
July 14, 2026 23:59
857e84e to
5bdfb92
Compare
adamziel
added a commit
that referenced
this pull request
Jul 15, 2026
`PHPWorker` can now deliver an event whose `stdin` remains a live `ReadableStream` across the browser worker boundary. Comlink applies transfer handlers only to the top-level value, so a stream stored in an event's `stdin` property otherwise reaches `postMessage()` without a transfer list and structured cloning fails. The event handler serializes `stdin` through a dedicated stream handler. It transfers the stream directly when the runtime supports that operation and uses the existing `MessagePort` bridge otherwise. The bridge transfers owned chunk copies because `ReadableStream.tee()` branches share chunk objects and detaching one branch's buffer must not invalidate the others. Consumer cancellation travels back over the port and cancels the source reader. A stream can only be transferred once, so `PHPWorker` gives each listener its own tee branch. Slow listeners may queue unread chunks because this transport does not coordinate backpressure between branches. Browser-worker tests cover native stream transfer and the forced `MessagePort` fallback, including incremental delivery to two listeners and source cancellation after both tee branches cancel. This is the worker transport required by #3996. It adds no sendmail behavior.
adamziel
force-pushed
the
codex/sendmail-mail-capture
branch
from
July 15, 2026 11:49
ec7e0de to
a49cd15
Compare
adamziel
force-pushed
the
codex/sendmail-mail-capture
branch
from
July 15, 2026 17:21
1023fef to
49267b4
Compare
adamziel
changed the base branch from
trunk
to
codex/transfer-event-stdin-streams
July 15, 2026 17:21
adamziel
force-pushed
the
codex/transfer-event-stdin-streams
branch
from
July 15, 2026 17:49
20f5efe to
beb8a52
Compare
adamziel
force-pushed
the
codex/sendmail-mail-capture
branch
from
July 15, 2026 17:49
49267b4 to
24034e3
Compare
adamziel
force-pushed
the
codex/sendmail-mail-capture
branch
2 times, most recently
from
July 15, 2026 19:37
7a67a7a to
8ef5dcb
Compare
adamziel
force-pushed
the
codex/sendmail-mail-capture
branch
from
July 15, 2026 19:41
8ef5dcb to
f5a59cd
Compare
adamziel
added a commit
that referenced
this pull request
Jul 15, 2026
Adds `sendmailSpawnHandler()` (introduced in #3996) to PHP.wasm objects instantiated in the `remote.html` web worker. The handler catches any emails sent by WordPress and re-exposes it through an event listener as follows: ```ts php.setCommandSpawnHandler('sendmail', sendmailSpawnHandler(php)); // In the website package: php.addEventListener('sendmail.spawned', async (event) => { if (event.type !== 'sendmail.spawned') { return; } const rawMessage = await new Response(event.stdin).arrayBuffer(); // Parse or relay rawMessage. }); ``` The goal is to expose these emails in the UI in a follow-up PR.
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.
PHP can write a message to
sendmailthroughmail(),popen(), orproc_open(), but PHP-WASM has no reusable transport for observing those bytes. This adds a sendmail-compatible null transport. It never delivers mail. It emitssendmail.spawnedwith raw stdin as a liveReadableStream<Uint8Array>.Registration is explicit:
The event fires with the first stdin chunk, before PHP finishes writing. The transport stays alive until stdin closes. Empty input exits with
EX_TEMPFAILand emits no event. Input above 20 MB errors the stream and exits with status 1. Cancelling the stream stops delivery to that reader but keeps draining stdin, so PHP can finish writing and receive the transport's exit status. There is no backpressure to PHP; a slow listener may buffer the full message.Node tests cover
mail(),proc_open(), delayed input, empty and oversized input, and runtime rotation. Browser tests cover live delivery to multiple listeners and cancellation while PHP continues writing.This PR does not install the transport by default. #4063 installs it in remote Playground instances.
#4064 provides the marked stdin event transport and per-listener streams used here.