Skip to content

Optional delay before reacting to a comment, so replies don't look automated #63

Description

@alberto3197

The problem

Right now a matched comment gets its public reply and its DM as fast as the
queue can move, usually within a second or two of the comment being posted.
That timing is the giveaway: a human does not answer that fast, consistently,
at any hour.

What I'd propose

Two optional delays, both defaulting to 0 so nothing changes for existing
installs:

  • seconds to wait before posting the public comment reply
  • seconds to wait before sending the DM

Independent values, because they serve different purposes — a short pause on
the public reply reads as natural, while a long pause on the DM costs
conversion, since the commenter is still looking at their phone.

How it turned out to be cheap

processInstagramWebhook enqueues process-comment knowing only the account
and the comment; keyword matching against automations happens later, inside the
worker. So the webhook cannot read a per-campaign setting at enqueue time.

What made this easy is that processComment's two legs are already decoupled
and independently idempotent — publicReplySentAt for the public reply,
SENT / dmDeliveryUnconfirmed for the DM — and the job is already re-entrant
by design. So the webhook can simply enqueue the job twice, each with its own
delay and a leg: "reply" | "dm" discriminator, and each existing block is
gated on the leg. No new dedup, no restructuring, no sleep holding a worker
slot.

Two details worth flagging if you take this:

  • The leg suffix on the job id is load-bearing. The base id is deterministic,
    so without it BullMQ treats the second add as a duplicate and silently drops
    one of the legs.
  • The polling reconciler deliberately gets no delay. Everything it sweeps up is
    a comment the webhook already missed, so it is late already; holding it back
    further would only make a late reply later.

Omitting leg still runs both legs, so the reconciler and any job queued
before the change behave exactly as they do today.

The question before I open a PR

I put the setting on InstagramAccount rather than Automation, for the
constraint above: per-campaign delays would need an extra hop — run the job
immediately, match, then enqueue the delayed legs per matched automation. That
is doable but restructures the loop and changes the dedup surface, so I did not
want to guess.

There is also an argument that per-account is the better model regardless: how
fast you answer is a property of the account, not of one campaign. It would be
odd for one campaign to feel human and another robotic from the same handle.

Would you want this, and if so, per account or per campaign? Happy to redo
it the other way if you prefer — I'd rather ask than send a PR built on the
wrong assumption.

State

Implemented and running on my own instance: two commits, a migration adding two
INTEGER NOT NULL DEFAULT 0 columns, 11 tests covering the leg split and the
enqueue. Verified in production against a real comment — the queue shows two
delayed jobs firing at their separate times, and both messages arrive.

I can open the PR against a clean branch whenever you say.

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