TryGhost/Ghost · error
Preview response was incomplete
Error message
Preview response was incomplete
What it means
Thrown by the automation email preview hook (useEmailPreview) after a successful call to previewAutomationEmail when the resolved response's first automation_email_previews entry is missing any of html, plaintext, or subject (undefined or empty string). It is a defensive contract check: the Ghost Admin API preview endpoint is expected to return all three rendered fields, and the hook refuses to render a partial preview rather than show a broken iframe.
Source
Thrown at apps/admin/src/automations/components/email-modal/use-email-preview.ts:104
setErrors({});
setPreviewState({status: 'loading'});
try {
// Only the latest preview request is allowed to update preview state.
const response = await previewAutomationEmail({
id: automationId,
subject: draft.subject,
lexical: draft.lexical
});
if (previewRequestIdRef.current !== requestId) {
return;
}
const preview = response.automation_email_previews?.[0];
if (!preview?.html || !preview?.plaintext || !preview?.subject) {
throw new Error('Preview response was incomplete');
}
setPreviewState({
status: 'success',
preview: {
...preview,
html: preparePreviewHtml(preview.html)
}
});
} catch (error) {
if (previewRequestIdRef.current !== requestId) {
return;
}
setPreviewState({
status: 'error',
message: getPreviewErrorMessage(error)
});View on GitHub (pinned to 47d8b0e2ad)
Solutions
- Inspect the actual API response in DevTools (Network tab, the preview request) to see which of html/plaintext/subject is missing or empty.
- If the backend legitimately returns empty plaintext for a text-less draft, relax the guard to require only html (the iframe payload) and treat missing plaintext/subject as non-fatal.
- Verify the admin app and ghost/core backend are on compatible versions (the automation_email_previews serializer must emit all three fields).
- Confirm the automationId passed to the hook still exists and the preview endpoint isn't silently returning an empty array for a missing automation.
- Reproduce with a non-empty subject and a Lexical doc containing a paragraph node to rule out empty-input edge cases.
Example fix
// before
if (!preview?.html || !preview?.plaintext || !preview?.subject) {
throw new Error('Preview response was incomplete');
}
// after — require only html for the iframe; surface missing text fields as a warning, not a hard failure
if (!preview?.html) {
throw new Error('Preview response did not contain rendered HTML');
} Defensive patterns
Strategy: fallback
Validate before calling
// Before calling enterPreview, ensure the draft has real content so the backend can render all fields
import {getEmailValidationErrors} from './validation';
const errs = getEmailValidationErrors(draft);
if (errs.lexical || !draft.subject?.trim()) {
// don't request a preview the backend can't fully render
return;
} Type guard
// Narrow on the hook's previewFrameState before rendering
import type {EmailPreviewFrameState} from './use-email-preview';
function isErrorState(state: EmailPreviewFrameState): state is {status: 'error' | 'invalid'; message: string} {
return state.status === 'error' || state.status === 'invalid';
} Try / catch
// enterPreview already catches internally and sets status:'error'; consumers should treat any non-success state as a fallback UI branch
const {previewFrameState} = useEmailPreview({...});
if (previewFrameState.status === 'success') {
return <iframe srcDoc={previewFrameState.html} />;
}
if (previewFrameState.status === 'error' || previewFrameState.status === 'invalid') {
return <PreviewError message={previewFrameState.message} />;
}
return <PreviewSkeleton />; // loading Prevention
- Keep the admin frontend and ghost/core backend on compatible versions so the automation_email_previews serializer returns html, plaintext, and subject.
- Don't request a preview for an empty/invalid draft — validate subject and Lexical content first.
- Treat the hook's error state as a first-class UI branch, not an edge case.
When it happens
Trigger: response.automation_email_previews is undefined, empty, or its [0] element has an empty/missing html, plaintext, or subject field. Concretely: the backend omits plaintext because the Lexical doc produced no text content; the API version returns a different shape; the preview render partially failed server-side but still 200-ed; or the automation_email_previews array came back empty because automationId didn't match an automation.
Common situations: Admin frontend version newer/older than the backend API (field renamed/removed); email rendering service (mailer) misconfigured or down so it returns no html; empty subject or empty Lexical draft reaching the backend; automation deleted between opening the modal and clicking preview; feature flag for automation emails partially enabled.
Related errors
- Preview response was incomplete
- Subject is required
- Email content is required
- Lexical must be a valid JSON string
- A view with this name already exists
AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13).
Data as JSON: /api/errors/c1f7cb02d29bf8d7.
Report an issue: GitHub.