Skip to content

fix(cli,plugin-email)!: 缺 apiKey 的 resend/postmark 不再静默降级为 LogTransport (#5132) - #5153

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-5132-cli-email-provider-loud
Aug 4, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/issue-5132-cli-email-provider-loud

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5132

改了什么

packages/cli/src/commands/serve.ts 的 resolveEmailCapabilityArg 里,provider 非 log/smtp 且缺 apiKey 时把 provider 改写成 log、打一条 warning、照常启动的那段没了。现在与紧邻的 smtp 分支同形:建不出投递通道就抛,由 capability 循环把它变成响亮失败。

覆盖三种「声明了投递意图但投不出去」的配置:

配置 改前 改后
OS_EMAIL_PROVIDER=resend/postmark,无 OS_EMAIL_API_KEY 降级 log + warning,正常启动 抛
OS_EMAIL_PROVIDER=sendgrid(已退役)/ 拼错的 tag 无 key 时同样降级 log;有 key 时透传到插件层再抛 在 CLI 层抛,消息里带 #5094 的 SMTP 迁移写法
OS_EMAIL_PROVIDER=smtp 无 host 抛(#5087 已修) 不变

「响亮失败」的准确含义(未夸大):app 在 requires: [...] 里显式声明了 email 时是硬启动失败;email 是平台自动补齐的默认能力时,是 console.error + 不挂载 email 服务。两种都不再是「每封都成功、一封没发出」——这与 smtp 分支现有行为完全一致。

抛错成立的前提是「本环境不发信」有说得出口的写法:OS_EMAIL_PROVIDER=log(也是默认值)。测试专门钉住了这一条仍能正常启动。

错误消息按 AGENTS.md degradation-log-level 同时给出后果与修复,措辞照抄 smtp 缺 host 那条的形状:

provider='resend' selects resend delivery but no API key is configured, so every send would
be recorded in sys_email as sent and nothing would leave the box — set OS_EMAIL_API_KEY
(or config.email.apiKey), or set OS_EMAIL_PROVIDER=log if this environment is not meant to send mail.

provider 词汇表:复用而非重写

按裁定要求,CLI 不再自己写 provider !== 'log' && provider !== 'smtp',而是从 @objectstack/plugin-email(#5133 合入的单一真相)读:isEmailTransportProvider / unsupportedProviderFix。

「哪些 provider 需要 apiKey」此前没有共享形式(makeTransport 的 switch 里、mail.manifest.ts 里各有一份),所以本 PR 在同一个文件里补上:

  • API_KEY_EMAIL_PROVIDERS(satisfies readonly EmailTransportProvider[])、ApiKeyEmailProvider、emailProviderRequiresApiKey();
  • makeTransport 的 resend/postmark 分支改走 requireApiKey(provider, apiKey),其形参类型是 ApiKeyEmailProvider —— 常量里删掉一个 tag,switch 直接编译不过(这是一个方向的锁);
  • 另一个方向(常量里加了 tag、对应分支忘了校验)由新增的 transports/api-key-providers.contract.test.ts 遍历 EMAIL_TRANSPORT_PROVIDERS 逐个实测 makeTransport 的真实行为钉住。

makeTransport 抛出的文案与改前逐字一致,插件层行为不变。

破坏性变更

@objectstack/cli 记为 major,changeset 里写明了受影响人群与迁移:只设 OS_EMAIL_PROVIDER=resend|postmark 而不配 key、靠降级启动的部署(典型是 CI / preview 环境),两条一行修法——改成 OS_EMAIL_PROVIDER=log,或补上 OS_EMAIL_API_KEY。完整配置、以及不设 OS_EMAIL_PROVIDER 的默认路径都不受影响。

顺带把 content/docs/deployment/environment-variables.mdx 的 OS_EMAIL_API_KEY 一行改成 Required(参照同表 OS_EMAIL_SMTP_HOST 的写法),并点明 log 是「本环境不发信」的声明方式。

测试

pnpm --filter @objectstack/cli test            → 69 files / 612 tests passed
pnpm --filter @objectstack/cli typecheck       → clean
pnpm --filter @objectstack/plugin-email test   → 9 files / 133 tests passed
pnpm --filter @objectstack/plugin-email typecheck → clean
eslint(改动文件)/ check:doc-authoring / check:docs-audit-scope → clean

serve-email-capability.test.ts:钉降级行为的那条用例已翻面(postmark 无 key 从「断言回落到 log」改为「断言抛错 + 消息里同时有后果、OS_EMAIL_API_KEY、OS_EMAIL_PROVIDER=log」),另加显式 OS_EMAIL_PROVIDER=log 仍能启动、未知/退役 provider 抛错并带迁移、完整 resend/postmark 配置原样透传三条;warning 字段随最后一个生产者一起删除(接口上不再保留没人写入的降级通道)。

越界发现

🤖 Generated with Claude Code

https://claude.ai/code/session_017MCKJaEomEqg4tvz4SzdNd


Generated by Claude Code

claude added 2 commits August 4, 2026 06:30
…Key fails the boot instead of becoming a LogTransport (#5132)

`resolveEmailCapabilityArg` answered a missing API key by rewriting the
provider to `log`, printing a warning, and booting: a server that accepted
every send, recorded each in `sys_email` as sent, and delivered nothing.
#5087 closed that inside plugin-email (`makeTransport` throws rather than
substituting a transport); the CLI kept doing it one layer up, which the
#5087 PR itself flagged in this function's docstring.

It now refuses every mail configuration it cannot deliver through, the way
its neighbouring `smtp` arm already did — resend/postmark with no key, and
a provider tag outside the supported set (retired `sendgrid`/`ses` get the
SMTP migration). Each message names the consequence and both fixes, per
AGENTS.md degradation-log-level. Refusing is only fair because
`OS_EMAIL_PROVIDER=log` is how a deployment says "no mail from here" — a
test pins that it still boots.

The provider vocabulary is read from `@objectstack/plugin-email`
(`isEmailTransportProvider` / `unsupportedProviderFix` from #5094, plus the
new `API_KEY_EMAIL_PROVIDERS` / `emailProviderRequiresApiKey`) rather than
restated in the CLI, and the new constant is tied to `makeTransport` by a
compile error in one direction and a contract test in the other.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017MCKJaEomEqg4tvz4SzdNd
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 4, 2026 6:33am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m labels Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/plugin-email.

24 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via packages/cli)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli)
  • content/docs/automation/flows.mdx (via @objectstack/plugin-email)
  • content/docs/automation/hook-bodies.mdx (via packages/cli)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli)
  • content/docs/deployment/environment-variables.mdx (via @objectstack/plugin-email)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/validating-metadata.mdx (via packages/cli)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli)
  • content/docs/plugins/index.mdx (via @objectstack/cli)
  • content/docs/plugins/packages.mdx (via @objectstack/cli, @objectstack/plugin-email)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/plugin-email)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli)
  • content/docs/releases/v16.mdx (via @objectstack/cli)
  • content/docs/releases/v17.mdx (via @objectstack/cli)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

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

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cli: OS_EMAIL_PROVIDER=resend/postmark 缺 apiKey 时静默降级为 LogTransport —— #5087 在 CLI 层遗留的同形缺口

2 participants