feat(intake): add the submission notification template (#224)

Editable from the settings screen like every other template. reviewUrl is the only required placeholder: a notification with no link in it still sends, still looks fine in the log, and is useless to whoever receives it, which is what the required-placeholder validation exists to catch.

The two signed links are deliberately optional. They are absent whenever INTAKE_ACTION_SECRET is unset, and a template demanding them would leave an unconfigured environment unable to send this at all.

A test asserts the template offers no way to publish. That the email cannot publish is what bounds the risk taken by pricing items on arrival, and it is a property of the copy as much as of the routes — a publish link in the body would be one nobody reviewed.

Where the notification goes is an admin setting rather than an environment variable, for the same reason drafting_model is one: it is changed by whoever runs the shop, not by whoever deploys it. Empty is the default and means do not notify, which is a working configuration.

INTAKE_ACTION_SECRET warns rather than fails at boot, like the drafting key. Being told an item arrived matters far more than being able to discard it in one click.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-01 15:23:32 -05:00
co-authored by Claude Opus 5
parent acc1b406d8
commit 0517faca60
5 changed files with 110 additions and 3 deletions
+14 -1
View File
@@ -170,6 +170,19 @@ function checkAdminGate(env: NodeJS.ProcessEnv): string[] {
// without its description written. Silence would be the wrong answer too: an
// operator who believes drafting is on and finds every item undrafted has
// nothing to tell them why.
// Optional, like the drafting key below. Absent, the notification still sends
// with its review link and simply carries no shortcuts — being told an item
// arrived matters far more than being able to discard it in one click.
function checkIntakeActionSecret(env: NodeJS.ProcessEnv): string[] {
if (isPresent(env, 'INTAKE_ACTION_SECRET')) {
return [];
}
return [
'INTAKE_ACTION_SECRET is not set — intake notifications will link to the review queue ' +
'but carry no regenerate or discard shortcuts.'
];
}
function checkDraftingKey(env: NodeJS.ProcessEnv): string[] {
if (isPresent(env, 'ANTHROPIC_API_KEY')) {
return [];
@@ -223,6 +236,6 @@ export function validateEnv(env: NodeJS.ProcessEnv): EnvValidation {
...mail.errors,
...uploads.errors
],
warnings: [...mail.warnings, ...checkAdminGate(env), ...uploads.warnings, ...checkDraftingKey(env)]
warnings: [...mail.warnings, ...checkAdminGate(env), ...uploads.warnings, ...checkDraftingKey(env), ...checkIntakeActionSecret(env)]
};
}