Found while implementing objectstack-ai/hotcrm#1185 (localize 16 notify nodes). The wording below is what made that card look actionable; the implementation is correct and the spec doc block is what drifted, so filing against packages/spec.
The drift
packages/spec/src/automation/io-node-config.zod.ts states, in the NotifyConfigSchema doc block (line ~135) and again in the template field .describe() (line ~188):
- L135: "the delivery path resolves
(name, recipient locale)per recipient at delivery time via IEmailService.sendTemplate({ template, locale })" - L188: "the delivery path resolves
(name, recipient locale) at delivery time and renders subject/body per recipient"
Read plainly, both say the recipient's own locale selects the template row.
The delivery path's own source says the opposite, and says it honestly — packages/services/service-messaging/src/email-channel.ts L86-99:
"The measured source, and its limits, spelled out: the platform has no per-user locale today (sys_user carries no locale column; the 2026-08-13 ruling defers one until measured pull) … So 'recipient locale' resolves to the deployment default — II18nService.getDefaultLocale()."
So "recipient locale" is a term of art here meaning deployment default, and the spec block is the one place it is not qualified.
Measured on the published 17.1.0 artifacts
Two recipients, different locales, one notification, real createEmailChannel / createInboxChannel, stub email service capturing what reaches sendTemplate / renderTemplate:
sendTemplate to=en@example.com locale="en"
renderTemplate to=(inbox) locale="en"
sendTemplate to=zh@example.com locale="en" <- zh-CN recipient
renderTemplate to=(inbox) locale="en" <- zh-CN recipient
The only lever is payload.locale, which is interpolated once before fan-out, so it is one value for the whole notification (verified at all three channel.send call sites in service-messaging: fanOut, the outbox single-delivery path, and processDigestGroup — each passes the same payload to every recipient).
Also confirmed: sys_user has no locale field (and carries protection.lock: "full"), and sys_notification_preference has none either — so there is no per-recipient source to read even if a channel wanted one.
service-messaging@17.2.0 is byte-identical on the two locale-resolution lines, so this is not a "fixed in a later release" situation.
Why it is worth a wording fix rather than a shrug
The doc block is the contract a consuming app reads. Quoted at face value it licenses exactly one conclusion — "convert the nodes and non-English users get non-English notifications" — which is false, and the conversion is a net regression when made on that belief (TEMPLATE_* failures classify permanent and dead-letter, and the inbox channel starts requiring an email service with renderTemplate() where inline text needed none).
One nearby log line has the same issue — service-messaging/src/plugin.ts: "notify template refs resolve sys_email_template per recipient locale".
Suggested fix
Qualify the three sites the way email-channel.ts already does — name the resolved value the deployment default (II18nService.getDefaultLocale()), and note that a per-user locale is deferred by the 2026-08-13 ruling and layers in as an override when it lands. No behaviour change.
Note packages/spec/src/automation/io-node-config.test.ts L277/L303 assert /recipient locale/ against these strings, so a reword touches those two assertions.
Not filing the per-user-locale capability itself here — that is the deferred half of the 2026-08-13 ruling and belongs to whoever weighs the pull; hotcrm#1185 is one such datapoint and says so.
Generated by Claude Code
Found while implementing objectstack-ai/hotcrm#1185 (localize 16
notifynodes). The wording below is what made that card look actionable; the implementation is correct and the spec doc block is what drifted, so filing againstpackages/spec.The drift
packages/spec/src/automation/io-node-config.zod.tsstates, in theNotifyConfigSchemadoc block (line ~135) and again in thetemplatefield.describe()(line ~188):(name, recipient locale)per recipient at delivery time viaIEmailService.sendTemplate({ template, locale })"(name, recipient locale)at delivery time and renders subject/body per recipient"Read plainly, both say the recipient's own locale selects the template row.
The delivery path's own source says the opposite, and says it honestly —
packages/services/service-messaging/src/email-channel.tsL86-99:So "recipient locale" is a term of art here meaning deployment default, and the spec block is the one place it is not qualified.
Measured on the published
17.1.0artifactsTwo recipients, different locales, one notification, real
createEmailChannel/createInboxChannel, stub email service capturing what reachessendTemplate/renderTemplate:The only lever is
payload.locale, which is interpolated once before fan-out, so it is one value for the whole notification (verified at all threechannel.sendcall sites inservice-messaging:fanOut, the outbox single-delivery path, andprocessDigestGroup— each passes the same payload to every recipient).Also confirmed:
sys_userhas nolocalefield (and carriesprotection.lock: "full"), andsys_notification_preferencehas none either — so there is no per-recipient source to read even if a channel wanted one.service-messaging@17.2.0is byte-identical on the two locale-resolution lines, so this is not a "fixed in a later release" situation.Why it is worth a wording fix rather than a shrug
The doc block is the contract a consuming app reads. Quoted at face value it licenses exactly one conclusion — "convert the nodes and non-English users get non-English notifications" — which is false, and the conversion is a net regression when made on that belief (
TEMPLATE_*failures classifypermanentand dead-letter, and the inbox channel starts requiring an email service withrenderTemplate()where inline text needed none).One nearby log line has the same issue —
service-messaging/src/plugin.ts: "notifytemplaterefs resolve sys_email_template per recipient locale".Suggested fix
Qualify the three sites the way
email-channel.tsalready does — name the resolved value the deployment default (II18nService.getDefaultLocale()), and note that a per-user locale is deferred by the 2026-08-13 ruling and layers in as an override when it lands. No behaviour change.Note
packages/spec/src/automation/io-node-config.test.tsL277/L303 assert/recipient locale/against these strings, so a reword touches those two assertions.Not filing the per-user-locale capability itself here — that is the deferred half of the 2026-08-13 ruling and belongs to whoever weighs the pull; hotcrm#1185 is one such datapoint and says so.
Generated by Claude Code