Repository navigation
Commit db04e5c
fix(plugin-email,service-settings): a sender with no From name goes out under the product name (#30548)
<!-- claude-projects-attribution: {"github_login":"os-bill"} -->
_Requested by **Bill** · [project
thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgXUJ7mi7dh8n9KT6npD9Tvg)_
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
## 维护者速读
- 改了什么:发件人名没设置时,邮件不再署名 ObjectStack,而是用产品名(Setup → 品牌
里设的产品名,没设就用部署名,再没有才是 ObjectStack);Setup → 邮件 的“发件人名称”去掉了默认值 ObjectStack。
- 为什么改:管理员只填了发件地址,收件人看到发件人是 ObjectStack,而主题和正文写的是产品名,显得像别家发的信(卡 #30212 第
3 行)。
- 风险与回滚:只影响“没设发件人名”的默认发件人;设过名字的照旧优先,调用方自带的发件人不改。回滚即 revert 本
PR。以前保存过的“ObjectStack”会被当作管理员显式设的名字继续生效,清空该栏即跟随产品名。
- 先看:packages/plugins/plugin-email/src/email-plugin.ts(namedSender /
applyDefaultSender 与 branding
绑定)、packages/services/service-settings/src/manifests/mail.manifest.ts、packages/plugins/plugin-email/src/email-plugin.mail-settings.test.ts
末尾新 describe。
- 两分钟验证:`pnpm --filter @objectstack/plugin-email exec vitest run
src/email-plugin.mail-settings.test.ts`,预期 34 条全过,其中“sender display name
follows the product name”一组 10 条。
- 需要你做的:无,席位落地。第 2 行(导入账号设密码链接 60 分钟)按席位裁定不在本 PR,留在卡上待决。
Part of #30212
Clause-②: no
Metadata-protocol: none — a server default changes (the sender's display
name, and the Mail settings page's From name default); nothing a
metadata author writes changes.
Skills-docs: none — `git grep -n -i -E "from_name|mail\.from|from
name|sender name|defaultFrom|resetPasswordTokenExpiresIn|set-password
link|60 minutes" -- skills` names no sentence about the mail sender or
the link lifetime (its one hit is `defaultFromRow` in
`skills/objectstack-ui/rules/actions.md`, an action-param key).
Row 3 of the card. Row 2 (the bulk-import set-password link lifetime) is
not in this PR: the seat ruled it out after the measurement (a
credential-setting link's lifetime, the security seat vacant), so the
card stays open for it. Rows 1 and 4 landed earlier.
## What changes
- **`@objectstack/plugin-email`.** `EmailServicePlugin` names a default
sender that has no display name of its own with the product name the
auth mails' subject and body already read: the Setup → Branding product
name (`branding.workspace_name`) when an administrator SET one, else the
deployment's name (`defaultTemplateContext.appName`, which `os serve`
resolves from `OS_APP_NAME` and the config chain), else `ObjectStack`.
This is the chain plugin-auth binds to the auth mails' `{{appName}}`.
- The Branding value is read on `kernel:ready` and again on every
Branding save, so a rename reaches the next mail with no restart. A
manifest default is not taken for a set value. The namespace is bound
only where the settings service lists it; a walled posture withholds it,
and the deployment's name applies there.
- Which senders: the Setup → Mail sender saved with the From name empty,
and a boot sender declared without a name (`OS_EMAIL_FROM` holding an
address alone, or `config.email.defaultFrom` with `address` alone).
- A From name somebody set always wins: a Mail settings From name whose
source is not `default`, or a boot-declared name. A sender a send passes
as `input.from`, and a template's own sender, are never renamed.
- "Send test email" sends under the same name real mail goes out with.
- The plugin keeps the declared sender apart from the live one, so a
later Branding save renames only a sender that has no name.
`EmailService`'s public surface is unchanged: the plugin installs the
named sender through the existing `setDefaultFrom`.
- **`@objectstack/service-settings`.** `mail.from_name` drops its
`default: 'ObjectStack'`: an unsaved field reads empty, and the resolver
answers `value: null`, `source: 'default'`, as Branding's product name
already does. The field gains help text in en, zh-CN, ja-JP and es-ES
saying that an empty value uses the product name. The Branding
manifest's header names plugin-email as a reader of `workspace_name`.
## Tests
-
`packages/plugins/plugin-email/src/email-plugin.mail-settings.test.ts`,
new top-level describe "sender display name follows the product name
when none is set" in the existing harness (the harness's fake settings
service now lists namespaces and serves a `branding` snapshot): Branding
name set; Branding unset falls back to the deployment name; nothing
configured gives `ObjectStack`; an explicit From name wins; a From name
that is only a manifest default does not; a Branding save and a Branding
clear take effect without a restart; a boot sender without a name is
named; a caller's own `from` is untouched; no Branding read under a
walled posture; the test mail carries the name.
-
`packages/services/service-settings/src/manifests/mail.manifest.test.ts`:
`from_name` declares no default, stays optional, and carries the help
text.
## Acceptance notes
- A From name stored before this change (for example `ObjectStack`, if a
save sent the prefilled default) is an explicit value and keeps winning.
Clearing the field makes the sender follow the product name.
- The boot fallback sender that `resolveEmailCapabilityArg` builds when
nothing declares one keeps its derived name (the deployment's name).
Setup → Branding does not rename that one. It is the sender for a
deployment with no From address configured anywhere.
- Non-auth templates render `{{appName}}` from the boot-time template
context (the deployment's name), not from Setup → Branding. This PR does
not change that. Only the auth mails read the Branding product name
today.
## Verification
Head `a2519c047`, merged with `origin/main` `fa9c0aa2a` (up to date).
<details><summary>Tests and typecheck (both touched packages)</summary>
- Red first on `origin/main` `fa9c0aa2a`: the new describe ran 8 failed
/ 1 passed (the passing one is the control: a caller's own `from` is
untouched); for example "expected 'no-reply@acme.test' to be 'Acme
Portal (no-reply@acme.test)'", angle brackets spelled as parentheses
here.
- `pnpm --filter @objectstack/plugin-email test` then `typecheck` under
the verify lock: VERDICT command-exit 0; Test Files 48 passed (48),
Tests 795 passed (795); `check:test-typecheck` OK.
- `pnpm --filter @objectstack/service-settings test` then `typecheck`
under the verify lock: VERDICT command-exit 0; Test Files 68 passed
(68), Tests 1157 passed (1157).
- `pnpm exec eslint --no-inline-config` on the 9 changed source files:
exit 0. The repo-wide `pnpm lint` is left to CI (`Lint & Repo Gates`).
</details>
<details><summary>Ablation (once, through
scripts/ablation-replace.mjs)</summary>
- Anchor in `email-plugin.ts` `namedSender`: the line that keeps a
sender's own name, replaced by an unconditional `return sender;` (marker
ABLATED-30212). Mutation landed (anchor 1 to 0, blob b493082 to
6572e816ed05).
- `vitest run src/email-plugin.mail-settings.test.ts`: Tests 8 failed /
26 passed (the 7 naming pins plus the existing sys_email delivery pin
that expects the named sender).
- Restored: blob back to HEAD b493082, `git diff HEAD` empty,
marker count 0. The tests import `src` directly, so no dist rebuild is
involved.
</details>
<details><summary>Derived gates (dispatch-gates --commands, reconciled
with --ran)</summary>
- 60 derived, 59 run with exit 0, 1 NOT MEASURED: `pnpm
check:dual-build-cjs-loads` exit 3, PREREQUISITE NOT MET (it reads every
package's built output; only this diff's closure is built here). Left to
CI.
- `node scripts/check-plugin-teardown-shape.mjs --self-test` exited 3 on
the shallow clone (its pinned fixture commit was missing); after `git
fetch origin 621a487` it exited 0 (48
cases pass).
- `--ran` verdict: "60 derived famil(ies) accounted for, 59 run, 1
NOT-MEASURED". Changed lines 366 (+344 / -22), under the 3000 threshold.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Utp6CbJtwiuT8bs47ARmYb)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 48b3ae6 commit db04e5c
10 files changed
Lines changed: 344 additions & 22 deletions
File tree
- .changeset
- packages
- plugins/plugin-email/src
- services/service-settings/src
- manifests
- translations
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
30 | | - | |
31 | | - | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
32 | 37 | | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
33 | 44 | | |
34 | 45 | | |
35 | | - | |
36 | | - | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
37 | 58 | | |
38 | 59 | | |
39 | 60 | | |
40 | 61 | | |
41 | 62 | | |
42 | 63 | | |
43 | | - | |
44 | | - | |
45 | | - | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
46 | 70 | | |
47 | 71 | | |
48 | 72 | | |
| |||
84 | 108 | | |
85 | 109 | | |
86 | 110 | | |
87 | | - | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
88 | 116 | | |
89 | | - | |
| 117 | + | |
90 | 118 | | |
91 | 119 | | |
92 | 120 | | |
| |||
467 | 495 | | |
468 | 496 | | |
469 | 497 | | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
0 commit comments