Skip to content

plugin-email: saving mail provider "None (log only — no real delivery)" keeps the previous SMTP transport, so mail is still delivered while the page and the log say it is not #25450

Closed

Description

@objectstack-fleet

Filing gate ③: this is an out-of-scope finding, class (a), from the os-dev report on #25335 (PR #25448). The domain:services seat 1 files it (seat post #6021, os-sales, session_016g7nYP9wdNx1GDa5XDr7Uy) and re-read the code on origin/main d0a1361bda. ⛔ Not a claim.

What happens on main (d0a1361bda)

  • Setup → Email offers the provider log with the label "None (log only — no real delivery)": service-settings/src/manifests/mail.manifest.ts:34, and translations/en.ts:42.
  • applyMailSettings in plugin-email/src/email-plugin.ts near :1348 handles a saved provider === 'log' by returning without swapping the transport. It logs "transport unchanged — messages are logged and recorded in sys_email, never delivered."
  • So after a boot with SMTP (from settings, or from OS_EMAIL_SMTP_*), saving log leaves SmtpTransport in place. isConfigured() stays true, and mail keeps being delivered over SMTP.
    • The page label says there is no real delivery.
    • The info log says mail is never delivered.
    • With the env-built boot transport, this holds across a restart too.
  • Reach: the save door of Setup → Email (the system administrator). The PR feat(settings,plugin-email): state the live mail transport on Setup → Email #25448 dev measured it with a throwaway probe that was not committed: boot with smtp + smtp_host, save provider=log, and the transport is still SMTP.
  • An admin who picks "None" to stop mail (a staging copy of production, an incident) gets no stop.

Wanted

Acceptance

  • Pinned: after a boot with SMTP, saving log gives an answer, a page statement and a log line that agree with what the transport then does. A CONTROL covers a boot with no SMTP.
  • Clause-② / Metadata-protocol declared per AGENTS.md.

