{"record":{"id":"893a6de8557c71cc","repo":"TryGhost/Ghost","slug":"post-email-error","errorCode":null,"errorMessage":"post.email.error","messagePattern":"post\\.email\\.error","errorType":"exception","errorClass":"EmailFailedError","httpStatus":null,"severity":"error","filePath":"apps/ember-admin/app/components/editor/publish-management.js","lineNumber":303,"sourceCode":"\n        let pollTimeout = 0;\n        if (post.email && post.email.status !== 'submitted') {\n            while (pollTimeout < CONFIRM_EMAIL_MAX_POLL_LENGTH) {\n                yield timeout(CONFIRM_EMAIL_POLL_LENGTH);\n                pollTimeout += CONFIRM_EMAIL_POLL_LENGTH;\n\n                yield post.reload();\n\n                if (!post.isSent && !post.isPublished) {\n                    // A post that is not published doesn't try to send or retry an email\n                    break;\n                }\n\n                if (post.email.status === 'submitted') {\n                    break;\n                }\n                if (post.email.status === 'failed') {\n                    throw new EmailFailedError(post.email.error);\n                }\n            }\n        }\n\n        return true;\n    }\n\n    @task\n    *revertToDraftTask() {\n        try {\n            yield this.publishTask.perform({taskName: 'revertToDraftTask'});\n\n            const postType = capitalize(this.args.post.displayName);\n            this.notifications.showNotification(`${postType} reverted to a draft.`, {type: 'success'});\n\n            return true;\n        } catch (e) {\n            this.notifications.showAPIError(e);","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/apps/ember-admin/app/components/editor/publish-management.js#L285-L321","documentation":"Thrown as an EmailFailedError (ghost-admin/app/errors/email-failed-error.js) when a published post's email record has status 'failed'. The publish-management retry loop reloads the post, checks post.email.status, and surfaces the underlying post.email.error to the editor. It represents a server-side email delivery failure, not a client-side validation problem.","triggerScenarios":"After triggering send/retry of a post email via publish-management's publishTask, the loop polls post.reload(); the returned post is isSent or isPublished AND post.email.status === 'failed'. The error message is whatever the API returned in post.email.error (e.g. Mailgun timeout, invalid recipient domain, rate limit).","commonSituations":"Mailgun/SES provider misconfiguration in Ghost settings, DNS/MX issues on recipient domains, hitting provider rate limits during large broadcasts, scheduled post going out while email service credentials expired, or the post was retried after a partial failure and the second attempt also failed.","solutions":["In Ghost admin go to Settings > Email and verify the from-address and mail service connection, then re-test delivery.","Open the post in the editor and use the retry-email action; if it fails again, inspect the exact post.email.error payload from the API response (Network tab on /admin/posts/<id>/?include=email) for the provider's reason.","If using a custom SMTP/Mailgun setup, confirm the configured domain's SPF/DKIM/MX records and that the API key has 'send' scope.","For rate-limit failures, wait and reduce batch size or schedule the send; check the email service provider dashboard for throttling."],"exampleFix":"// before: status==='failed' surfaces raw provider error\nthrow new EmailFailedError(post.email.error);\n\n// caller (publish-flow.js:103) already discriminates by name:\nif (e?.name === 'EmailFailedError') { /* show modal with retry */ }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"// distinguish the email-failed signal from other errors\nfunction isEmailFailedError(e) {\n    return e?.name === 'EmailFailedError' || /email/i.test(e?.message || '');\n}","tryCatchPattern":"try {\n    yield this.publishTask.perform({taskName: 'sendEmailTask'});\n} catch (e) {\n    if (e?.name === 'EmailFailedError') {\n        // show the email-failure modal with retry option (see publish-flow.js:103)\n        this.showEmailFailureModal(e);\n    } else {\n        throw e; // rethrow non-email errors\n    }\n}","preventionTips":["Always check post.email.status after reload and before declaring success.","Wrap email send in a retry-with-backoff that distinguishes transient (rate-limit) from permanent (config) failures.","Surface post.email.error verbatim in the UI so the operator can act on the provider's reason.","Validate mail-service credentials in Settings > Email before scheduling large broadcasts."],"tags":["email","ghost-admin","ember","publishing","retry"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}