You can manage Instagram DMs with AI today by connecting the inbox to an assistant over MCP. The only decision that matters is how much authority you delegate: read and summarise, draft for a human to approve, or reply autonomously on a narrow class of messages. Most teams should stop at the second one.
That last sentence is the whole argument of this post, and almost nobody selling Instagram DM automation will say it out loud. What follows is the build for each of the three levels, the exact tool calls and prompts, the escalation path that makes level three survivable, and the point at which delegating more authority stops paying for itself.
What actually breaks in a busy Instagram inbox
Before automating anything, be precise about what is failing. Four things break, and they break independently, which is why teams that fix one still feel like they are drowning.
Intent mixing
The Instagram DM tab is a single undifferentiated queue holding at least six unrelated kinds of message: a buying question, a support complaint, an order status check, a story reply that is really just a fire emoji, a partnership pitch from an agency, and outright spam. Email solved this with folders in 1996. The DM tab has one list, sorted by recency, with no owner, no status, no due date and no way to say "this one is worth money".
Work through the arithmetic on a modest account. Forty inbound DMs a day, of which perhaps six are real buying questions. If it takes ninety seconds to open, read and mentally file each one, you have spent an hour a day before writing a single reply, and you have spent it mostly on story reactions. That hour is not support work. It is sorting, and sorting is the one thing a model does well at near zero marginal cost.
Response time decay across evenings and weekends
Inbound DMs do not respect your working hours. They peak in the evening, because that is when people are on Instagram. A message arriving at 21:40 on a Tuesday and answered when the team logs in at 09:00 has waited eleven hours and twenty minutes, and nobody did anything wrong.
The weekend version is worse. A message that lands Friday at 18:05 and gets its first human read at 09:00 on Monday has waited 62 hours and 55 minutes. Hold that number, because the next section explains why 62 hours is not merely slow. It is out of bounds.
The point is not that you need a human awake at 3am. It is that the gap between "when messages arrive" and "when your team exists" is a structural property of your business, not a performance problem you can coach away. We worked the staffing arithmetic in detail in the piece on lead response time: continuous coverage needs roughly five people to keep one chair occupied, before you have considered volume at all.
The messaging window that closes
On Instagram, replying late is not just less effective. Past a point it is not permitted. Meta's messaging platform gives businesses a standard window to reply to a message a user sent them, and once that window closes you can no longer send a free-form message in that thread. The exact rules and the paths that extend handling time are set out below, with a link to the current documentation, because this is the one part of the system that Meta can change without telling you.
The consequence for the Friday 18:05 message is blunt: the window had already closed roughly 39 hours before anyone opened the app on Monday. The reply you write is not late. It is a reply you are not allowed to send.
Nothing in the DM tab becomes a customer record
This is the expensive one, and it is invisible because nothing visibly fails. Someone messages you in March about sizing, does not buy, and comes back in August ready to order. The DM thread is still there, technically. Nobody reads it. You meet the same person as a stranger twice.
Worse, you cannot measure anything. There is no denominator. You cannot compute what share of Instagram conversations produced revenue, because a conversation has no outcome field. And when the person who ran the inbox for two years leaves, every piece of context about every repeat customer leaves with them. An inbox is a buffer, not a memory. The section on turning conversations into contact records is the part of this post with the clearest return, and it has nothing to do with AI writing text.
Three ways to manage Instagram DMs with AI, and what each one costs you
Every tool in this category sits at one of three levels of delegated authority. Vendors blur the boundaries because level three demos better. Treat them as separate products with separate risk profiles.
| Level | What the AI does | What you gain | What you risk | Scopes the key needs | Who it suits |
|---|---|---|---|---|---|
| 1. Read and summarise | Reads conversations, classifies intent, ranks by urgency, writes nothing anywhere | Triage time back. Nothing valuable sits unread behind forty story replies | A wrong ranking. That is the entire downside, and it is recoverable in one glance | social:read, with the proxy started in read-only mode |
Everyone, on day one, with no policy work required |
| 2. Draft for human approval | Produces a specific reply per conversation. A person reads it and clicks send, edits, or escalates | Most of the typing, plus a consistent voice across whoever is on shift | Rubber stamping. A reviewer who approves 200 drafts an hour is not reviewing | social:read plus social:write, with the write gated behind a human action |
Any team with a named inbox owner. This is where most teams should stop |
| 3. Guarded autonomy | Sends without a human, but only for messages on a written allowlist | Overnight and weekend coverage on the subset of messages that cannot be got wrong | A wrong answer published under your brand name, screenshotted, with no takeback | Same as level 2, plus tasks:write and contacts:write so escalation actually works |
Teams with a written hard stop list and someone accountable for it |
Notice what changes between levels: not the model, not the prompt quality, not the integration. What changes is who is liable for the sentence that reaches the customer. Pick the level by answering that question, not by comparing feature lists.
Level 1 build: read and summarise your Instagram DMs
Level one is a read-only assistant that tells you what is in the inbox and what to do first. It takes about ten minutes to set up and it is the only level with no downside worth discussing.
The connection runs over the Model Context Protocol, which is how an assistant like Claude Desktop, Claude Code, Cursor or ChatGPT calls tools that live outside itself. The CRM Solid MCP server is a stdio proxy: it runs on your machine, forwards JSON-RPC to the CRM Solid API with your bearer key, and the platform connections stay server-side. No Instagram password, session cookie or Meta token ever touches your laptop. If you want the architecture in full, the companion post on the social MCP server covers the transport, the tool surface and the scope model.
- Connect the Instagram account to CRM Solid, so the platform side of the connection is held by the backend rather than by your client.
- Create a bearer key at the developer settings screen with
social:readonly. Do not grant write scopes for a level one build. A key that cannot send cannot send by accident. - Add the server to your MCP client config, with
--read-onlyand a tools filter so the assistant sees a small surface. - Ask for the inbox summary. One call, no arguments.
- Pull the active conversations for Instagram.
- Run a classification pass with an explicit output contract.
The client config, for a level one key:
{
"mcpServers": {
"crmsolid": {
"command": "npx",
"args": [
"-y", "@crmsolid/mcp-server",
"--read-only",
"--tools", "social,contacts"
],
"env": { "CRMSOLID_API_KEY": "csk_live_..." }
}
}
}
Two flags are doing real work there. --read-only drops every write tool in the local proxy before the client ever sees the list, so the model cannot call what it cannot see. --tools social,contacts narrows 62 tools down to the two families this job needs, which measurably improves tool selection: a model choosing between nine tools makes fewer mistakes than one choosing between sixty. Both filters run locally, so a filtered tool is not listed and not callable even if the key would have allowed it.
Now the session. MCP tool output is camelCase throughout; the public v1 REST API returns the same data in PascalCase. Do not mix the two in one script.
> crm_social_inbox_summary()
{
"accounts": 4,
"conversations": 132,
"activeConversations": 34,
"archivedConversations": 98,
"unreadConversations": 11,
"unreadMessages": 19,
"lastMessageAt": "2026-08-24T08:41:12Z",
"platforms": [
{ "platform": "instagram", "conversations": 71,
"unreadConversations": 7, "unreadMessages": 12 },
{ "platform": "linkedin", "conversations": 38,
"unreadConversations": 3, "unreadMessages": 5 }
],
"awaitingReply": [
{ "conversationId": 4821, "platform": "instagram",
"participantName": "Dilara K.", "contactId": 91043,
"unreadCount": 2, "lastMessageAt": "2026-08-24T08:41:12Z",
"lastMessagePreview": "is the 12 month plan still available?" }
]
}
> crm_list_social_conversations({
"platform": "instagram",
"status": "active",
"limit": 50
})
{
"count": 18,
"conversations": [
{
"id": 4821,
"platform": "instagram",
"participantName": "Dilara K.",
"participantUsername": "dilarak",
"contactId": 91043,
"unreadCount": 2,
"status": "active",
"lastMessageAt": "2026-08-24T08:41:12Z",
"lastMessageOutgoing": false,
"lastMessagePreview": "is the 12 month plan still available?"
}
]
}
Read the shape of that second result carefully, because it is the one people assume wrongly. The MCP list tools are not cursor paginated. There is no items wrapper, no nextCursor, no hasMore and no after argument. A list takes limit, an integer from 1 to 100 that defaults to 25, and gives you back a named array (conversations here) plus a count. Cursor paging with ?after= is the v1 REST API, which is a different surface for a different caller.
That has a direct consequence for a triage prompt: ask for the limit you want up front. If you leave limit off and the model summarises 25 conversations as though they were the inbox, that is not the model being lazy, it is the default doing exactly what it says. Set it to 50 or 100 for a morning pass, and use the count in the response against the activeConversations figure from the summary to see whether you actually got everything. Message history inside one thread is the exception that does page, and it pages backwards: crm_list_social_messages takes beforeMessageId, so you pass the id of the oldest message you hold and get the batch before it.
Every id on this surface is an integer. conversationId: 4821, contactId: 91043, messageId: 88214. Note also that the participant fields are participantName and participantUsername, not a contact name and a handle, and that participantUsername arrives without the leading at sign, so put it back yourself if you are rendering it.
The classification pass, with an output contract
A summary is not triage. Triage is a decision about order. The instruction below is the one that turns a list into a decision, and every rule in it exists because of a specific failure mode observed without it.
For each conversation below, output one JSON object per line with exactly
these fields:
id the conversation id, an integer, copied exactly and never
reformatted, quoted or abbreviated
intent one of: sales, support, order_status, story_reply,
partnership, spam, other
urgency one of: now, today, this_week, none
window_left hours remaining before the reply window closes, computed
from lastMessageAt, or "expired"
needs_human true when the reply requires a fact that is not visible in
the thread: an order number, an amount, a delivery date,
anything about money
evidence the exact substring of lastMessagePreview that decided the
intent, or null
Rules:
- If you cannot quote evidence, intent is "other". Do not infer intent
from the contact's name, follower count or profile.
- story_reply outranks every other intent. A reply to a story is a
reaction first and a question second, and treating it as a sales lead
is the most common classification error here.
- needs_human defaults to true. Set it false only when the answer is a
fact you can point to in the thread.
- Output one JSON object per line. No wrapper array, no prose, no
preamble, no summary at the end.
Three of those rules deserve a note. Requiring a quoted evidence span is the cheapest hallucination brake available: a model that must point at a substring cannot invent an intent. Forbidding inference from follower count stops the model from ranking an influencer's emoji above a real order problem. And defaulting needs_human to true inverts the usual bias, because a model asked to decide whether it can handle something will nearly always say yes.
The output, rendered as the triage board a human actually reads:
| Conversation | Handle | Intent | Urgency | Window left | Needs human |
|---|---|---|---|---|---|
| 4821 | @dilarak | sales | today | 19h | no |
| 4819 | @mert.buildsit | order_status | now | 4h | yes |
| 4812 | @studio.kavi | partnership | this_week | 21h | yes |
| 4803 | @ay.senn | support | now | expired | yes |
| 4826 | @zeynep_pfd | story_reply | none | 17h | no |
Sort that by window_left ascending within urgency, and you have the order to work in. The expired row is not a failure to hide. It is a measurement: something in your coverage let a support conversation run past the window, and you now know it without anyone filing a complaint.
Run level one for two weeks before touching level two. You will learn your own intent distribution, which is the input to every decision that follows, and you will find out how often the classifier is wrong on your traffic rather than on a vendor's.
Level 2 build: drafts a human approves before they send
Level two writes the reply and hands it to a person. The person is the send button. This is the level that pays for itself fastest and the level almost every team should run in production.
The dm-reply-draft prompt
The server publishes MCP prompts as well as tools, and dm-reply-draft is the one for this job. It takes a required numeric conversationId and an optional tone. In a client that surfaces MCP prompts, it appears as a slash command; in one that does not, you invoke it through the prompts API. The prompt pulls the thread with crm_list_social_messages, reads the participant's language and the platform's length conventions, and returns a draft plus its reasoning. It drafts only. There is no argument that makes it send, which is the property that lets you hand it to someone on their first day.
The tone argument matters more than it looks, and it takes three values rather than free text: friendly, which is the default, professional, and urgent. A support reply and a sales reply are different registers, and a single "friendly, professional" instruction produces text that is neither. Pass tone: "professional" for an order status question where the customer wants a fact and not warmth, leave it at friendly for a first contact, and reserve urgent for the threads your triage pass marked as running out of window. Let the classification from level one pick the value, since it already decided the intent.
Because the set is closed, do not write a tone into the argument that is not on that list. A model handed tone: "cheeky" does not get a cheeky draft plus a warning; the value is simply not one the prompt knows, and you lose the steer you thought you applied. Register nuance beyond those three belongs in the voice file, which is the next section and a better home for it anyway.
The brand voice file, and what belongs in it
Tone arguments are not enough on their own. You need a file. Keep it short, keep it in version control, and write it as constraints rather than adjectives, because "be friendly" is unfalsifiable and "never use the word just" is checkable.
# Voice
Second person. Short sentences. No exclamation marks.
Greeting: none for a returning contact. "Hi <first name>" on a first message.
Sign off: none. The message ends on the answer or on a question.
# Never
- Never state a figure that is not published on the product page.
- Never promise a delivery date. Link to the tracking page instead.
- Never write "unfortunately", "kindly" or "just".
- Never apologise twice in one message.
- Never answer a question about someone else's order.
# Language
Reply in the language of the customer's last message.
Turkish replies use the informal register only if the customer used it first.
# Length
Two sentences by default. Four is the hard maximum. If the answer does not
fit in four sentences, escalate instead of writing five.
# Escalate, do not answer
refund, chargeback, "still waiting", any named deadline, anything legal,
anything mentioning a third party's account.
The length cap is the rule that surprises people. Models write long, and long DM replies read as evasive on a channel where the customer typed nine words. Capping at four sentences also forces escalation on exactly the cases that should escalate, because a genuinely complicated answer cannot be compressed, and the cap turns that into a signal instead of a wall of text.
The banned-words list is not style pedantry. Every one of those words is a tell that a model is hedging around a fact it does not have. Ban the hedge and the missing fact becomes visible.
The human review gate
The gate has exactly three outcomes, and building a fourth is how teams end up rubber stamping.
- Send as written. The draft is correct. One click.
- Edit and send. The draft is close. The edit is the training signal, so capture the diff. Consistent edits in the same place mean the voice file is missing a rule, not that the model is bad.
- Escalate. The draft is wrong or the question is out of scope. This creates a task and pauses the assistant for that conversation, covered below.
There is no "approve all". There is no bulk action. If your reviewer needs a bulk action, your classifier is sending them things that should never have been drafted, and the fix is upstream. Track the share of drafts sent unedited as your primary quality metric, with the caveat in the measurement section: a very high number is a warning, not a win.
Sending, and what actually stops a duplicate
When the human clicks send, one tool call goes out. Note how few arguments it takes:
> crm_send_social_message({
"conversationId": 4821,
"text": "Yes, the 12 month option is still open. I can walk you through it here, or send the details to the email on your account, whichever is easier."
})
{
"status": "sent",
"messageId": 88214,
"conversationId": 4821,
"platform": "instagram",
"contactId": 91043,
"externalMessageId": "aWdfZG1fMTo...",
"sentAt": "2026-08-24T09:03:12Z",
"message": "Message sent on instagram to Dilara K."
}
That is the complete argument list: conversationId, text, and an optional mediaUrl, with one of text or media required and text capped at 8000 characters. The response is a confirmation of what changed, not a data feed. That is a deliberate property of the whole tool surface: no tool both reads and writes, and a write never returns a list you could have got from a read. It means a compromised or confused model cannot use a send call to exfiltrate the inbox.
Now the failure this section is really about, because it is the most common visible failure of DM automation and the honest account of it is more useful than the reassuring one.
Consider the sequence. The model calls crm_send_social_message. The proxy forwards it. The message reaches Instagram successfully. Then the HTTP response is lost: a dropped connection, a proxy timeout, a laptop that slept. From the model's point of view the tool call failed. Models retry failed tool calls, because that is almost always the right behaviour. The retry sends the message a second time, and your customer receives the same sentence twice, thirty seconds apart, from a brand that just told them it was automated.
There is no idempotencyKey argument on this tool. If you have seen one in an example, it came from the REST endpoint or from documentation written before the server shipped. Nothing you can pass to crm_send_social_message collapses the retry into the original call, and a post that told you otherwise would be selling you a guarantee the product does not make.
What does stand between you and the duplicate is three real properties, and it is worth knowing which is doing the work in your setup:
- The send is annotated as a write, so the client asks first. The tool declares itself non-idempotent and open-world, and a well-built MCP client turns that into a confirmation dialog showing the recipient and the exact text. In a level two build this is not an extra prompt, it is the review gate from the previous section, which is why the two are the same design and not two overlapping safeguards. A retry surfaces as a second confirmation. Approving the same message twice is something a person can notice, and the entire value of that depends on the reviewer reading rather than clicking.
- A platform rejection returns an error, not a silent retry. If Instagram refuses the send because the window closed, you get back a message like
The platform rejected this message: outside the 24 hour window (code 10). The server does not swallow it, queue it, or try again later on its own. The failure is visible at the moment it happens, and what to do next is your decision. This matters more than it sounds on a channel with a hard window: a system that quietly retried would keep failing against a wall while telling you nothing. - Check the thread before you re-run anything. This is the operational rule that costs three seconds and resolves the ambiguity a timeout cannot. Call
crm_list_social_messageson the conversation and look for anoutboundmessage carrying your text. Present means the send worked and only the response was lost. Absent means it genuinely failed and a retry is correct. Put this in the runbook as a step, because under time pressure the instinct is to click send again.
If your integration is code rather than a person in an MCP client, the picture improves: the v1 REST send endpoint does accept an idempotency key, and that is the right surface for anything running unattended. The reason the key lives there and not on the tool is worth understanding, because it is not an oversight. An idempotency key only works if it is derived deterministically from the intent, minted once before the first attempt and carried unchanged through every retry. A key generated fresh inside the retry loop is a different key every time and protects nothing at all, which is worse than having none because it looks like protection. Deriving a stable key from the conversation id, a timestamp and a short hash of the text is trivial for a program and unreliable for a language model, which will treat an identifier field as an invitation to invent a new value each call. A key that a model mints per attempt is decoration.
There is a second protection here that most teams do not know they are getting, and it guards against a worse duplicate than the one above. Sending through this tool marks an operator takeover, which pauses the AI agent for that contact. The dangerous case in a level two or level three build is not the same message arriving twice, it is a human reply and an automated reply landing in the same thread within a minute of each other, saying different things. Because the takeover fires on the send itself, the automation steps back the moment a person speaks. You do not pass a flag, you do not tag anything, and you cannot forget to do it. The send is also written to the contact timeline as a message activity, so the reply becomes part of the record rather than something that happened only inside Instagram.
Duplicate sends are almost never a model problem. They are a retry semantics problem wearing a model costume, and the fix is a reviewer who reads, a runbook step, and batches small enough that a partial failure is legible.
Level 3 build: guarded autonomy on a narrow class of messages
Level three sends without a human. It is defensible only when the set of messages it may answer is written down, narrow, and boring.
The class of messages that can be answered without a human
A message qualifies for autonomy when the correct reply is a fact that does not change per customer and does not involve money. In practice that is a short list:
- Opening hours and location. Same answer for everyone, published, verifiable.
- Where to check shipping status. Not the status itself, which requires a lookup and often an apology. The link to the tracking page, plus the sentence explaining what to have ready.
- A link to the size guide, the care instructions, the spec sheet or the returns policy. A pointer to a canonical document is safe in a way that a summary of that document is not.
- Do you ship to X. Answerable from a static list, if you keep that list in the voice file rather than in the model's memory.
- Acknowledging a story reply. Low stakes by construction.
- Out of hours acknowledgement with an honest statement of when a human will answer, and only if that statement is true.
Look at what those have in common. Every one of them is a pointer, not a judgement. The moment a reply requires the model to combine two facts, or to decide what this particular customer's situation is, it belongs at level two.
The hard stop list
Write this down before you enable anything. It is the document you will be judged on when something goes wrong.
- Any message containing a number the model would have to produce: an amount, a discount, a delivery date, a stock count.
- Anything about refunds, cancellations, chargebacks or a payment that did not work.
- Any complaint, and any message with a second question mark after a negative sentence.
- Any message in a language your voice file does not cover. A model will happily reply in a language nobody on your team can review.
- Any conversation attached to a contact with an open deal.
- Any conversation with a verified account, a journalist, or anyone whose profile suggests the reply will be read by more than one person.
- Anything with a legal word in it. The list is short and you know what is on it.
- Any conversation where the assistant has already sent two consecutive messages without an inbound reply. This is the loop guard, and it is not optional.
Implement the stop list as a filter in front of the model, not as an instruction inside the prompt. Instructions are advisory. A filter that removes the conversation from the queue before the model sees it is enforcement. This is the same argument as running --read-only in the proxy rather than asking the model not to write.
Do not run level 3 on sales conversations
Here is the opinionated part. Most teams should not let an AI answer sales DMs autonomously, and the reasoning is asymmetry rather than squeamishness.
The upside of autonomous sales replies is speed. On an asynchronous channel, the marginal value of answering in 40 seconds instead of 40 minutes is small: the customer is not sitting by a ringing phone, the notification persists, and the thing that decays is their intent, which does not measurably decay in 39 minutes. The downside is a wrong figure, a promise you cannot keep, or a confidently invented policy, published under your handle, screenshotted, and permanent. One is a small gain repeated often. The other is a large loss that arrives rarely and cannot be undone.
Support has the opposite shape. "We open at 09:00" is correct every time, and answering it at 23:00 is genuinely better than answering it at 09:15. That is where autonomy earns its keep.
So the practical rule: level three for the pointer class and the out of hours acknowledgement, level two for everything with revenue attached. If you want autonomy in the sales path, put it in the qualification questions rather than the answers, which is the pattern described in the post on AI lead qualification. Asking a good question autonomously is far safer than answering one.
If you are still weighing whether a scripted flow would do the job, the comparison of AI agents and chatbots is the piece that draws that line properly. A decision tree that can only say six things cannot invent a refund policy, which is a real advantage, and the cost is that it cannot handle the seventh thing at all.
Escalation and handoff, the part that makes autonomy survivable
An autonomous assistant without a working escalation path is not automation, it is an unattended machine. Escalation has to be cheap, specific and observable.
| Trigger in the customer's message | Why it escalates | What happens |
|---|---|---|
| refund, chargeback, dispute, "money back" | Money is leaving. The answer has a policy and a system of record behind it | Task created, contact assigned, assistant paused for this conversation |
| lawyer, legal, consumer rights, "report you" | Legal exposure. Anything the assistant writes becomes evidence | Task at high priority, routed to a named person, assistant paused |
| "still waiting", "third time", "nobody replied" | Repeat contact. The customer has already been failed once | Task at high priority, plus a note recording how many prior contacts |
| A named deadline: "before Friday", "by the 30th", "for Saturday" | Time bound. A generic reply is worse than no reply | Task with a due date set from the stated deadline |
| An amount, an order number, or a payment method | Requires a lookup the model cannot perform reliably | Routed to level 2 drafting with the lookup result attached |
| Verified account, press or partnership pitch | Reputational reach beyond this one customer | Routed to a named human, never drafted autonomously |
Match on phrases, not on sentiment scores. Sentiment classifiers are unreliable on short text with slang and emoji, and a phrase list is auditable by a person who does not know what a classifier is. Keep the list in the same file as the voice rules so it changes with review.
What escalation actually does
Four things, in this order, and skipping any one of them leaves the escalation cosmetic:
- Create the task with
crm_create_task, with a due date and a real title. "Instagram DM" is not a title. "Refund request, order not identified, @dilarak" is. - Assign the contact so the task has an owner. An unassigned task is a wish.
- Pause the assistant for that one contact. You do not have to build this, and you should not try.
crm_send_social_messagemarks an operator takeover, which pauses the AI agent for that contact automatically, so the moment the human handling the escalation sends their first reply the automation stops answering that person. The scope is right by construction: one contact, not the whole account, which is what stops a single bad conversation turning into a system somebody switched off in frustration and never switched back on. Where you do still act deliberately is the gap before that first human reply, and the answer there is the task and the assignment in steps one and two: an escalated conversation with an owner is one a person reaches before an autonomous queue does. - Write the handoff note with
crm_add_contact_note, in a fixed format, so the human picking it up does not have to reconstruct the thread.
The note format is the artifact that decides whether escalation feels like help or like being handed a mess. Fix the shape and never deviate:
> crm_create_task({
"title": "Refund request, order not identified, @dilarak",
"dueAt": "2026-08-24T14:00:00Z",
"priority": "high"
})
Handoff note written to contact 91043:
Handoff: conversation 4821 (instagram, @dilarak)
Why: refund requested, order mentioned but not identified
Thread so far: 3 inbound, 1 outbound sent 08:52 (opening hours, autonomous)
Customer says: bought "about two weeks ago", wants money back
Unknown: order number, payment method, whether it shipped
Do not: quote a refund window. We have not confirmed the order exists.
Deadline: reply window closes 2026-08-25T08:41Z
Assistant pauses for this contact on your first reply (operator takeover)
Eight lines, all of them facts, one of them an explicit prohibition. The "do not" line is the one that separates a useful handoff from a summary. It tells the human what the assistant would have got wrong, which is the only thing the assistant knows that the human does not. The last line is there so the person picking this up knows they are not racing the automation: their reply is what stops it, and it stops for this contact rather than for the account.
Turning Instagram DMs into CRM records
This is the section that explains why an inbox alone loses money, and it is the one worth building even if you never send a single AI-written reply.
An inbox stores messages. A CRM stores what those messages meant. The gap between the two is where the revenue goes. Four write tools close it, and they should run at the end of every conversation that reached a conclusion, not per message.
crm_add_contact_notewrites what happened. One note per conversation state change, never one per message. A timeline with 400 notes is a timeline nobody reads. First line is a one-sentence summary, second line carries the conversation id so the thread is findable.crm_tag_contactwrites what kind of person this is. Tags are for facts that stay true:instagram-inbound,wholesale,size-question. Tags are not for status, which is what stages are for, and they are not the place to reimplement an assistant pause, which the operator takeover already handles.crm_set_lead_scorewrites how interested they are, and only on evidence. A DM asking about availability is not a qualified lead, and a model asked to score will produce a number regardless. Score on stated facts, not on tone. The mechanics are in the lead scoring guide, and the definition is in the glossary entry.crm_update_contact_stagewrites where they are in the process. This is the field that makes the inbox measurable, because a stage change has a date and a direction.
Once those four run, three things become possible that were not possible before. You can answer "what share of Instagram conversations became a deal", because conversations now have outcomes. You can recognise the March sizing question when it comes back in August, because the note is on the contact and not in a thread nobody opens. And you can hand the account to a different person without losing the context, which is the actual cost of an inbox-only operation and the one nobody budgets for.
Move the ones with revenue attached into a pipeline and track them as deals. A DM conversation that reached "wants to buy, waiting on stock" is a deal with a next action, and leaving it as an unread badge is a decision to forget it. The same contact record is shared by every channel in the inbox, which is why the WhatsApp thread and the Instagram thread from the same person land on one timeline instead of two.
One warning about note volume. The temptation is to have the assistant log everything, because storage is cheap and completeness feels responsible. Resist it. A contact timeline is read by a human under time pressure, and its value falls off a cliff once it stops being skimmable. Log conversation outcomes, escalations, and anything the customer stated as a fact about themselves. Log nothing else.
What Instagram's own rules allow, and how to check
Write this section into your own runbook, because getting it wrong is the difference between an automation that works and an account that gets restricted.
The Instagram messaging API is part of Meta's messaging platform. Access requires a professional account (business or creator) connected to an app, and the fundamental constraint is that the conversation is user-initiated: a business cannot open a DM thread with someone who has not messaged it first. That single rule eliminates most of what people imagine when they hear "Instagram DM automation".
Inside an open conversation, Meta operates a standard messaging window, documented as 24 hours, measured from the user's message. While it is open you can reply freely. When it closes, free-form messaging in that thread is no longer permitted, and you are restricted to the narrow set of message tags Meta has approved for specific purposes. The critical detail, and the one teams misread: the clock is set by the customer's message, not by yours. Replying does not extend it. A holding message sent at hour 23 to look responsive buys you nothing at all.
Meta also documents a human agent path for cases where a person, rather than an automation, needs longer to resolve something. At the time of writing it has been documented as extending the handling period to seven days, and it exists precisely because real support sometimes takes longer than a day. Verify the current duration and the eligibility conditions yourself rather than trusting a figure in a blog post, including this one.
Unsolicited bulk DMs are prohibited. This is not a rate limit you can work around with better pacing; it is a platform policy about the nature of the message. If your plan involves messaging people who did not message you, the Instagram messaging API is not the tool, and no amount of AI changes that. The compliance piece on cold outreach covers what the equivalent rules look like across channels and jurisdictions.
Finally, and most importantly: automation policy changes. Meta revises these rules, deprecates paths and adds new ones on its own schedule. Read the current version in Meta's Instagram messaging documentation and the platform policy overview before you enable anything autonomous, and put a calendar reminder to re-read them. Any post that states these rules as permanent facts, including this one, is a snapshot with a decay rate.
One structural note in your favour. Because the MCP server is a proxy to a backend that holds the platform connection, the policy surface is one you manage in one place rather than in every script. Your machine never holds a Meta token, which also means a compromised laptop is not a compromised Instagram account. The details are on the security page.
Measuring whether the AI is actually helping
Four numbers tell you whether this is working, and each one has a trap that makes it look better than reality.
| Metric | What it tells you | The trap |
|---|---|---|
| First response time, median | Whether coverage exists at the hours your customers message | Use the median, never the mean: one conversation answered after three days destroys a mean and hides a good week. And marking a conversation read is not responding to it |
| Reply rate inside the window | The share of user-initiated conversations answered before the reply window closed. This is the platform-enforced version of the metric above | An autonomous acknowledgement counts as a reply but does not count as handled. Track "answered" and "resolved" as two different numbers or the automation will flatter itself |
| Share of drafts sent unedited | Whether the voice file and the classifier are calibrated | Target a band, not a maximum. Below roughly half and the drafts are wasting reviewer time. Near 100 percent and nobody is reading them, which is a different and worse failure |
| Conversations that became a deal | Whether the inbox is worth staffing at all, which is the only question a finance conversation cares about | Attribution lag. A DM in March closes in August. Measure by cohort of conversation start date, not by close date, or you will conclude that last month was terrible every month |
| Escalation rate | Whether the hard stop list is calibrated | A falling escalation rate looks like improvement and is usually the model getting bolder. Sample escalations that did not happen, not just ones that did |
| Reopen rate within 48 hours | Whether the answer actually answered the question | The cleanest quality signal in the set, and the one no dashboard shows by default. A fast reply that produces a second question is not a fast reply |
Be precise about which of these the assistant can fetch for you, because the MCP surface covers less of this table than you would hope. crm_messaging_stats takes a windowDays argument restricted to 1, 7 or 30, and returns outbound send volume and a success rate: queued, sent, failed, total, and the share that landed. That is your delivery health, and it is genuinely useful for spotting a token that expired overnight. It is not a response time report, it does not break down by platform, and it says nothing about conversations. crm_social_inbox_summary is the one that answers the per-platform question: current load, unread counts per network, and the oldest threads still waiting on a reply.
Response time medians, cohort views and the conversation-to-deal number are not on the MCP surface today. They come from the analytics side of the product, where the time series lives. Plan for that split rather than discovering it mid-review: ask the assistant for the current state of the inbox and for send health, and open the analytics screen for anything with a trend line in it. Asking a model to compute a median from a list it paged through itself is how you get a confident number nobody can reproduce.
Set the baseline before you enable anything. Two weeks of level one gives you exactly that, for free, and without a baseline every improvement claim afterwards is a story rather than a measurement. Channel-level context for what normal looks like is in the omnichannel messaging benchmarks piece.
Seven mistakes teams make automating Instagram DMs
Autoreply loops between two bots
Your assistant replies to an agency's assistant, which replies to yours, and by morning there are forty messages in a thread where no human was ever present. This happens more than anyone admits, because partnership pitches are frequently automated and your out-of-hours acknowledgement is exactly the kind of message that triggers another autoresponder. The guard is mechanical: never send two consecutive outbound messages without an inbound message between them, enforced as a filter and not as an instruction. Add a hard cap of one autonomous message per conversation per 24 hours and the failure becomes impossible rather than unlikely.
Over-templating until every reply reads the same
The first month of level two feels excellent because the voice is finally consistent. The sixth month, a customer messages twice about different things and gets structurally identical replies, and the account reads as a machine. The fix is not more randomness, which produces noise. It is fewer templates and more context: give the drafting prompt the contact's note history so the second reply can say "you asked about sizing in March" instead of opening the same way for the fortieth time. Templates belong on the parts that genuinely repeat, like a returns link, not on the opening sentence.
Ignoring story replies
Story replies arrive in the same inbox and are mostly noise, so teams filter them out entirely and then discover they filtered out a buying question. Story replies are context-poor by design: the customer can see the story, and the message "is this still available?" is unresolvable without it. Do not autonomously answer story replies as if they were normal DMs. Classify them as their own intent, answer the safe ones with an acknowledgement, and route anything with a question mark to a human who can look at what the story actually showed.
Sending at 3am in the customer's time zone
Autonomous coverage means a message can go out at any hour, and a notification at 03:12 is an unsubscribe event on a channel that has no unsubscribe button, only a block. Instagram gives you the customer's activity, not their time zone, so infer conservatively from their message pattern and default to holding non-urgent autonomous replies until a civilised local hour. The exception is the honest out-of-hours acknowledgement, which is worth sending immediately precisely because it sets the expectation that nothing else will arrive until morning.
Letting the model invent refund policy
Ask a model a refund question with no policy in context and it will produce a refund policy, because that is what you asked for. It will be plausible, it will be specific, and it will be a commitment made in writing by your brand. The correct architecture is that policy questions never reach the model as questions to answer: they are on the hard stop list, filtered before drafting. If you want policy answers automated, put the policy text in the context as a quoted document and require the reply to cite the line it came from, exactly as the level one classifier requires an evidence span.
Marking everything read to clear a badge
crm_mark_social_conversation_read exists because sometimes you genuinely have handled something elsewhere. It is also the single easiest tool to misuse, because bulk-marking read makes the dashboard look wonderful and destroys the only signal you had about what is unanswered. Never give an autonomous agent unconditional access to it. Mark read as a consequence of replying or escalating, never as an action in its own right, and if your unread count is the metric someone is judged on, expect it to be gamed within a week.
Treating the AI as a headcount replacement instead of a first pass
The framing that fails is "the AI handles the inbox now". The framing that works is "the AI does the first pass and a human does the last ten percent". The difference shows up in staffing decisions: teams that cut the inbox role after deploying level two find that the escalations have nowhere to go, the voice file stops being maintained, and quality decays over about a quarter with nobody noticing until the reopen rate doubles. Level two makes one person able to run an inbox that used to need three. It does not make zero people able to run it, and the person you keep should be the one who was best at it.
The same workflow on the other eleven platforms
The inbox covers Instagram, Facebook, X, LinkedIn, TikTok, YouTube, Threads, Pinterest, Reddit, Bluesky, Telegram and WhatsApp, and the tools are platform-agnostic: crm_list_social_conversations takes a platform argument and everything downstream is identical. The recipes port. The policies do not.
| Platform | Does the recipe port | What changes before you allow autonomy |
|---|---|---|
| Fully | The reply window, professional account requirement, user must initiate | |
| Fully | Similar window mechanics, plus page roles and a different tag set | |
| Fully | A customer service window that resets on each new customer message, and pre-approved templates once it closes | |
| X | Fully | Who is permitted to DM you at all, plus rate limits that bite at volume |
| Drafting yes, autonomy no | The most restrictive automated messaging stance in this set. Keep it at level 2 and keep a human on the send button | |
| TikTok | Fully | DM availability varies by account type and region, so verify before designing around it |
| YouTube | Partly | Most inbound is comments rather than DMs, which is a different moderation problem with a public audience |
| Threads | Fully | A newer messaging surface. Check what the current API exposes before assuming parity with Instagram |
| Fully | Low volume and rarely transactional. Level 1 is usually the whole answer | |
| Drafting yes | Subreddit and community rules bite harder than platform rules, and they are not machine readable | |
| Bluesky | Fully | Small volume, no messaging window, so nothing forces your hand on speed |
| Telegram | Fully | No reply window at all, which means speed is a business choice rather than a platform deadline |
The pattern in that table is worth naming. Where a platform enforces a reply window, autonomy stops being a preference and becomes a coverage requirement, because the alternative is losing the right to reply. Where there is no window, as on Telegram, you can be deliberate and slow and still win. Knowing which of your channels have walls is a prerequisite to setting any response policy, a point developed in the Telegram and WhatsApp comparison.
The same connection also handles the outbound half of social media. Publishing, scheduling and cancelling posts run through the same server and the same key, with the post on scheduling social posts with AI covering the safety model there: a post needs an explicit scheduledAt unless you pass publishNow: true, so an assistant that forgets to say when is rejected rather than publishing something on the spot. If you would rather work through the connection end to end, the API and MCP integration guide is the step-by-step version, and the unified inbox setup guide covers connecting the accounts themselves.
For teams that want autonomy without writing prompts, the same behaviours are available as configured AI agents and automatic replies inside the product, with the same escalation model. The MCP path is for when you want the assistant you already work in to be the interface. What is available on which plan is on the pricing page, and the full tool and scope reference is on the public API page.
Frequently asked questions
Can AI reply to Instagram DMs automatically?
Yes, within limits set by Meta rather than by the AI. The conversation has to be user-initiated, your account has to be a professional account connected to an app, and the reply has to land inside the messaging window. Within those limits an assistant can send autonomously.
Whether it should is a separate question. Restrict autonomy to messages whose correct answer is a fixed, published fact, and route everything involving money, complaints or judgement to a human.
Do I need an Instagram business account?
Yes. The messaging API requires a professional account, which means a business or creator account connected to an app. A personal account cannot be automated through the official API, and tools that claim otherwise are driving the app or the website, which is a different risk category entirely.
Will Meta restrict my account for using AI on DMs?
Using the official messaging API within its policy is a supported use. Restrictions come from behaviour the policy prohibits: unsolicited messages, bulk outreach to people who did not contact you, or automation that mimics a human in ways the policy forbids. Read the current platform policy before enabling autonomy, and re-read it on a schedule, because it changes.
What happens if nobody replies within 24 hours?
The free-form reply window closes and you can no longer send an ordinary message in that thread. You are limited to the specific message tags Meta permits, or to a documented human agent path where one applies. The clock runs from the customer's message, so replying earlier does not extend it and a filler reply buys nothing.
This is why overnight and weekend coverage on Instagram is a structural decision rather than a nice-to-have. A Friday evening message read on Monday morning is out of bounds before anyone opens the app.
Which MCP clients can I use for this?
Any client that speaks the Model Context Protocol over stdio, which currently includes Claude Desktop, Claude Code, Cursor and ChatGPT among others. The server is an npm package started with npx, so the configuration is a few lines of JSON and a bearer key.
Client support for MCP prompts varies. Where a client does not surface prompts as slash commands, dm-reply-draft is still callable through the prompts API, but the ergonomics are worse and you may prefer to inline the instruction.
Can the AI read my DMs without being able to send anything?
Yes, and this is the correct way to start. Create a key with social:read only and start the proxy with --read-only. That gives you two independent guarantees: the key would be rejected server-side for a write, and the write tools are never exposed to the model in the first place.
Every read tool is annotated read-only, and no tool in the surface both reads and writes, so the boundary is a property of the design rather than a promise about behaviour.
How do I stop the AI on one conversation without turning it off everywhere?
Reply to it yourself. Sending through crm_send_social_message marks an operator takeover, which pauses the AI agent for that contact automatically. There is no tag to apply, no flag to pass and nothing to remember, and because the pause is attached to the contact rather than to the thread it still holds when the same customer messages you on another channel.
Scope matters here, and this is the right scope. Teams that can only pause globally end up disabling the whole system after the first bad conversation and never turning it back on.
Does this work for Turkish and other non-English DMs?
Yes, and the language rule belongs in the voice file rather than in the model's judgement: reply in the language of the customer's last message, and name the register explicitly for languages that have one, because a model choosing between formal and informal address will pick inconsistently across a thread.
Add one hard rule: never send an autonomous reply in a language nobody on your team can review. A fluent reply in a language you cannot read is not a reply, it is an unreviewed publication.
What does it cost to run?
The MCP server itself is an open source npm package under the MIT licence, and the code is on GitHub. What you pay for is the CRM Solid workspace holding the platform connections and the contact records, and which capabilities are included on which plan is listed on the pricing page.
The cost that surprises teams is not the software. It is the maintenance of the voice file and the hard stop list, which needs a named owner and about an hour a month. Systems that decay do so because that hour stopped happening, not because the model got worse.