{"record":{"id":"ae71962cf6dc862d","repo":"TryGhost/Ghost","slug":"email-content-is-required-ae7196","errorCode":null,"errorMessage":"Email content is required","messagePattern":"Email content is required","errorType":"validation","errorClass":"ValidationError","httpStatus":422,"severity":"error","filePath":"ghost/core/core/server/api/endpoints/utils/validators/input/automation_email_previews.js","lineNumber":28,"sourceCode":"    invalidEmailReceived: 'The server did not receive a valid email',\n    invalidLexical: 'Lexical must be a well-formed Lexical document',\n    subjectRequired: 'Subject is required',\n    lexicalRequired: 'Email content is required'\n};\n\nconst validatePreviewData = async (frame) => {\n    const subject = frame.data.subject;\n    const lexical = frame.data.lexical;\n\n    if (typeof subject !== 'string' || !subject.trim()) {\n        throw new ValidationError({\n            message: tpl(messages.subjectRequired),\n            property: 'subject'\n        });\n    }\n\n    if (typeof lexical !== 'string' || !lexical.trim()) {\n        throw new ValidationError({\n            message: tpl(messages.lexicalRequired),\n            property: 'lexical'\n        });\n    }\n\n    if (!await lexicalLib.validate(lexical)) {\n        throw new ValidationError({\n            message: tpl(messages.invalidLexical),\n            property: 'lexical'\n        });\n    }\n};\n\nmodule.exports = {\n    async preview(apiConfig, frame) {\n        await validatePreviewData(frame);\n    },\n","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/ghost/core/core/server/api/endpoints/utils/validators/input/automation_email_previews.js#L10-L46","documentation":"A ValidationError thrown by the automation email preview validator when `frame.data.lexical` is missing, not a string, or whitespace-only. The validator treats `lexical` (the Lexical editor JSON serialized as a string) as mandatory for both the `preview` and `sendTestEmail` operations. It fires before the Lexical structural validation, so it only catches empty/absent content.","triggerScenarios":"Calling the automation preview/send-test-email endpoint with no `lexical` field, with `lexical: null`, or with `lexical: \"\"`. Also when the caller passes a Lexical object instead of its JSON-serialized string form and the field ends up undefined after frame parsing.","commonSituations":"A post has no Lexical document (e.g. a brand-new/draft post with empty content); the editor's `serialize()` output was not stringified before being sent; the wrong key name is used (e.g. `mobiledoc` or `html` instead of `lexical`); a migration left posts with null content.","solutions":["Stringify the Lexical editor state and pass it as `data.lexical`: `JSON.stringify(editorState.toJSON())`.","Verify the post actually has content before triggering the preview — guard with a non-empty check upstream.","Use the correct field name `lexical` (not `mobiledoc` or `html`) for this endpoint.","If the source content is HTML/Mobiledoc, convert it to a Lexical document first via the Ghost lexical converter."],"exampleFix":"// before\nawait api.sendTestEmail({data: {email, subject, lexical: editorState.toJSON()}});\n\n// after\nconst lexical = JSON.stringify(editorState.toJSON());\nif (!lexical.trim()) throw new Error('Editor has no content');\nawait api.sendTestEmail({data: {email, subject, lexical}});","handlingStrategy":"validation","validationCode":"function ensureLexicalString(lexical) {\n  if (typeof lexical !== 'string' || !lexical.trim()) {\n    throw new Error('Email content is required');\n  }\n  return lexical;\n}","typeGuard":"const isLexicalString = (v) => typeof v === 'string' && v.trim().length > 0;","tryCatchPattern":"try {\n  await api.preview({data: {subject, lexical}});\n} catch (err) {\n  if (err.type === 'ValidationError' && err.property === 'lexical' && /required/i.test(err.message)) promptForContent();\n  else throw err;\n}","preventionTips":["Always stringify the editor state with JSON.stringify before sending.","Disable the submit button until the editor has non-empty serialized content.","Use the field name `lexical`, not `mobiledoc`/`html`."],"tags":["validation","automation","email-preview","lexical","api-input"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}