Repository navigation
fix(plugin-email): Send test with "None (log only)" picked but not saved no longer delivers over the live SMTP - #25519
Merged
Conversation
…hrough a one-shot LogTransport Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016g7nYP9wdNx1GDa5XDr7Uy
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016g7nYP9wdNx1GDa5XDr7Uy
…ive-LogTransport leg Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016g7nYP9wdNx1GDa5XDr7Uy
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
Contributor
Author
席位核对 · ACCEPT · 2026-10-23T18:18Z
Generated by Claude Code |
This was referenced Oct 23, 2026
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.
Requested by Sales · project thread
Fixes #25509
Clause-②: no
Metadata-protocol: none
What was wrong
Setup → Email's "Send test email" (the
mail/testhandler inpackages/plugins/plugin-email/src/email-plugin.ts) tests the form's merged values, saved or not:smtpbuilds a one-shotSmtpTransport, Resend / Postmark build a one-shot service.provider === 'log'matched neither branch, sotargetstayed the LIVE service. With SMTP live and "None (log only — no real delivery)" picked on the form but not saved, the press delivered a real mail and answeredok: true"Sent test email to …".The change
provider === 'log'branch: when the live transport is not already aLogTransport, the test goes through a one-shotEmailServiceover a newLogTransport, with the samepersistenceoption as the smtp branch (the press is still recorded insys_email). When the live transport already IS aLogTransport(a savedlog, or the out-of-the-box namespace), the target stays the live service and the answer is the existing saved-logwarning, unchanged.logpress uses the saved-loganswer's severity and words (ok: false,severity: 'warning', "only logged and recorded in sys_email"), the banner's phrase for a log-only transport ("no mail left this machine"), and names the live transport it did not use, so the two doors agree:Nothing was delivered — the provider on the form is "None (log only)", so the test message was only logged and recorded in sys_email; no mail left this machine. The live transport was not used (Mail is delivered through SMTP (smtp.saved.test:587)); save the form to stop delivery.provider !== 'log'body, untouched (it became theelse).packages/specis not touched.Tests
packages/plugins/plugin-email/src/mail-log-provider.pin.test.tsgains a describe block (its harness'ssendTestnow takes an optional unsaved form state, posted aspayload.values, as the page does):log:nodemailer.sendMailis never called; the answer isok: false/warning, starts "Nothing was delivered", says "no mail left this machine" and namesSMTP (smtp.saved.test:587); the live transport is still theSmtpTransportafterwards.OS_EMAIL_SMTP_*asproviderOptions), form providerlog: nosendMail, same answer shape.smtpwith a host still sends (sendMailonce,ok: true, "Sent test email to admin@example.test via smtp (smtp.form.test:587)").Fixture re-judged: two pins in
email-plugin.queue-delivery.test.ts("mail/test is never queued") pressed the button with form providerlogto reach the harness's host transport, which is exactly the behaviour this PR removes. They now press with an SMTP host on the form, faking only nodemailer's wire (sendMailresolves;sendMailrejects with535 5.7.8), and keep every assertion they had (inline answer from the transport, nosys_job_queuejob, audit row finalizedsent). A third leg keeps the queue invariant on the LIVE service: a liveLogTransportwith queue delivery on answers the warning inline, with no job.Runs (head
79a4642ce8, after mergingorigin/main; the merge brought no plugin-email path):pnpm --filter @objectstack/plugin-email test: Test Files 38 passed (38), Tests 689 passed (689).pnpm --filter @objectstack/plugin-email typecheck: exit 0 (tsc --noEmit, thencheck:test-typecheck: OK).Ablation (head
2485802f70, throughscripts/ablation-replace.mjs): the anchorif (!(svc.options.transport instanceof LogTransport)) {was replaced byif (false /* ABLATION-25509 */) {. The tool reported "ok mutation landed: anchor 1 -> 0, blob a4f67d1b9e86 -> 6d31a251e7e3". The pin went RED: Tests 2 failed | 6 passed (8), both unsaved-loglegs failing on "expected vi.fn() to not be called at all, but actually been called 1 times" (the live SMTP sent), with the CONTROL and the saved-logpins holding. The restore reported "ok restored: blob == HEAD (a4f67d1b9e86) andgit diff HEADis empty". The test imports./email-plugin.jsfromsrc, so nodistleg applies.Gates
node scripts/pm/dispatch-gates.mjs --commandsat79a4642ce8derives 67 commands; all 67 ran locally with their exit codes recorded.--ransays: "67 derived famil(ies) accounted for — 66 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)".check:dual-build-cjs-loads, reason: PREREQUISITE NOT MET (it reads built output of every package; this worktree built only plugin-email's closure). CI's Build Core / Lint lanes carry it.Acceptance notes
logbranch now honours it.🤖 Generated with Claude Code
https://claude.ai/code/session_016g7nYP9wdNx1GDa5XDr7Uy