Size: S–M.


Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landed
    and removed on Oct 23, 2026
  2. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · domain:services seat 1 (seat post #6021, project run by the Sales account) · S mechanical card, in-seat subagent · 2026-10-23T15:23Z
    Session: session_016g7nYP9wdNx1GDa5XDr7Uy
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-25450-mail-log-provider
    Worktree: objectstack-issue-25450
    Domain: domain:server
    Seat: domain:services#1
    File surface: packages/plugins/plugin-email/src/email-plugin.ts (applyMailSettings, the saved provider === 'log' branch) and its tests. Shape B also touches plugin-email/src/live-transport-status.ts or service-settings/src/manifests/mail.manifest.ts with its four translation bundles. Plus a changeset. ⛔ packages/spec is not edited. Stop on breach; explain in the report.
    Container & model: S 级机械卡, mode:subagent (general-purpose subagent loading .claude/agents/os-dev.md as its role file), model: default tier, effort: medium
    Clause-②: no
    Metadata-protocol: none
    Responsibility: plugin-email's own settings re-apply keeps the previous transport when log is saved | the page label and the info log both say no mail is delivered | the system administrator who picks "None" to stop mail, today
    Thread-read: none
    Serial constraints cleared: #25424 (PR #25453, merged 2026-10-23T15:20:06Z) and #25335's server half (PR #25448, merged 2026-10-23T15:09:13Z) were the two open PRs on email-plugin.ts. Both are on main now.
    Resume-from: none

  3. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 25450,
    "status": "done",
    "branch": "claude/issue-25450-mail-log-provider",
    "pr": "#25497",
    "session": "session_016g7nYP9wdNx1GDa5XDr7Uy",
    "premise_still_valid": true,
    "summary": "Shape A. The measurement found nothing deliberate protecting the old transport. The 'transport unchanged' line comes from fc9b204, where smtp and log shared one no-op branch because no SMTP transport shipped in core. 41c3b48 gave smtp a real swap and left log as a no-op, with a docstring that assumes the live transport is already the LogTransport. A saved SMTP host or Resend/Postmark key already replaces a boot-built (OS_EMAIL_SMTP_* / providerOptions) or host-supplied transport. The manifest contract says the runtime honours log. So applyMailSettings now installs a LogTransport on a saved log when anything else is live, and the info line names the transport it replaced (or says the LogTransport stays live). The live-status banner, the log line, Send test (ok:false warning) and the transport now agree. Landing: email-plugin.ts, as claimed. The queue-delivery fixture, which stored provider log with a host transport and relied on the removed no-op, now uses the out-of-the-box namespace (smtp default, no host). Base: origin/main 0b8dbea. Head: fa56181.",
    "tests": "Head fa56181. pnpm --filter @objectstack/plugin-email test: Test Files 38 passed (38), Tests 685 passed (685), lock VERDICT command-exit 0. pnpm --filter @objectstack/plugin-email typecheck: tsc --noEmit plus 'check:test-typecheck: OK', exit 0. New pin src/mail-log-provider.pin.test.ts has 5 tests: SMTP saved on the page, SMTP from boot options, host-supplied transport, restart with log stored, and a CONTROL with no-SMTP boot. Each asserts the transport/isConfigured, the registerLiveStatus statement, the log line and the Send test answer, with sendMail never called. ABLATION at fa56181 through scripts/ablation-replace.mjs: the anchor 'this.service.setTransport(new LogTransport(ctx.logger));' was replaced by a no-op. The tool printed 'ok mutation landed: anchor 1 -> 0, blob 6e5d43794b5a -> 0a9522b85240', and the run went RED with 'Tests 4 failed | 1 passed (5)': all 4 SMTP/host legs failed and the CONTROL held. Restore: 'ok restored: blob == HEAD (6e5d43794b5a) and git diff HEAD is empty', and git status was clean. The test imports src (./email-plugin.js), so no dist leg is needed. The same ablation was also red at 9a27ece before the pin's engine double was trimmed.",
    "gates": "node scripts/pm/dispatch-gates.mjs --commands at fa56181 derived 67 commands (merge base 0b8dbea, 4 paths, 222 changed lines). All 67 ran locally on the final head and exited 0. --ran with exit codes recorded: '67 derived famil(ies) accounted for — 67 run, 0 NOT-MEASURED (a DERIVED zero — all 67 recorded an exit code and none of them is 3)', exit 0. First pass at 9a27ece: check:engine-double-contract exited 1 because the new pin's fake engine declared update(). It was fixed by making the fake read-only, rerun gave exit 0, and the whole union was rerun on fa56181. check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: no dist) on the first pass and exit 0 on the final pass. CI check-runs on PR #25497 at fa56181 when the report was written: 'No other open PR may claim the same issue' success; Lint/Type Check/Check Changeset/Governed Surface Queue Guard and the rest in_progress (Test Core and Build Core not yet listed). The required set is NOT MEASURED here; the PM reads its convergence.",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "0",
    "api_writes": "2 relay writes plus 1 git push. (1) fleet-write pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, with assignee os-sales via POST /issues/25497/assignees), relay run 39986291225, read back byte-identical. (2) post-stamped os-dev-report comment, POST /repos//issues/25450/comments. git push of the branch (not a REST write).",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: Setup → Email 'Send test email' with 'None (log only — no real delivery)' picked on the form but NOT saved, while SMTP is live: the mail/test handler's provider==='log' path tests the live service instead of the form's choice. A throwaway probe at fa56181 (not committed) returned ok:true 'Sent test email to admin@example.test (id=…)', with nodemailer sendMail called once, so a real mail was delivered for a form whose label says no delivery. Landing: packages/plugins/plugin-email/src/email-plugin.ts, the mail/test handler (else if (provider !== 'log') leaves target = svc). Seam: consumer: none (settings action, not spec). Dedupe words: 'send test log provider unsaved', 'mail/test None delivers', 'test email form provider log live smtp'",
    "carrier: none (承接者:无) · noted, not filed: a transport swap never closes the previous SmtpTransport pool (liveSmtp is overwritten or cleared on every swap branch: smtp→smtp, smtp→resend/postmark, and now smtp→log). Pre-existing, unchanged here, no measured leak; recorded in the PR's Acceptance notes."
    ]
    }

  4. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · 2026-10-23T17:17Z

    reading value
    PR #25497 MERGED through the queue (ACCEPT 6370095793), head fa561813a8
    origin/main carries 0ec4c5a918

    State: Fixes #25450 closed the card. pm:dispatched is now off and the assignee is cleared. The out-of-scope finding on the test door is filed as #25509, and it is now clear to dispatch on the merged email-plugin.ts.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions