Intercom API for AI Support Agents
Your agent replies, gets a 200, and the customer never receives it. The Intercom email flag, the danger of replying to "last", and how to build support agents that draft.
Replying to the wrong conversation is worse than not replying at all. A failed call shows up in your logs and someone fixes it. A reply sent to the wrong customer is a message in somebody's inbox containing another person's context, and no amount of fixing makes it unsent.
That is the frame for Intercom. The API is pleasant to work with. The risk is not technical failure, it is confident delivery of the wrong thing to a real human.
The reply endpoint, and the word "last"
Replying is POST /conversations/{id}/reply. For a reply as the contact you need message_type: "comment", type: "user", an identifier such as intercom_user_id (email and user ID variants exist too), and a body. You can attach up to 10 image URLs, pass reply_options for quick replies, and set created_at if you need a specific timestamp rather than now.
Here is the part to be careful with: the id path parameter accepts the literal string last as well as a real conversation ID. It is a convenience for scripts. For an agent it is a loaded gun, because "last" resolves at request time, not at reasoning time.
Picture the agent reading a conversation, thinking for eight seconds, and sending a reply to "last." In those eight seconds a different customer wrote in. The reply goes to them instead. Everything returns 200.
So: never let an agent use "last." Always pass the explicit conversation ID that the agent actually read, and ideally verify the ID it is replying to matches the one it loaded. Threading bugs in support tooling are nearly always a race like this rather than a logic error.
The silent email failure
This one is worth the price of admission. Intercom's own reference notes that by default, bot or Operator replies to inbound email conversations are not sent to the customer. Sending them as outbound email requires enabling an email-reply feature flag, which goes through your accounts team.
Read that again with an agent in mind. Your agent replies. The API accepts it. The reply appears in the Intercom conversation. Your logs show success. The customer, who originally emailed in, receives nothing and waits.
There is no error to catch, because nothing failed. This is the purest version of a problem we write about a lot: the call succeeded and the outcome did not happen. If you are building an agent that handles email conversations in Intercom, confirm the flag's status with your accounts team before you trust a single success response, and check a real customer inbox during testing rather than only the Intercom UI.
More on this class of bug in 200 OK but it failed: catching errors hidden in API response bodies.
Two settings to get right before anything else
Your region's base URL. Intercom runs api.intercom.io, api.eu.intercom.io and api.au.intercom.io. If your workspace is hosted in the EU and you call the US host, your perfectly valid token gets you nowhere and the errors read like a data problem rather than a region problem. This burns a surprising amount of time.
Pin the version. The Intercom-Version header is optional, and when you omit it you get whatever version is set on your app package. That means your integration's behaviour can change without you deploying anything. Pin it explicitly, then upgrade deliberately. This is the agent-consumer version of the problem we describe in API versioning strategy when AI agents are your consumers: a human reads the changelog, an agent does not.
Setting it up
npx swytchcode
swy login
swy init --mode=sandbox
swy get intercom
swy auth connect intercom # OAuth, or paste an access token
swy auth status
# reads first
swy discover "list conversations" --project intercom
swy discover "add a note to a conversation" --project intercom
swy discover "reply to a conversation" --project intercom
swy list methods intercom
swy info <canonical_id>
swy add method <canonical_id> # reads + notes now; replies later, if everThe allowlist we would actually ship for v1 of a support agent:
- Read conversations, read contacts, search. Freely. This is where most of the value is.
- Add an internal note, set tags, assign. Allowed. Notes are not customer-visible, so a wrong note is an inconvenience rather than an incident. This is where an agent can be useful immediately with almost no risk.
- Reply to a customer. Not in the allowlist for v1. Let the agent draft into a note, and let a human copy it into a reply. Unglamorous, and it means your first week in production cannot produce a customer-visible mistake.
If a method is not added, it cannot run. Not "the model has been told not to." There is no path to it, so no prompt injection in a customer message can produce a reply. That distinction matters more in support than anywhere else, because your agent reads untrusted text from strangers all day.
When you do allow replies, gate them
{
"id": "customer-replies-need-a-human",
"target": ["<intercom reply canonical_id>"],
"when": { "field": "message_type", "operator": "==", "value": "comment" },
"action": {
"type": "REQUIRES_APPROVAL",
"message": "Agent wants to send a customer-visible reply"
},
"approval_timeout": "30m"
}The rule keys on message_type, which is how you let internal notes flow freely while customer-visible comments stop and ask. Thirty minutes is deliberately short: in support, an approval that arrives two hours later is worse than no reply, so failing closed quickly and handing the conversation to a human is the right outcome. Setup in the human approval docs, schema in the policy reference, walkthrough in how to set up policy guardrails.
Idempotency: the double reply
A reply is a non-idempotent write to a human's inbox. The request lands, Intercom posts the reply, the response is lost in transit, your agent retries, and the customer gets the same message twice. They will assume you are a bot, which, fair enough, but it also looks careless.
Writes are only auto-retried when an idempotency key is set, so an unprotected reply fails visibly rather than double-sending. Derive the key from the conversation and the thing you are responding to, not from the attempt, so a retry lands on the same key. And before replying, read the conversation's recent parts and check whether your reply is already there. One extra read, zero double replies. Background in API idempotency: the complete guide and the mechanics in the idempotency docs.
The same thinking applies to whatever triggers the agent. If a webhook delivers the same conversation event twice, you get two agent runs on one event, which is its own kind of double reply. Dedupe on the event ID before the run starts; we cover that in best webhook infrastructure for agent triggers.
From Claude Code, Cursor or your own service
For a human-in-the-loop support workflow, MCP in an editor or desktop client works nicely: you read a thread, ask for a draft, paste it yourself. Connect via the MCP server and your allowlist applies, so the assistant can read and annotate but not send. Setup in the MCP guide, reference in the MCP docs. For an always-on service, the SDK route with the Anthropic SDK is the usual shape, and skills.md gives a coding agent the conventions to build it.
What you can build
- Draft replies as internal notes. The agent reads the thread and the customer's history, writes a suggested reply as a note, and a human sends it with one edit or none. No reply permission needed, immediate time saving, and your team builds trust in the drafts before you ever consider automating the send.
- Triage and routing. Tag by topic, set priority, assign to the right team based on content rather than keyword rules. Low risk, and it fixes the thing that actually slows support down, which is conversations sitting in the wrong queue. A small classifier via Hugging Face is often cheaper here than a large model.
- Escalation detection. Watch open conversations for frustration, repeated contacts about the same issue, or a thread that has gone quiet for too long, and flag them to a lead. Reads plus a Slack message, no Intercom writes at all.
- Support signal into product. When several conversations describe the same gap, write it up as an idea in Aha! or a page in Notion with the customer quotes attached. This is the job everyone agrees should happen and nobody has time for.
- Multilingual support. Translate inbound messages into your working language for triage, and draft the reply back in the customer's language with a glossary so product names survive. Pair with DeepL.
FAQ
Should an agent ever reply to customers unsupervised?
Eventually, for narrow and well-understood categories, after you have months of draft quality to look at. Start with drafts. The teams that go straight to autonomous replies are the ones that end up writing an apology blog post.
How do I stop prompt injection from a customer message?
Assume it will happen, because people do try. The protection is structural: do not allowlist the reply method, keep credentials out of the model's context, and gate anything customer-visible behind approval. Then a malicious message in a ticket has nothing to reach. More in API access control for AI agents.
Why is my reply in Intercom but not in the customer's inbox?
Most likely the email-reply flag for bot and Operator replies on inbound email conversations. Check with your accounts team. It is not a bug in your code and you will not find it by reading your logs.
How do I prove what the agent sent?
swy audit network gives you every call with its arguments, so you can show the exact body sent to a specific conversation at a specific time, and swy audit policy shows what was held. In support that is the record you want when a customer disputes what they were told. See how to log every API call an AI agent makes.
Related reading
More content
How to Debug an AI Agent That Called the Wrong API
Working backwards from the response your agent got to the tool-selection decision that caused it, which is where most of the real debugging time goes.
iPaaS Was Built for Integrations, Not Agents
Deterministic trigger-action versus non-deterministic tool choice, and why the governance model that works for one doesn't transfer cleanly to the other.
Using the Airtable API from an AI Agent
Someone renames a field in the UI and your agent breaks with an error saying the field does not exist, while you are looking right at it. Airtable's schema drift problem, solved.
