Skip to content

Prevent duplicate opening private replies from polling races #69

Description

@goat255

Duplicate opening private replies after a polling sweep

Summary

When a campaign uses an opening DM, the same Instagram comment can receive the opening private
reply repeatedly on each polling sweep. The worker and diagnostics then report repeated Meta API
errors for the same comment.

This was reproduced against upstream commit 0d723ef with direct Instagram Login and Meta Graph
API v25.0.

Reproduction

  1. Enable a campaign with an opening DM for a keyword comment.
  2. Let the comment be processed by the normal comment path and recorded in DmLog.
  3. Allow a second polling or redelivered queue job for the same (automationId, commentId) pair to
    overlap the first job.
  4. Observe the worker logs and DmLog row.

What happens

The first request can successfully send the opening private reply and the public comment reply.
The overlapping request then tries to send the private reply again. Instagram permits only one
private reply per comment, so the second request returns a generic Meta code 1 or 2 error.

OpenReply updates the existing row to FAILED even when dmSentAt is already populated. The
polling reconciler considers only status = SENT or dmDeliveryUnconfirmed = true to be handled,
so it enqueues the comment again on the next sweep. Because the campaign still has
openingDmEnabled, every reprocessed comment attempts the opening DM again.

Impact

Users can receive repeated opening messages, while the dashboard reports a failure for a message
that may already have been delivered. Repeated retries also create avoidable Meta API errors and
can damage the account's messaging experience.

Proposed fix

  1. Treat dmSentAt IS NOT NULL as an authoritative comment-stage delivery signal, independent of
    the final status string.
  2. Atomically claim (automationId, commentId) before making the external private-reply call.
    A unique-create race or a fresh PENDING claim must not send a second request; stale claims
    can be reclaimed after a bounded lease.
  3. Treat Meta code 1 and 2 from the private-reply path as delivery-uncertain and stop automatic
    BullMQ retries. The row should remain handled until an operator reviews it.

I have implemented and tested this fix locally. The relevant checks pass with 275 tests and a
production Next.js build. The patch is intentionally small and does not change the opening-DM or
reveal UX.

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions