Intercom Local Dev Loop
Overview
Set up a fast local development workflow for Intercom integrations with proper test isolation, mocking strategies, and webhook tunneling. The loop has two lanes: a mocked unit lane that runs offline with no token, and an integration lane that talks to a real dev workspace and is skipped automatically when no token is present.
Prerequisites
- Completed
intercom-install-authsetup - Node.js 18+ with npm/pnpm
- A test/development Intercom workspace (separate from production)
Authentication
The client authenticates with a single Intercom bearer access token, issued per
workspace by the intercom-install-auth step. Read it from
process.env.INTERCOM_ACCESS_TOKEN (loaded from git-ignored .env.development);
never hardcode it. The mocked unit lane needs no token at all — pointing the loop
at a different dev workspace is only a matter of swapping the .env.development
value.
Instructions
Work through these steps to stand up the loop. The full, copy-paste-ready code for every step lives in the implementation walkthrough.
Scaffold the project structure — an
src/intercom/module (singletonclient.ts, pluscontacts.ts/conversations.ts/types.ts), atests/tree with amocks/factory, and three env files (.env.examplecommitted,.env.developmentand.env.testgit-ignored). Use Write to create each file. See implementation.md.Configure environments — commit
.env.exampleas the template and keep real tokens in the git-ignored.env.development. See implementation.md.Write an environment-aware client singleton that reads the token, throws a clear error when it is missing, and exposes a
resetClient()for tests. The skeleton:// src/intercom/client.ts import { IntercomClient } from "intercom-client"; let instance: IntercomClient | null = null; export function getClient(): IntercomClient { if (!instance) { const token = process.env.INTERCOM_ACCESS_TOKEN; if (!token) { throw new Error( "INTERCOM_ACCESS_TOKEN not set. Copy .env.example to .env.development" ); } instance = new IntercomClient({ token }); } return instance; } export function resetClient(): void { instance = null; }Build a mock client factory (
tests/mocks/intercom.ts) covering contacts, conversations, messages, admins, and tags withvi.fn()resolved values, so the unit lane never touches the network. Full factory in implementation.md.Write mocked unit tests against the factory, asserting call arguments and returned shapes. See implementation.md.
Tunnel webhooks with ngrok — run the local server,
ngrok http 3000, and register the HTTPS URL in Intercom Developer Hub. Use Bash(npx:*) for ngrok. See implementation.md.Wire package scripts (
dev,test,test:watch,test:integration,typecheck) — use Edit to add them topackage.json, then drive the loop with Bash(npm:*). See implementation.md.
Output
Following this skill produces a working local Intercom development loop:
- A scaffolded
src/intercom/client module andtests/tree with a reusable mock factory. - Three environment files — a committed
.env.exampletemplate plus git-ignored.env.development/.env.test. - An offline mocked unit test lane (
npm run test/test:watch) that runs with no token and no network. - A token-gated integration lane (
npm run test:integration) that is skipped automatically whenINTERCOM_ACCESS_TOKENis absent. - An ngrok webhook tunnel exposing the local server to Intercom's Developer Hub.
Error Handling
| Error | Cause | Solution |
|---|---|---|
INTERCOM_ACCESS_TOKEN not set |
Missing .env file | Copy .env.example to .env.development |
| Port 3000 in use | Another process | lsof -i :3000 and kill, or change port |
| ngrok tunnel expired | Free tier 2h limit | Restart ngrok or use paid plan |
| Mock type mismatch | SDK updated | Regenerate mocks from SDK types |
rate_limit_exceeded in dev |
Dev workspace limits | Add delays between integration tests |
Examples
A minimal mocked unit test (from implementation.md):
it("should create a user contact", async () => {
const contact = await mockClient.contacts.create({
role: "user",
externalId: "user-123",
email: "test@example.com",
});
expect(contact.id).toBe("mock-contact-id");
expect(mockClient.contacts.create).toHaveBeenCalledOnce();
});
For the token-gated integration test pattern (describe.skipIf, live create +
cleanup) and the commands that drive each lane, see
the examples reference.
Resources
- Full implementation walkthrough — every step's complete code
- Integration examples — live-workspace test pattern and run commands
- intercom-client npm
- Vitest Documentation
- ngrok
- See
intercom-sdk-patternsfor production-ready code patterns.