intercom-local-dev-loop

'Configure Intercom local development with testing, mocking, and hot

6 Tools
intercom-pack Plugin
saas packs Category

Allowed Tools

ReadWriteEditBash(npm:*)Bash(npx:*)Grep

Provided by Plugin

intercom-pack

Claude Code skill pack for Intercom (24 skills)

saas packs v1.0.0
View Plugin

Installation

This skill is included in the intercom-pack plugin:

/plugin install intercom-pack@claude-code-plugins-plus

Click to copy

Instructions

Intercom Local Dev Loop

Overview

Set up a fast local development workflow for Intercom integrations with proper test isolation, mocking strategies, and webhook tunneling.

Prerequisites

  • Completed intercom-install-auth setup
  • Node.js 18+ with npm/pnpm
  • A test/development Intercom workspace (separate from production)

Instructions

Step 1: Project Structure


my-intercom-app/
├── src/
│   ├── intercom/
│   │   ├── client.ts       # Singleton client
│   │   ├── contacts.ts     # Contact operations
│   │   ├── conversations.ts # Conversation operations
│   │   └── types.ts        # Intercom type extensions
│   └── index.ts
├── tests/
│   ├── mocks/
│   │   └── intercom.ts     # Mock client factory
│   ├── contacts.test.ts
│   └── conversations.test.ts
├── .env.development        # Dev workspace token
├── .env.test               # Test config (mocked)
├── .env.example            # Template
└── package.json

Step 2: Environment Configuration


# .env.example (commit this)
INTERCOM_ACCESS_TOKEN=
INTERCOM_WEBHOOK_SECRET=
NODE_ENV=development

# .env.development (git-ignored, real dev workspace token)
INTERCOM_ACCESS_TOKEN=dG9rOmRldl90b2tlbl9oZXJl
INTERCOM_WEBHOOK_SECRET=your-webhook-secret
NODE_ENV=development

Step 3: Client Singleton with Environment Awareness


// 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;
}

// Reset for testing
export function resetClient(): void {
  instance = null;
}

Step 4: Mock Client for Tests


// tests/mocks/intercom.ts
import { vi } from "vitest";

export function createMockClient() {
  return {
    contacts: {
      create: vi.fn().mockResolvedValue({
        type: "contact",
        id: "mock-contact-id",
        role: "user",
        email: "test@example.com",
        name: "Test User",
        external_id: "ext-123",
        custom_attributes: {},
        created_at: 1711100000,
        updated_at: 1711100000,
      }),
      find: vi.fn().mockResolvedValue({
        type: "contact",
        id: "mock-contact-id",
        email: "test@example.com",
      }),
      search: vi.fn().mockResolvedValue({
        type: "list",
        data: [],
        total_count: 0,
        pages: { type: "pages", page: 1, per_page: 50, total_pages: 0 },
      }),
      list: vi.fn().mockResolvedValue({
        type: "list",
        data: [],
        total_count: 0,
        pages: { next: null },
      }),
      update: vi.fn(),
      delete: vi.fn(),
      tag: vi.fn(),
      untag: vi.fn(),
    },
    conversations: {
      create: vi.fn().mockResolvedValue({
        type: "conversation",
        id: "mock-convo-id",
        state: "open",
      }),
      find: vi.fn(),
      list: vi.fn().mockResolvedValue({
        type: "conversation.list",
        conversations: [],
        pages: { next: null },
      }),
      reply: vi.fn(),
      close: vi.fn(),
      assign: vi.fn(),
    },
    messages: {
      create: vi.fn().mockResolvedValue({
        type: "user_message",
        id: "mock-msg-id",
      }),
    },
    admins: {
      list: vi.fn().mockResolvedValue({
        type: "admin.list",
        admins: [{ id: "admin-1", name: "Test Admin", email: "admin@test.com" }],
      }),
    },
    tags: {
      create: vi.fn().mockResolvedValue({ type: "tag", id: "tag-1", name: "test" }),
      list: vi.fn().mockResolvedValue({ type: "list", data: [] }),
    },
  };
}

Step 5: Write Tests


// tests/contacts.test.ts
import { describe, it, expect, vi, beforeEach } from "vitest";
import { createMockClient } from "./mocks/intercom";

describe("Contact Operations", () => {
  let mockClient: ReturnType<typeof createMockClient>;

  beforeEach(() => {
    mockClient = createMockClient();
  });

  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(contact.role).toBe("user");
    expect(mockClient.contacts.create).toHaveBeenCalledWith({
      role: "user",
      externalId: "user-123",
      email: "test@example.com",
    });
  });

  it("should search contacts by email", async () => {
    await mockClient.contacts.search({
      query: { field: "email", operator: "=", value: "test@example.com" },
    });

    expect(mockClient.contacts.search).toHaveBeenCalledOnce();
  });
});

Step 6: Webhook Testing with ngrok


# Install ngrok
npm install -g ngrok

# Start your local server
npm run dev  # Starts on port 3000

# Tunnel to expose locally
ngrok http 3000

# Use the HTTPS URL (e.g., https://abc123.ngrok.io) as your webhook URL
# in Intercom Developer Hub > Webhooks

Step 7: Package Scripts


{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "test": "vitest",
    "test:watch": "vitest --watch",
    "test:integration": "INTERCOM_ACCESS_TOKEN=$INTERCOM_DEV_TOKEN vitest --config vitest.integration.config.ts",
    "typecheck": "tsc --noEmit"
  }
}

Integration Test Pattern


// tests/integration/contacts.integration.test.ts
import { describe, it, expect } from "vitest";
import { IntercomClient } from "intercom-client";

const client = new IntercomClient({
  token: process.env.INTERCOM_ACCESS_TOKEN!,
});

describe.skipIf(!process.env.INTERCOM_ACCESS_TOKEN)("Contacts Integration", () => {
  it("should create and retrieve a contact", async () => {
    const created = await client.contacts.create({
      role: "lead",
      name: `Integration Test ${Date.now()}`,
    });

    expect(created.id).toBeDefined();

    // Clean up
    await client.contacts.delete({ contactId: created.id });
  });
});

Error Handling

Error Cause Solution
INTERCOMACCESSTOKEN 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
ratelimitexceeded in dev Dev workspace limits Add delays between integration tests

Resources

Next Steps

See intercom-sdk-patterns for production-ready code patterns.

Ready to use intercom-pack?