Reply to DMs that match a campaign's keywords - #9
Merged
diwenne merged 1 commit intoJul 31, 2026
Conversation
A campaign can now fire on inbound DMs as well as comments. Turning on "also reply when someone DMs these words" makes the worker answer any DM whose text matches the campaign's keywords with the same reveal message, honouring the follow gate, the appreciation follow-up, and the monthly send limit. The `messages` webhook field was already subscribed; only the parser was missing. Echoes are dropped so an autoreply containing its own keyword cannot trigger itself, and each inbound message id dedupes to one reply. The opening DM is skipped on this path: the user has already opened the conversation, so the private-reply workaround it exists for does not apply. The postback and DM-trigger paths now share one reveal sender, which keeps the template-rejection fallback in a single place. The builder gets a toggle and a conditional "DM trigger" preview tab showing that thread — their message, the follow prompt if gated, then the reveal — since the existing DM tab always opens with the account's opening DM, which never happens on this path.
|
@kimthangk is attempting to deploy a commit to the diwenne's projects Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A campaign can now fire on inbound DMs as well as comments. Turning on "also reply when someone DMs these words" makes the worker answer any DM whose text matches the campaign's keywords with the same reveal message.
The motivation: people who see a reel often skip the comment and just DM the keyword directly. Today that goes unanswered.
Story replies come through too — Instagram delivers them via the same
messagesfield as an ordinary message with text.How
parseMessageEvents(lib/meta/webhook.ts) —subscribed_fieldsalready includedmessages; only the parser was missing. Echoes are dropped, which matters here: without that, an autoreply whose text contains its own keyword would trigger itself. Deletions, unsupported messages, and attachment-only messages with no text are dropped too.processMessage(lib/queue/dm-worker.ts) — newprocess-messagejob. Dedup is per inbound message id (DmLog.commentId=dm:<mid>), so a job retry never double-sends.Automation.dmTriggerEnabled— additive column, defaultsfalse, so existing campaigns are untouched.Two decisions worth reviewing
The opening DM is skipped on this path. It exists to work around the one-private-reply-per-comment limit — the button tap is what opens a messaging window. A user who DMs you has already opened that window, so the button buys nothing and costs a send plus a drop-off point. The follow gate is preserved, because its button does real work (re-verifying the follow).
The follow-up fires here too. A DM-triggered link delivery is the same "link delivered" moment as a button tap, so it schedules the appreciation follow-up identically — but only on the reveal branch, not behind the follow prompt, where no link has gone out yet.
Both are judgement calls; happy to change either.
Refactor
processPostbackandprocessMessageboth send a reveal into an already-open conversation, so the button-template-with-inline-link-fallback logic is now one sharedsendRevealDirectMessage. This keeps theisTemplateRejectionguard from 99600c2 in a single place rather than duplicated.processCommentis untouched — it still uses the private-reply variants.Not included
reply_to.storyplumbed through plus another campaign field, which felt like a separate change.Checks
All four from CONTRIBUTING pass on this branch:
npm run typecheck— cleannpm run lint— cleannpm test— 132 passed, including 16 new (parser: echoes, deletions, attachment-only, self-sends, non-instagram payloads; worker: match, no-match, dedup, tracked-link buttons, follow gate both ways, plan limit, send failure)npm run build— cleanNot verified against live Instagram traffic for the story-reply case specifically — that claim is based on the payload shape, not an observed webhook.