--- name: intercom-sdk-patterns description: 'Apply production-ready intercom-client SDK patterns for TypeScript. Use when implementing Intercom integrations, refactoring SDK usage, or establishing team coding standards for Intercom API calls. Trigger with phrases like "intercom SDK patterns", "intercom best practices", "intercom code patterns", "idiomatic intercom", "intercom typescript". ' allowed-tools: Read, Write, Edit version: 1.0.0 license: MIT author: Jeremy Longshore tags: - saas - support - messaging - intercom compatibility: Designed for Claude Code --- # Intercom SDK Patterns ## Overview Production-ready patterns for the `intercom-client` TypeScript SDK covering client initialization, pagination, error handling, and type safety. ## Prerequisites - `intercom-client` package installed - TypeScript 5.0+ project - Familiarity with async/await and generators ## Instructions ### Step 1: Type-Safe Client Wrapper ```typescript // src/intercom/client.ts import { IntercomClient } from "intercom-client"; import { Intercom } from "intercom-client"; let instance: IntercomClient | null = null; export function getClient(): IntercomClient { if (!instance) { instance = new IntercomClient({ token: process.env.INTERCOM_ACCESS_TOKEN!, }); } return instance; } // Type-safe contact creation helper export async function createContact( params: Intercom.CreateContactRequest ): Promise { return getClient().contacts.create(params); } // Type-safe search helper export async function searchContacts( query: Intercom.SearchRequest ): Promise { return getClient().contacts.search(query); } ``` ### Step 2: Cursor-Based Pagination Intercom uses cursor-based pagination. The `starting_after` parameter points to the next page. ```typescript // Generic paginator for any list endpoint async function* paginateContacts( client: IntercomClient, perPage = 50 ): AsyncGenerator { let startingAfter: string | undefined; do { const page = await client.contacts.list({ perPage, startingAfter, }); for (const contact of page.data) { yield contact; } // Cursor for next page startingAfter = page.pages?.next?.startingAfter ?? undefined; } while (startingAfter); } // Usage const client = getClient(); for await (const contact of paginateContacts(client)) { console.log(contact.email); } ``` The SDK also supports built-in iteration: ```typescript // SDK auto-pagination (articles, contacts, etc.) const response = await client.articles.list(); for await (const article of response) { console.log(article.title); } ``` ### Step 3: Error Handling with IntercomError ```typescript import { IntercomError } from "intercom-client"; async function safeIntercomCall( operation: () => Promise, context: string ): Promise<{ data: T | null; error: IntercomError | null }> { try { const data = await operation(); return { data, error: null }; } catch (err) { if (err instanceof IntercomError) { console.error(`[Intercom:${context}] ${err.statusCode}: ${err.message}`, { requestId: err.body?.request_id, errors: err.body?.errors, }); // Specific error handling switch (err.statusCode) { case 401: console.error("Token invalid or expired. Regenerate access token."); break; case 404: console.error("Resource not found. Verify the ID."); break; case 409: console.error("Conflict: resource already exists."); break; case 422: console.error("Validation failed:", err.body?.errors); break; case 429: console.error("Rate limited. Back off and retry."); break; } return { data: null, error: err }; } throw err; // Re-throw non-Intercom errors } } // Usage const { data: contact, error } = await safeIntercomCall( () => client.contacts.find({ contactId: "abc123" }), "findContact" ); ``` ### Step 4: Retry with Exponential Backoff ```typescript async function withRetry( operation: () => Promise, config = { maxRetries: 3, baseDelayMs: 1000 } ): Promise { for (let attempt = 0; attempt <= config.maxRetries; attempt++) { try { return await operation(); } catch (err) { if (err instanceof IntercomError) { // Only retry on rate limits and server errors if (err.statusCode !== 429 && (err.statusCode ?? 0) < 500) { throw err; } if (attempt === config.maxRetries) throw err; // Use Retry-After header if available, otherwise exponential backoff const retryAfter = err.headers?.["retry-after"]; const delay = retryAfter ? parseInt(retryAfter) * 1000 : config.baseDelayMs * Math.pow(2, attempt) + Math.random() * 500; console.log(`Retry ${attempt + 1}/${config.maxRetries} in ${delay}ms`); await new Promise((r) => setTimeout(r, delay)); } else { throw err; } } } throw new Error("Unreachable"); } ``` ### Step 5: Contact Search with Compound Queries ```typescript // Search with multiple conditions (AND/OR) const results = await client.contacts.search({ query: { operator: "AND", value: [ { field: "role", operator: "=", value: "user" }, { field: "custom_attributes.plan", operator: "=", value: "pro" }, { operator: "OR", value: [ { field: "email", operator: "~", value: "@acme.com" }, { field: "email", operator: "~", value: "@bigcorp.com" }, ], }, ], }, pagination: { per_page: 25 }, sort: { field: "created_at", order: "descending" }, }); ``` ### Step 6: Multi-Tenant Client Factory ```typescript const clientCache = new Map(); export function getClientForWorkspace( workspaceToken: string ): IntercomClient { if (!clientCache.has(workspaceToken)) { clientCache.set( workspaceToken, new IntercomClient({ token: workspaceToken }) ); } return clientCache.get(workspaceToken)!; } ``` ## Intercom Search Operators | Operator | Meaning | Example | |----------|---------|---------| | `=` | Equals | `email = "test@example.com"` | | `!=` | Not equals | `role != "lead"` | | `~` | Contains | `email ~ "@acme.com"` | | `!~` | Not contains | `name !~ "test"` | | `>` | Greater than | `created_at > 1700000000` | | `<` | Less than | `last_seen_at < 1700000000` | | `IN` | In list | `tag_id IN ["tag1", "tag2"]` | | `NIN` | Not in list | `segment_id NIN ["seg1"]` | ## Error Handling | Pattern | Use Case | Benefit | |---------|----------|---------| | `safeIntercomCall` wrapper | All API calls | Prevents uncaught exceptions | | `withRetry` | Transient failures (429, 5xx) | Automatic recovery | | Cursor pagination generator | Large data sets | Memory-efficient streaming | | Client factory | Multi-tenant apps | Workspace isolation | ## Resources - [intercom-client npm](https://www.npmjs.com/package/intercom-client) - [Intercom API Reference](https://developers.intercom.com/docs/references/rest-api/api.intercom.io) - [Search Contacts](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/contacts/searchcontacts) ## Next Steps Apply patterns in `intercom-core-workflow-a` for contact management workflows.