A reader comments “link please” at 11 p.m. Twenty seconds later, our comment-to-DM service DMs them the article. Nobody on my team touched it.
My name is Gian Viterbo and I run Gadget Pilipinas and Blip Media. I am not a developer. I am a publisher who got tired of watching good reader questions go unanswered, because answering them well does not scale.
That is what our comment-to-DM service fixes. It reads a campaign code in a comment and replies privately with the article asked for. It went live September 25 and sent 38 replies in its first 28 hours, with no human in the loop.
Why we built comment-to-DM
Readers ask for the link constantly. “Link please.” “Saan po ito?” Our social editors answered those by hand, or the comment sat there. The obvious fix, put the link in the caption, does not survive reality.
I went back through 12 recent Instagram captions and counted how often the article’s own link appeared. It was there once. Captions carry sponsor tags and cross-links, so a reader has to guess which one. And a caption cannot be edited after publishing, so we never learn which post drove a read.
The one reply that has to be right
Meta allows exactly one private reply per comment. Ever. There are no retries. Everything here is built around getting that single send right: a reader comments a code, and the comment-to-DM service resolves it to the article.
The code is the whole trick, and it is why comment-to-DM is deterministic. A code is armed with the article’s permalink before the post goes up, so resolving it is a lookup, not a guess. We measured the alternative: a keyword search of our own site matched correctly twice out of those 12 captions. A usable backstop, a bad plan. When it cannot resolve a link with certainty, it sends nothing.
The guard rails all exist because of that one-reply rule:
- One reply per comment, ever. The comment id is recorded, so a duplicate send is impossible by design.
- A 7-day window from when the comment was posted, matching what Meta allows.
- A hard hourly ceiling set below Meta’s limit, so a viral post throttles instead of failing.
- Fail-closed signatures. Anything unsigned is rejected outright.
Architecture note: the webhook listener does no work inline. It verifies the signature, queues the event, and answers 200 immediately, so a slow reply can never look like a failure to Meta. A job drains that queue every minute, and it accepted 1,266 events in its first 28 hours without dropping any.

The Newsroom owns the campaigns
A campaign a marketing team cannot launch themselves is one they will not use. So the live campaign list is a Google Sheet. Column A is the code, column B the article link. The sheet is the source of truth, mirrored by a sync job every ten minutes. Add a row and the code is armed. Delete the row and it stops.
It is guarded too. A failed read changes nothing, and a link pointing anywhere but our own domains is rejected. An empty sheet, or more than five removals in one pass, is refused rather than applied, since both look more like an outage than a decision.
The AI harness behind comment-to-DM
Running comment-to-DM unattended means something must watch it. Three scheduled jobs do: one drains the queue every minute, one checks the service every five, one mirrors the sheet every ten. All three are deliberately silent, printing nothing unless a comment needs a person. Our AI channel is an exception channel, not a firehose.
We also screen incoming comments with a decision model. It is not a chatbot: you send comment text plus a typed question, and get a typed answer with a probability in a few hundred milliseconds. It ranks, and never decides alone.
That is a rule, not a preference. Early on, an innocent “po please!” comment scored as a prompt-injection risk. Had we wired that score to a blocking action, that reader would have gotten silence because a model misread their manners. Screening is advisory, blocking is opt-in, and today it is off. Everything fails open: and the reply still goes out.
Where comment-to-DM goes next
Attribution, finally. Every campaign carries its own tracking tag, so we can see which post drove which read: the difference between a post that performed and one that produced.
Campaign management as a product. A retailer launch now gets its own code, its own article, and a number we can report. That turns a reader-service tool into a measurable delivery channel, the piece our sponsored partners care about most.
The decision layer moves from watching to deciding. The model runs in shadow mode today: it records its reading and enforces nothing, so we can compare its judgment against what we actually did. Once a week of data agrees, we promote one caller at a time; static rules stay ahead of it for anything irreversible. At roughly four orders of magnitude cheaper than the work it gates, one draft not written pays for many decisions.
What running this actually looks like
The automation was the easy part. The hard part is that Meta’s rules, not our code, decide the shape of the feature. The clearest example cost us three readers. Our first design sent a follow card first and released the link on a tap. That works for accounts with a role on our app, which is what we tested with, and it stranded ordinary readers, because messaging someone without a role needs an approval we did not have. The lesson is blunt: test with an account that has no role on your app, or you will call your own privileges a feature. The rest is a set of refusals: no send when unsure, no second send, no run on an unsigned request.
68 comments arrived with no recognisable code out of 1,266 events in the first 28 hours. Our most useful number: the codes need to be easier to spot, and the copy asking for them clearer.
The stack, for those who want the details
The service is Python standard library, no framework, on the same box as our other newsroom tooling. A tunnel publishes the webhook endpoint without exposing anything else. State lives in JSON files and every decision goes to an append-only log, so every action is auditable.
Verification is first-class. It carries 101 offline tests that run with no network and send nothing, plus live probes for each required Meta capability. A kill switch halts every send while the listener keeps logging.
Running cost is negligible, fractions of a cent per decision. Sources: Meta’s private replies docs, Anker PH launch, our iPhone pre-order campaign.
I am not selling this. It is our own plumbing, built for our own problem. If your comment sections look like ours did, you already have the candidate list. You just need a system that answers while you sleep.