feat: include Form ID as first entry of admin notification JSON#9732
feat: include Form ID as first entry of admin notification JSON#9732tishttash wants to merge 3 commits into
Conversation
Admins feeding the storage-mode admin notification JSON through an
automation (e.g. a CRM) had no way to tell which form a response came
from. Prepend a { "question": "Form name", "answer": form.title } entry
as the first element of the response JSON, ahead of "Response ID" and
"Timestamp".
The single data-assembly point (fullDataCollationData) feeds both the
standardised template and the legacy HTML path, so one change covers
both surfaces. The user-facing label is "Form name" even though the
backend field is `title`, matching the frontend and the CONTEXT.md
glossary.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Automations routing on the notification JSON need a stable, unique key. Form names are mutable and non-unique, so the form-name entry added in d05b371 is replaced by the form ID, which is what admins should route on. The entry leads the payload, before Response ID / Timestamp. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The admin-notification JSON is built in two independent places; the regular-form builder (sendSubmissionToAdmin) already leads with Form ID. Without this, MRF workflow-completion and approval emails would diverge, and admins routing both form types through one automation would see the leading key appear and disappear depending on form type. buildMrfResponseJson has a single call site feeding both MRF emails, so one insertion covers both. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| { question: 'Response ID', answer: params.submission.id }, | ||
| { question: 'Timestamp', answer: FORMATTED_SUBMISSION_TIME }, | ||
| ...params.dataCollationData, | ||
| // The template embeds the JSON as text, so double quotes are rendered |
There was a problem hiding this comment.
The standardised template embeds the response JSON as a text node (<p>{responseJson}</p>), so react-email escapes the JSON's double quotes to " in the rendered HTML. The test therefore builds the expected JSON with JSON.stringify(...) and then .replace(/"/g, '"') to match what actually lands in the email. Asserting on the rendered HTML (rather than reaching into the private #sendEmailWithTemplate args) keeps the test on observable output, at the cost of encoding this escaping detail.
| // automation can tell which form a response came from. Form ID is the | ||
| // stable identifier automations should route on. Mirrors the regular-form | ||
| // admin notification (mail.service.ts sendSubmissionToAdmin). | ||
| { question: 'Form ID', answer: formId }, |
There was a problem hiding this comment.
Gotcha for future changes: the admin-notification email JSON is assembled in two independent places that must be kept in sync — fullDataCollationData in mail.service.ts sendSubmissionToAdmin (regular forms; feeds both the standardised and legacy templates) and buildMrfResponseJson here (single call site; feeds both the workflow-completion and approval emails). Any field added to the notification JSON must land in both builders or MRF diverges — the original plan assumed one insertion point and silently missed MRF. MRF workflow-step and respondent-copy emails carry only formQuestionAnswers, not a JSON block, so they are correctly untouched.
Mirrors the table added to PR opengovsg#9732 so the docs artifact stays the source of truth for the PR body. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review findings — Form ID in admin notification JSON
StandardsClean — no surviving findings. The diff aligns with CONTEXT.md's glossary ("Form ID is the identifier automations should route on"; "Form name is for human reading only"), and both known builders of the notification JSON were updated consistently. Dropped below threshold after verification:
SpecClean — all acceptance criteria genuinely satisfied. Verified against Dropped below threshold after verification:
ArchitectureOne non-blocking finding.
DivergentConfirmed as the best available option: putting the ID in the JSON body (email headers are invisible to JSON-consuming automations; titles are unstable), passing One minor finding.
SummaryStandards 0, Spec 0, Architecture 1 (non-blocking), Divergent 1 (minor) surviving findings; 3 dropped below threshold. Worst issue: the duplicated metadata-prefix contract. Nothing blocks merging. |
|
Warning Endor Labs detected 1 policy violations associated with this pull request. Please review the findings that caused the policy violations.
|
Problem
Many form admins built automations on email subject lines or body content; the email-standardisation work broke those integrations. As admins move to the machine-parseable JSON snippet in the admin notification email, that JSON needs to say which form a response came from — today it starts at "Response ID" and carries no form identifier. The Form ID is the stable, globally-unique key automations should route on (form names are mutable and non-unique). Ref: Zendesk ticket 62023; PRD "JSON improvements".
Solution
Prepend
{ "question": "Form ID", "answer": <form._id as string> }as the first entry of the admin-notification response JSON. The JSON is assembled in two independent builders, and both are covered:sendSubmissionToAdmininmail.service.tsfor regular forms (one array feeds both the standardised and legacy templates), andbuildMrfResponseJsonfor MRF forms (a single call site feeds both the workflow-completion and approval emails, via a newformIdparam). The branch's first commit added a "Form name" entry; the follow-up replaces it with Form ID only, so the net change againstdevelopis the Form ID entry alone.Before vs after — the JSON block in the admin notification email
[ {"question":"Response ID", "answer":"6a41e30480938b7dd6c4ea64"}, {"question":"Timestamp", "answer":"Mon, 29 Jun 2026 11:14:25 AM"}, {"question":"Email", "answer":"tasha@open.gov.sg"}, {"question":"Short answer", "answer":""} ][ {"question":"Form ID", "answer":"64f8a1b2c3d4e5f60718293a"}, {"question":"Response ID", "answer":"6a41e30480938b7dd6c4ea64"}, {"question":"Timestamp", "answer":"Mon, 29 Jun 2026 11:14:25 AM"}, {"question":"Email", "answer":"tasha@open.gov.sg"}, {"question":"Short answer", "answer":""} ]The same shape applies to regular-form notifications and MRF workflow-completion / approval emails.
Alternatives considered
buildMrfResponseJson) so it is at least visible.Breaking Changes
No - backwards compatible (a new entry prepended to the response JSON array; existing entries and shape unchanged).
Tests
TC1: Regular form — admin notification
-- Start of JSON --block's first entry is{"question":"Form ID","answer":"<form id>"}, before "Response ID" and "Timestamp", and the ID matches the form's dashboard URL.TC2: MRF — workflow completion
TC3: MRF — approval outcome
TC4: No leakage into CSV
🤖 Generated with Claude Code