TryGhost/Ghost · error · EmailFailedError

email.error

Error message

email.error

What it means

retryEmailTask is an ember-concurrency task that retries sending a post email and polls the email record's status until 'submitted' or a timeout. When email.reload() returns status === 'failed', it throws EmailFailedError(email.error) where email.error is the backend-provided failure reason. The catch block checks e.name === 'EmailFailedError' and surfaces the message as newEmailErrorMessage. The error is not a thrown-to-caller error — it is caught internally and converted to a UI state.

Source

Thrown at apps/ember-admin/app/components/editor/modals/publish-flow/complete-with-email-error.js:48

    *retryEmailTask() {
        this.retryErrorMessage = null;

        try {
            let email = yield this.args.publishOptions.post.email.retry();

            let pollTimeout = 0;
            if (email && email.status !== 'submitted') {
                while (pollTimeout < CONFIRM_EMAIL_MAX_POLL_LENGTH) {
                    yield timeout(CONFIRM_EMAIL_POLL_LENGTH);
                    pollTimeout += CONFIRM_EMAIL_POLL_LENGTH;

                    email = yield email.reload();

                    if (email.status === 'submitted') {
                        break;
                    }
                    if (email.status === 'failed') {
                        throw new EmailFailedError(email.error);
                    }
                }
            }

            this.args.setCompleted();

            return email;
        } catch (e) {
            // update "failed" state if email fails again
            if (e && e.name === 'EmailFailedError') {
                this.newEmailErrorMessage = e.message;
                return false;
            }

            if (e) {
                let errorMessage = '';

                if (isServerUnreachableError(e)) {

View on GitHub (pinned to 47d8b0e2ad)

Solutions

  1. Inspect the email.error message displayed in the UI (newEmailErrorMessage) — it carries the provider's failure reason (e.g. 'Mailgun domain not verified').
  2. Verify the email provider configuration in Ghost Labs/settings: Mailgun domain/API key or custom SMTP credentials.
  3. Check the Mailgun/SMTP provider dashboard for the specific failure (bounces, blocks, domain verification).
  4. If 'partially' failed (isPartialError), some recipients received the email — review the recipient list and re-send to the failed subset after fixing the provider issue.
Defensive patterns

Strategy: try-catch

Type guard

function isEmailFailedError(e: unknown): e is Error {
  return e instanceof Error && (e as any).name === 'EmailFailedError';
}

Try / catch

// the task already catches internally; this is the pattern it uses:
try {
  yield this.args.publishOptions.post.email.retry();
  // ... polling loop ...
} catch (e) {
  if (isEmailFailedError(e)) {
    this.newEmailErrorMessage = e.message; // provider's failure reason
    return false;
  }
  // other errors (network, unknown)
}

Prevention

When it happens

Trigger: The email send (Mailgun/SMTP) failed server-side and the email record's status transitioned to 'failed' with an error string in email.error. The polling loop (up to CONFIRM_EMAIL_MAX_POLL_LENGTH) observes this status. Causes: Mailgun API error, SMTP connection failure, invalid recipient list, bounce/blocklist, or rate limit from the email provider. The email.error field carries the provider-specific message.

Common situations: A publication's Mailgun domain is not verified, causing all sends to fail. An SMTP integration (custom mailer) has wrong credentials. The recipient list contains addresses that cause a hard bounce on a previous send, triggering provider-level blocks. A large blast hits the provider rate limit and some emails fail.

Related errors


AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13). Data as JSON: /api/errors/eb91be7728212f3b. Report an issue: GitHub.