# Shipping an Instagram DM automation engine without breaking Meta's rules: 6 lessons

> Source: <https://dev.to/targenix/shipping-an-instagram-dm-automation-engine-without-breaking-metas-rules-6-lessons-1db8>
> Published: 2026-09-28 09:21:05+00:00

We run [Targenix](https://targenix.uz/en), a free automation platform for businesses in Uzbekistan that sell through Facebook and Instagram ads. This year we replaced our hard-coded "reply to a comment, then ask for a phone number" logic with a real flow engine: 9 triggers (ad comment, DM, story reply, story mention, ad click, ig.me link, Ice Breakers, persistent menu, button), 17 step types, and an AI reply step for everything the flow does not cover.

The engine itself was the easy part. The hard part was the platform rules and the edge cases they create. Here is what we would tell anyone building on the Instagram Messaging API and Messenger Platform.

When someone comments under an ad, you are allowed to send them **one private reply** about that comment. It is addressed to the comment, not to the person. You do not know the customer's messaging ID (PSID/IGSID) until the Send API returns it in its response.

That changes your data model. We had assumed "a conversation starts with a user id", so a run was keyed on the user before anything was sent. For comment flows that is impossible. The fix: send the private reply first, read the recipient id from the response, and only then open the run.

Our first version applied the 24-hour check to the private reply as well. Every comment flow older than a day died on its first step, silently. If you model "can I send now?" as a single function, give it the reason you are sending, not just the timestamp.

A flow often greets the customer and then waits for their answer. We implemented the wait as a state (`awaiting first inbound message`) and, out of habit, attached it to the scheduler that handles timed waits.

A timer can never know that the customer replied. The result was a flow that said hello once and then stayed silent forever. The inbound webhook has to resume the run. Timers are only for "no answer after N hours".

We did not switch engines at once. For every page in the pilot, the new engine processed each event and recorded what it *would* have sent, while the old code kept answering customers. We compared the two decisions for 48 hours, fixed the differences, then enabled real sending page by page.

One practical detail: write the shadow decisions to the database, not to logs. Our logs went to stdout, and a deploy recreates the container. The first comparison window disappeared with it.

A page cannot send a private reply to a person who has a role on that page. Meta returns error **10903** ("This user can't reply to this activity"). The owner commenting on their own ad is the most natural test, and it never works.

Keep a role-less test account for end-to-end checks, and do not read 10903 in production as "the engine is broken".

A pure AI chat drifts. A pure rule-based flow breaks the moment the customer asks something unexpected ("do you deliver to Samarkand?"). What worked for us:

Each public comment reply is generated separately, so a hundred "price?" comments do not get a hundred identical answers. Repeated template text is what both users and platforms read as spam.

No broadcasts, no "we miss you" messages days later, no messages to people who never wrote. Conversations start with the customer and stay inside the window. Keeping messaging permissions is worth more than any single campaign.

If you are solving the same problem for a Facebook or Instagram business, Targenix does all of the above without code and is free (one plan, 0 UZS, AI included): [targenix.uz/en](https://targenix.uz/en). Questions about the Messenger Platform edge cases are welcome in the comments.
