{"record":{"id":"6a00eb4d67cf7ed0","repo":"TryGhost/Ghost","slug":"preview-response-was-incomplete-6a00eb","errorCode":null,"errorMessage":"Preview response was incomplete","messagePattern":"Preview response was incomplete","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/admin/src/settings/app/components/settings/membership/member-emails/use-welcome-email-preview.ts","lineNumber":104,"sourceCode":"\n        setPreviewState({status: 'loading'});\n\n        try {\n            // Only the latest preview request is allowed to update preview state.\n            const response = await previewWelcomeEmail({\n                id: automatedEmailId,\n                subject: draft.subject,\n                lexical: draft.lexical\n            });\n\n            if (previewRequestIdRef.current !== requestId) {\n                return;\n            }\n\n            const preview = response.automated_emails?.[0];\n\n            if (!preview?.html || !preview?.plaintext || !preview?.subject) {\n                throw new Error('Preview response was incomplete');\n            }\n\n            setPreviewState({\n                status: 'success',\n                preview: {\n                    ...preview,\n                    html: preparePreviewHtml(preview.html)\n                }\n            });\n        } catch (error) {\n            if (previewRequestIdRef.current !== requestId) {\n                return;\n            }\n\n            setPreviewState({\n                status: 'error',\n                message: getPreviewErrorMessage(error)\n            });","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/apps/admin/src/settings/app/components/settings/membership/member-emails/use-welcome-email-preview.ts#L86-L122","documentation":"Thrown by the welcome email preview hook (useWelcomeEmailPreview) after previewWelcomeEmail resolves, when the first automated_emails entry lacks any of html, plaintext, or subject. Same defensive contract as the automation preview: the Ghost Admin API for automated email previews must return all three rendered fields, and the hook refuses to render a partial preview.","triggerScenarios":"response.automated_emails is undefined, empty, or its [0] element has an empty/missing html, plaintext, or subject. Concretely: backend omits plaintext for a welcome email with no text body; API shape mismatch; render partially failed; automated_emails array empty because automatedEmailId doesn't match a real automated email.","commonSituations":"Admin/frontend version skew with backend (automated_emails serializer changed); mailer rendering service unavailable so html comes back empty; empty subject or empty Lexical draft; welcome email record deleted between opening settings and previewing; membership/tier feature flags affecting automated email registration.","solutions":["Inspect the preview request response in DevTools to identify which field is missing or empty.","If plaintext is legitimately empty for a text-less welcome email, relax the guard to require only html and degrade gracefully on the others.","Confirm admin and ghost/core versions are compatible (the automated_emails preview serializer must emit html, plaintext, and subject).","Verify the automatedEmailId still maps to a real automated email and the endpoint isn't returning an empty array.","Reproduce with a non-empty subject and a Lexical doc containing content to rule out empty-input edge cases."],"exampleFix":"// before\nif (!preview?.html || !preview?.plaintext || !preview?.subject) {\n    throw new Error('Preview response was incomplete');\n}\n\n// after — require only html for the iframe; treat missing plaintext/subject as non-fatal\nif (!preview?.html) {\n    throw new Error('Preview response did not contain rendered HTML');\n}","handlingStrategy":"fallback","validationCode":"// Before calling enterPreview, ensure the welcome email draft has real content\nimport {getWelcomeEmailValidationErrors} from './welcome-email-validation';\nconst errs = getWelcomeEmailValidationErrors(draft);\nif (errs.subject || errs.lexical || !draft.subject?.trim()) {\n    return;\n}","typeGuard":"// Narrow on the hook's previewFrameState before rendering\nimport type {WelcomeEmailPreviewFrameState} from './use-welcome-email-preview';\nfunction isErrorState(state: WelcomeEmailPreviewFrameState): state is {status: 'error' | 'invalid'; message: string} {\n    return state.status === 'error' || state.status === 'invalid';\n}","tryCatchPattern":"// enterPreview already catches internally and sets status:'error'; consumers render a fallback UI\nconst {previewFrameState} = useWelcomeEmailPreview({...});\nif (previewFrameState.status === 'success') {\n    return <iframe srcDoc={previewFrameState.html} />;\n}\nif (previewFrameState.status === 'error' || previewFrameState.status === 'invalid') {\n    return <PreviewError message={previewFrameState.message} />;\n}\nreturn <PreviewSkeleton />;","preventionTips":["Keep the admin frontend and ghost/core backend on compatible versions so the automated_emails preview serializer returns all three fields.","Don't request a preview for an empty/invalid welcome email draft.","Treat the hook's error state as a first-class UI branch."],"tags":["preview","api","react","async","ghost"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}