You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit aa2dbab
Browse filesBrowse the repository at this point in the historyBrowse files
fix(plugin-email): Send test with "None (log only)" picked but not saved no longer delivers over the live SMTP (#25519)
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuACdPWFJTLDcTrByFCHLzqb)_Fixes#25509
Clause-②: no
Metadata-protocol: none
## What was wrong
Setup → Email's "Send test email" (the `mail/test` handler in
`packages/plugins/plugin-email/src/email-plugin.ts`) tests the form's
merged values, saved or not: `smtp` builds a one-shot `SmtpTransport`,
Resend / Postmark build a one-shot service. `provider === 'log'` matched
neither branch, so `target` stayed 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 answered `ok: true` "Sent test email
to …".
## The change
- New `provider === 'log'` branch: when the live transport is not
already a `LogTransport`, the test goes through a one-shot
`EmailService` over a new `LogTransport`, with the same `persistence`
option as the smtp branch (the press is still recorded in `sys_email`).
When the live transport already IS a `LogTransport` (a saved `log`, or
the out-of-the-box namespace), the target stays the live service and the
answer is the existing saved-`log` warning, unchanged.
- The answer for the unsaved-`log` press uses the saved-`log` answer'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.`
- The other providers' branch is the old `provider !== 'log'` body,
untouched (it became the `else`).
- Landing: the file the claim names. `packages/spec` is not touched.
## Tests
`packages/plugins/plugin-email/src/mail-log-provider.pin.test.ts` gains
a describe block (its harness's `sendTest` now takes an optional unsaved
form state, posted as `payload.values`, as the page does):
- SMTP saved on the page and live, form provider `log`:
`nodemailer.sendMail` is never called; the answer is `ok: false` /
`warning`, starts "Nothing was delivered", says "no mail left this
machine" and names `SMTP (smtp.saved.test:587)`; the live transport is
still the `SmtpTransport` afterwards.
- SMTP built at boot from the boot options (`OS_EMAIL_SMTP_*` as
`providerOptions`), form provider `log`: no `sendMail`, same answer
shape.
- CONTROL: form provider `smtp` with a host still sends (`sendMail`
once, `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 provider
`log` to 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 (`sendMail` resolves; `sendMail` rejects
with `535 5.7.8`), and keep every assertion they had (inline answer from
the transport, no `sys_job_queue` job, audit row finalized `sent`). A
third leg keeps the queue invariant on the LIVE service: a live
`LogTransport` with queue delivery on answers the warning inline, with
no job.
Runs (head `79a4642ce8`, after merging `origin/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`, then `check:test-typecheck: OK`).
**Ablation** (head `2485802f70`, through
`scripts/ablation-replace.mjs`): the anchor `if (!(svc.options.transport
instanceof LogTransport)) {` was replaced by `if (false /*
ABLATION-25509 */) {`. The tool reported "ok mutation landed: anchor 1
-> 0, blob a4f67d1 -> 6d31a251e7e3". The pin went RED: Tests 2
failed | 6 passed (8), both unsaved-`log` legs 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-`log` pins holding. The
restore reported "ok restored: blob == HEAD (a4f67d1) and `git diff
HEAD` is empty". The test imports `./email-plugin.js` from `src`, so no
`dist` leg applies.
## Gates
`node scripts/pm/dispatch-gates.mjs --commands` at `79a4642ce8` derives
67 commands; all 67 ran locally with their exit codes recorded. `--ran`
says: "67 derived famil(ies) accounted for — 66 run, 1 NOT-MEASURED (1
DERIVED from a recorded exit 3)".
- NOT MEASURED: `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.
- The CI-only families (Test Core, Build Core, the type-check lanes,
artifact rosters) are read from this PR's check-runs.
## Acceptance notes
- The handler's own doc comment already says a one-shot temporary
service is built when the form differs from the live one; the `log`
branch now honours it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_016g7nYP9wdNx1GDa5XDr7Uy
---------
Co-authored-by: Claude <noreply@anthropic.com>
fix(plugin-email): "Send test email" with "None (log only)" picked but not saved no longer delivers over the live transport
6
+
7
+
Clause-②: no
8
+
Metadata-protocol: none
9
+
10
+
Setup → Email's "Send test email" tests what the form says, saved or not: an SMTP host on the form gets a one-shot SMTP connection, a Resend / Postmark key a one-shot client. With "None (log only — no real delivery)" picked and not saved, the test used to go through the live service instead, so with SMTP live a real message was delivered and the answer was "Sent test email to …". The test now goes through a one-shot `LogTransport` (recorded in `sys_email` like any other test send) and answers with a warning that nothing was delivered and no mail left the machine, naming the live transport it did not use. Once `log` is saved the answer is the existing warning that no delivery transport is active. The other providers are unchanged.
0 commit comments