# Comment to DM Delivery: free dry run

**See which comments would get a private reply before anything is connected.** A read-only script, a sample register and the rules from the full kit.

## What it does

`dry_run.py` reads your register and a text file of comments and tags each one: REPLY with the keyword, no reply, or OPT-OUT. It matches whole words only, so "guidelines" does not fire "guide", and paused rows never fire. It sends nothing and needs no account.

## Run it

1. Save the three files below.
2. Put real comments from one of your posts into `comments.txt`, one per line.
3. `python3 dry_run.py register.csv comments.txt`

Prompt for Grok Bot, Claude Code, Codex or Hermes:
> Run python3 dry_run.py register.csv comments.txt and tell me which keywords would fire and any comment that fired by accident. Do not change the register.

## The rules worth keeping, even without the kit

1. Reply only to people who commented a keyword you published.
2. One private reply per comment, within 7 days of the comment (Meta's rule for posts and reels; [Source: Meta Instagram Platform docs, private replies, checked Nov 1, 2026](https://developers.facebook.com/docs/instagram-platform/private-replies)).
3. Follow-ups by a person, only after they reply, and within 24 hours of that reply (Meta's rule).
4. Approve the exact message text before it goes live.
5. STOP ends it, at once.
6. A kill switch that starts in the off position.

## The full kit adds

The Cloudflare Worker on Meta's official private reply path with signature checks, one-reply claims, the 7-day window, a conservative hourly cap you set and opt-outs; a register checker with 16 checks; a weekly log review; the three-stage test protocol; agent rules; and 20 tests. $29.

#### file: mini/dry_run.py
~~~~python
#!/usr/bin/env python3
"""Free sample: see which comments would get a private reply. Read-only. Sends nothing, needs no account.
python3 dry_run.py register.csv comments.txt   (comments.txt: one comment per line, optional 'MEDIA_ID<TAB>text')"""
import csv, re, sys
OPT = re.compile(r"\b(stop|unsubscribe|opt out|optout|no more)\b")
def norm(t): return re.sub(r"[^a-z0-9#]+", " ", (t or "").lower()).strip()
def match(rows, media, text):
    w = " " + norm(text) + " "
    for r in rows:
        if r.get("status") != "live": continue
        if r.get("post_scope") not in ("*", media): continue
        k = norm(r.get("keyword"))
        if k and " " + k + " " in w: return r
    return None
def run(rows, lines):
    out = []
    for l in lines:
        l = l.rstrip("\n")
        if not l.strip(): continue
        media, text = (l.split("\t", 1) + [""])[:2] if "\t" in l else ("", l)
        if OPT.search(norm(text)): out.append(("OPT-OUT", text)); continue
        r = match(rows, media, text)
        out.append(("REPLY " + norm(r["keyword"]), text) if r else ("no reply", text))
    return out
if __name__ == "__main__":
    if len(sys.argv) < 3: print(__doc__); sys.exit(2)
    rows = list(csv.DictReader(open(sys.argv[1], newline="", encoding="utf-8")))
    res = run(rows, open(sys.argv[2], encoding="utf-8"))
    for tag, text in res: print(f"{tag:16} {text[:70]}")
    print(f"{sum(t.startswith('REPLY') for t, _ in res)} of {len(res)} comments would get one private reply. Paused rows never fire.")
~~~~

#### file: mini/register.csv
~~~~csv
keyword,post_scope,asset_url,message,approved_by,approved_on,status
guide,*,https://example.com/free-guide,"Thanks for asking. Here is the guide.",Owner Name,2026-11-01,live
promo,*,https://example.com/offer,"Here is the offer.",,,paused
~~~~

#### file: mini/comments.txt
~~~~text
Guide please!
the guidelines were great
promo?
STOP
Can I get the GUIDE
~~~~
