Skip to content

Make newsletter delivery replay-safe and track per-recipient outcomes #81

Open

Description

@philipithomas

Severity: P1 — newsletter delivery reliability. Confirmed in main 6722c6b and still present after hardening PR #77.

Post#send_newsletter checks finished_sending? before enqueue, but does not claim sending atomically. PublishPostJob#perform neither checks that flag nor records a unique delivery for each post/subscription: it delivers to every active subscriber on every invocation. Two queued jobs or a retry after partial progress can therefore send duplicates. Ordinary recipient errors are logged and the post is still marked finished, with no durable failed-recipient retry state. The existing EmailMessage history has no unique delivery key.

Evidence: an isolated regression with a published post and one synthetic subscriber, with delivery mocked, runs PublishPostJob twice. It observes two deliveries to the same subscription even though the first run sets finished_sending. No external email was sent. This is separate from subscriber confirmation idempotence (#29, addressed in #77) and fatal-exception handling (#24).

Sources: Post, PublishPostJob, AccountMailer#new_post, email_messages schema.

Acceptance:

  • Define one delivery record/key per post and subscriber, with an atomic claim that prevents concurrent/duplicate jobs from both delivering it. Preserve a deliberate resend as an explicit separate operation.
  • Track pending, delivered, and failed recipients; derive completion from this state instead of marking all recipients finished after logged errors.
  • Handle provider-accepted-but-response-lost ambiguity explicitly: use provider idempotency if available or document recovery semantics. A database flag alone cannot promise exactly-once external delivery.
  • Test duplicate enqueue, concurrent workers, interruption after one accepted delivery, transient/permanent recipient failures, retry, unsubscribe/locked-author before delivery, and explicit resend. Use isolated mail/provider doubles in both modes.
  • Surface actionable delivery progress/failure counts without exposing subscriber addresses to other accounts.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions