Skip to content

Newsletter can remain pending after a crash before job scheduling #29987

Description

@fmercurio

Issue summary

Ghost's boot-time newsletter recovery handles recent submitting emails, but it does not recover a recent pending email when the process exits after the email row is persisted and before its background send job is scheduled.

This is adjacent to, but distinct from, the interrupted-send recovery merged in #27822.

Minimal reproduction

  1. Use a sendable post/newsletter and create an email row with status: pending and a positive recipient count.
  2. Simulate process termination after persistence succeeds but before scheduleEmail() has added the background job (so no email batches or recipients have been materialized).
  3. Restart Ghost within bulkEmail:resumeMaxAgeMs.
  4. Observe that the boot-time scanner only queries status:submitting; the eligible pending row is never scheduled and remains pending.

Expected behavior

A boot-only recovery pass should conservatively re-enter the ordinary CAS-protected scheduling path for a row only when it is recent and clearly represents this pre-scheduling crash window. It must not broadly resend pending newsletters or interfere with already-materialized work.

A candidate implementation applies these safeguards:

  • only rows created before the current service start and within the configured recovery-age bound;
  • only sendable parent posts;
  • no existing email batches or recipients;
  • normal status locking remains the final deduplication authority;
  • rows outside the narrow eligibility predicate remain untouched for operator review.

Why this matters

The email row is durable before the job is scheduled. A crash in that interval otherwise leaves an email that is neither queued for sending nor surfaced as a failed send.

Version and environment

Observed with Ghost v6.57.0 and reproduced against MySQL 8 using a controlled test harness. The affected logic remains present on main at the time of filing.

Proposed follow-up

I will open a focused draft PR with integration and unit coverage for this specific crash window. The test transport is mocked; no real recipient or provider data is involved.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    affects:serverIssues relating to the server or core of Ghostbug[triage] something behaving unexpectedlycore team[triage] Being looked at by the core Ghost team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions