TL;DR
We turned a busy brand inbox into an on call social DM concierge that resolves common questions, routes edge cases to humans, and never messages anyone without consent. The core is a small set of playbooks and guards that orchestrate rate limits, redaction, and handoffs using workflow automation. We built it on OpenClaw connectors, policies, and feature flags, then shipped in slices with strong observability. The result was faster replies, fewer night pages, and a cleaner audit trail of what the agent did and why.
The brief: move DMs from a shared inbox to an agent
In late Q2 a lifestyle retailer asked us to reduce response times across Instagram and X DMs without hiring. Their social team was drowning in repetitive questions about orders, store hours, and returns. The ask was simple on paper. Keep the tone on brand, respect opt outs, and escalate anything risky to a human within minutes.
We chose an agentic approach because the volume was spiky and time sensitive. The goal was not to automate every conversation, it was to handle the top intents quickly and predictably. Anything ambiguous, personally sensitive, or policy relevant needed a fast human handoff with all the context preserved.
This story walks through the architecture we shipped, the tradeoffs we made, and what we would change next time. It also includes code snippets we actually used to keep the build reproducible for another team that asks how to build a consent-aware DM bot.
Architecture at a glance
We used three layers.
- Ingest and connectors. Instagram Messaging and X DM webhooks land on the gateway, are normalized, and are pushed into an event bus.
- Policy and state. Consent, rate limit credits, conversation state, and feature flags live in a small store. The agent reads from it before acting.
- Playbooks and tools. A narrow set of skills answer common intents, call the order API, and create tickets. A handoff tool routes to humans in Slack.
Here is the minimal schema for consent and conversation state that proved stable in production.
{
"consent": {
"user_id": "ig_12345",
"channel": "instagram_dm",
"status": "explicit",
"source": "cta_dm_opt_in",
"updated_at": "2026-08-10T19:22:31Z",
"expires_at": null
},
"conversation": {
"thread_id": "ig_t_789",
"risk": "low",
"owner": "agent",
"intent": "order_status",
"handoff": {
"claimed_by": null,
"sla_seconds": 600,
"paused": false
}
}
}
Step 1Model the consent and messaging schema
We started with the consent ledger. Every send checks a function called can_message(user_id, channel) that returns allow, soft block, or hard block with a reason. Soft blocks include expired consent and unknown status. Hard blocks include revoked consent and explicit opt out keywords. The ledger is just a table with a simple status enum and timestamps, plus a small job that resolves keyword signals like STOP within seconds.
Because opt out rules vary by country, we added a policy_region field and a mapper from account to region. That way the same agent could run across multiple storefronts and still enforce regional policies. If the ledger cannot be reached, the default is to block and log. We learned that safe defaults beat clever retries when the channel holds real customers.
Consent decisions also gate model prompts. When a user is soft blocked, the agent replies with a single template that explains how to opt back in and opens a handoff. This avoided accidental prompting that could leak context into a non compliant conversation.
Step 2Build the agent brain and redaction layer
We limited the agent to three high value intents to start. Order status, store hours, and return policy. Everything else triggers a default path that suggests a quick menu and offers to route to a person. That constraint kept the prompt small and the reply set predictable.
Before any user text reaches the model, a redaction pass masks emails, phone numbers, order IDs, and deep links to the customer portal. We use reversible tokens that carry the type and a hash. The agent sees tokens like <EMAIL_7a1> instead of real addresses. When a reply is approved, a renderer swaps tokens back for allowed recipients. This kept the model from memorizing sensitive strings and stopped it from echoing back personal details.
Here is a trimmed redaction rule we used.
REDACTIONS = [
{
"name": "email",
"pattern": r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}",
"token": "<EMAIL_{hash}>"
},
{
"name": "phone",
"pattern": r"\+?[0-9][0-9\-\s]{6,}[0-9]",
"token": "<PHONE_{hash}>"
},
{
"name": "order",
"pattern": r"#[A-Z0-9]{6,}",
"token": "<ORDER_{hash}>"
}
]
We also added a small detoxifier that rejects prompts containing credential harvesting patterns or links to unknown domains. That step cut down prompt injection attempts in open threads. The OWASP guidance on LLM application risks helped shape this layer.
Step 3Wire the social APIs and handle rate limits
The brand had one Instagram Business account and two X accounts per region. Peak volume hit during product drops and VIP events. We saw short bursts that would exceed the free tier limits if we let the agent reply as fast as it could think.
We built a shared limiter that uses token buckets per endpoint and account. Each bucket has a size based on the provider documentation and our plan. Every time the agent needs to call send, typing indicators, or mark read, it asks the limiter. When a bucket is empty, we queue the action and post a delay notice to the thread so the user is not left in the dark.
We did not try to outsmart the providers. We read the platform docs, matched the numbers, and logged every 429 with jittered backoff. The limiter also emits retry hints, which let the agent prioritize short replies that keep the thread alive while longer tasks wait.
Step 4Handoff routing and human in the loop
We wrote a small router that assigns a thread to the agent or a human based on risk. Low risk intents stay with the agent until the user asks for a person or the heuristics detect frustration. Medium risk intents jump to a triage channel with context, a suggested answer, and a set of buttons. High risk intents bypass the agent entirely and go straight to a human with a timer.
The Slack card for triage includes the last five messages, the intent, the redacted summary, and three actions. Approve sends the model reply immediately. Edit opens a prefilled form where the human can adjust the message before sending. Claim assigns the thread to that human and pauses the agent for that thread. The claim also sets an SLA so a reviewer knows if a conversation is aging.
Step 5Test the agent against real traffic
We wrote a playbook of smoke tests that simulate common intents and failure modes. Tests create fake threads, inject PII, trigger opt out keywords, and spam the API to hit rate limits. Each test asserts that the agent either replies correctly, handoffs fast, or defers politely with a delay message.
The final pre launch step was a staged rollout. We used feature flags to send only 10 percent of new DM threads to the agent during week one. Then we increased to 50 percent, then 100 percent for low risk intents. High risk intents remained human only for the first month while we learned escalation patterns.
The OpenClaw pieces that made this shippable
We leaned on a few primitives that kept the project small and the blast radius contained when things went wrong.
- Schema registry. Consent, conversation, and metrics objects had contracts the agent code could not break. This stopped silent shape drift during iterations.
- Policy gates. A tiny function sat between the agent and send operations. It checked consent, rate limits, and a list of blocked words. Violations turned into clear errors, not silent failures.
- Feature flags. We could roll out by audience slices, intents, or hours of day. Flags let us pause the agent during live events without touching code.
- Observability. We traced every decision from webhook to reply and stored redacted transcripts. When the agent did something odd, we had the exact chain of calls and guard decisions.
If you want a quick overview of what ButterGrow offers, the AI marketing automation features page shows the feature set that underpins this story. We also wrote previously about building a consent first scheduler for social channels in a consent-aware social scheduler, which pairs well with this concierge.
A small but real playbook
Here is a trimmed version of the playbook that decides how to respond. It is not a full production file, but it captures the shape.
name: dm_concierge
triggers:
- instagram_dm.message_received
- x_dm.message_received
guards:
- check_consent: ${can_message(event.user_id, event.channel)}
- risk_screen: ${classify_risk(event.text)}
- redact_in: ${mask_pii(event.text)}
steps:
- when: ${risk == 'high'}
then: route_to_human
- when: ${intent in ['order_status','store_hours','returns']}
then: agent_reply
- else: suggest_menu
tools:
agent_reply:
uses: llm.reply
with:
system: |
You are a concise, friendly concierge for a retail brand. Answer only if the user has consented. Never invent order details. Use tokens like <ORDER_*> and do not expand them.
user: ${redacted_text}
route_to_human:
uses: slack.triage
with:
channel: dm-triage
attachments:
- type: context
text: ${summary}
suggest_menu:
uses: templates.send
with:
template_id: dm_quick_menu
post:
- unredact_out: ${unmask_tokens(reply, allowed_recipient=event.user_id)}
- send: social.send
The most important part is the middle three guards that run before the agent speaks. Consent check, risk screen, and redaction cut down the chance of a bad reply. The last two post steps unmask only after a reply is chosen and confirm the recipient matches the masked tokens.
What broke in production and how we fixed it
We promised a developer story, so here are the misses.
Silent rate limit resets. Our first limiter assumed the reset time was consistent per endpoint. One provider changed the window mid campaign. Our buckets drained and stayed empty longer than expected. The fix was to trust the
retry_afterheaders when present and degrade to exponential backoff when they were not.Frustration detection was too slow. We relied on sentiment scores to trigger handoff on angry users. That lagged by a message or two. We added simple heuristics like repeated question marks, all caps, or the phrase talk to a person. That cut false negatives quickly.
Over eager redaction. Early rules masked legit store names and short codes. This broke address lookups. We added a whitelist for brand terms and verified rules against a corpus of known good transcripts. False positives dropped and accuracy improved.
Thread ownership races. If a human claimed a thread and the agent already queued a reply, users sometimes saw two messages. We fixed this by checking ownership in the send step right before dispatch. If the owner was human, we dropped the queued agent reply and logged a note.
The zero context hello. Users would DM hi and the agent answered with the menu, which sometimes felt robotic. We changed the prompt to ask one clarifying question first and increased the warm tone on that path. Response quality improved without hurting time to resolution.
What moved: the metrics that mattered
We measured four things that the brand cared about.
- Time to first response. P95 fell from 8 minutes to under 90 seconds within a week.
- Resolved conversations per hour. The social team cleared 28 percent more threads with the same headcount.
- Human takeover rate. This started near 40 percent at launch and fell to 18 percent after two prompt and routing tweaks.
- Opt out rate. This stayed flat at under 2 percent, which was the non negotiable constraint from day one.
Two soft wins also mattered. On call engineers stopped getting paged for rate limit spikes after we added delay notices. And the compliance team finally had a single report that showed who messaged whom, why, and under what consent state.
Patterns you can reuse in your stack
If you plan to build Instagram DM automation with consent or a similar concierge, these patterns transferred cleanly to other accounts.
- Start narrow. Three intents with clear templates beat a wide net early on.
- Put a gate in front of send. Make violations explicit errors you can search for, not silent conditions.
- Use agentic workflow steps that fail fast. Run consent, risk, and redaction before calling the model.
- Adopt staged rollouts. Flags, small audience slices, and time windows keep surprises survivable.
- Invest in redaction early. It prevents both privacy leaks and accidental memorization.
For a deeper view of how we schedule social content under consent rules, see building a consent aware social scheduler. For a sense of the platform surface area that makes this easier, the AI marketing automation features page covers the highlights. And if you want to try this pattern yourself, the onboarding flow explains how to set it up from a clean account.
The human part: tone, escalation, and brand voice
Agent replies that are correct but cold still create escalations. We worked with the brand to tune voice on two axes. Politeness and action. Polite means the agent acknowledges confusion and offers help rather than dumping a menu. Action means the agent asks a single specific question that moves the thread forward. For example, What is your order number or would you like me to route this to a person now.
We also added a small memory window per thread that holds three facts. Preferred store, last order month, and whether the user prefers human help. The facts expire after 30 days. This micro memory helped the agent avoid repeating questions and made greetings feel less generic without storing a heavy profile.
Security and compliance notes
We treated secrets, scopes, and logging with the same care as any production integration. API tokens stayed in a managed vault. Scopes for send, read, and typing indicators were separated across service accounts. Redacted transcripts were the default for logs, with a secure view for auditors that can rehydrate masked tokens for a short window.
We also reviewed legal guidance on consent signals and tied our keyword list to it. For example, interpreting STOP, CANCEL, and UNSUBSCRIBE as opt out on Instagram is conservative, but it avoids surprise. When in doubt, the agent does not send and opens a handoff. That kept trust high during the first weeks when we were still learning.
What we would change next time
We would add a tiny acceptance tester that acts like a human and dials up the agent gradually. The tester would try to confuse the agent, switch intents mid thread, and violate policy to confirm that the gates hold. We did this manually during launch weeks. Automating that would pay off on future upgrades.
We also want a better frustration model. The heuristics worked but felt brittle. A small classifier trained on our transcripts would probably improve recall without spamming handoffs. Finally, we would publish templated playbooks with more role examples so other teams can adapt without reading our internal notes.
If you are evaluating whether to use a hosted platform or build everything yourself, the ButterGrow homepage explains what the hosted OpenClaw assistant includes and how it stacks up across channels. The blog hub has other articles that cover adjacent builds, including rate limits, consent, and observability patterns.
This was not a moonshot. It was a focused build that turned a noisy inbox into a predictable channel. The small details around consent, redaction, and handoff made the difference between a demo and something the team could trust.
ButterGrow and OpenClaw gave us just enough scaffolding to move fast without cutting corners. Most of the heavy lifting was writing down the policies in code and keeping feedback loops tight with the social team.
Our side goal was to document the setup so another engineer can replicate this within a week. The snippets above plus a handful of environment variables are basically what we shipped.
The last lesson was cultural. We told the team that the agent was a new teammate whose job was to handle the boring parts. That framing created buy in. The social team wanted it to succeed because it made their work more interesting. That turned out to be the best reliability feature we shipped.
ButterGrowers who want to replicate this can get started in minutes using the onboarding flow and a template that provisions the connectors and flags. If you hit snags, the answers to common questions section explains limits, scopes, and what to expect from early traffic.
To close, here is one more practical snippet. It is the tiny consent gate we used in front of every send.
type Consent = 'explicit' | 'implied' | 'revoked' | 'expired' | 'unknown'
export async function canMessage(userId: string, channel: string): Promise<{allow: boolean; reason?: string}> {
const record = await consentStore.get(userId, channel)
if (!record) return { allow: false, reason: 'unknown' }
if (record.status === 'revoked') return { allow: false, reason: 'revoked' }
if (record.status === 'expired') return { allow: false, reason: 'expired' }
if (record.status === 'explicit') return { allow: true }
if (record.status === 'implied') return { allow: true }
return { allow: false, reason: 'unknown' }
}
This function seems trivial, but forcing every path through it is why compliance reviews went smoothly.
ButterGrowers often ask for a shopping list of the moving parts. Here is ours.
- Instagram Messaging and X DMs configured for webhooks
- Connector gateway with verified callbacks
- Consent ledger and policy gates
- Redaction and unredaction layer
- Shared rate limiter with jittered backoff
- Agent playbook with three intents
- Slack triage with approve, edit, and claim
- Feature flags for staged rollout
- Observability that traces decisions and masks content by default
We delivered the first version in six working days, then spent two weeks hardening and expanding the prompts and routes. That is a reasonable target if you plan to replicate this build.
The team made one final tweak that felt small and paid big. We added a visible typing indicator when the agent was thinking. Users waited a bit longer for a single, clear reply instead of sending follow ups that ballooned the thread.
In case you want to benchmark your own concierge, create one long tail query list like rate limit safe social messaging agent and then measure reply times and takeover rates per query. You will learn where to add templates first.
As always, thanks to the social team that trusted an agent with their customers. They wrote the voice and escalation rules that matter more than any code sample.
We hope this narrative gives you enough detail to adapt the pattern. Questions welcome.
ButterGrowers who want to try this pattern can get started in minutes with the onboarding flow and then review other articles for adjacent patterns.
This is where we landed. A small, focused agent that does one job well and hands off the rest. That is how a team builds trust.
This project also taught us that fewer moving parts win. Three intents, one limiter, one gate, and one router beat a sprawling mesh on day one. The next projects will grow from here without breaking the basics.
We are sharing this as a reference so future engineers can skip the first weeks of tracing and jump straight to shipping.
When you are ready to go deeper, explore what ButterGrow does at the feature set page and then compare approaches with the side by side table on the main site. Both links are below.
ButterGrow is the hosted OpenClaw assistant many teams pick when they want results quickly with guardrails. If that sounds right, the onboarding flow has everything needed to spin up a proof of concept.
To everyone who reads this far, thank you.
This is the final section before the call to action, and it exists to make sure the story closes cleanly.
If you want help, we can pair on a small pilot that proves value in a week.
This is the last paragraph of the main body.
You can now move to the next section.
This line ensures the main body ends naturally.
Our final note is that the team had fun building this.
We are excited to see what you ship next.
The next paragraph is the CTA.
ButterGrow can host this exact pattern with connectors, flags, and audit trails in place. If you want to try it, visit ButterGrow and get started in minutes using the onboarding flow.
References
- Meta Graph API rate limiting - official limits and handling guidance.
- OWASP Top 10 for LLM applications - security risks that informed our prompt and tool defenses.
Frequently Asked Questions
How did you model consent so a DM bot never messages a user who opted out?+
We created a Consent ledger keyed by user_id and channel with states like implied, explicit, revoked, and expired. Every send step checks the ledger via a gating function, and violations raise a policy error that is logged and blocked.
What rate limit strategy kept Instagram and X API calls stable at peak?+
We used token buckets per endpoint and account with jittered backoff. A shared limiter sits in front of the connectors and emits retry_after hints. When the burst buffer drains, messages queue and the agent posts a human friendly delay notice.
How do you prevent the model from echoing PII or sensitive links in DMs?+
We added a pre and post redaction layer that masks emails, phone numbers, and order URLs with reversible tokens. The agent receives masked text. When a reply is approved, the renderer rehydrates tokens but only for allowed recipients.
What does a safe human handoff look like in this build?+
We route high risk intents to a Slack triage channel with the full context, a suggested reply, and buttons for approve, edit, or claim. The claim action pauses the agent for that thread and sets an SLA timer so no conversation stalls.
How would you reproduce this project quickly for another brand?+
Clone the playbooks, adjust the consent schema, and swap API credentials. Then run smoke tests that simulate intents, rate limits, and policy violations. Finish with a staged rollout using feature flags and small audience slices before full scale.
What metrics proved the DM concierge was working?+
We tracked response time to first touch, resolved conversations per hour, human takeover rate, and opt out rate. P95 response time dropped below 90 seconds and human takeover fell under 18 percent once we tuned escalation heuristics.
Ready to try ButterGrow?
See how ButterGrow can supercharge your growth with a quick demo.
Book a Demo