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".
Production-ready patterns for the intercom-client TypeScript SDK covering client initialization, pagination, error handling, and type safety.
intercom-client package installed// 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<Intercom.Contact> {
return getClient().contacts.create(params);
}
// Type-safe search helper
export async function searchContacts(
query: Intercom.SearchRequest
): Promise<Intercom.ContactList> {
return getClient().contacts.search(query);
}
Intercom uses cursor-based pagination. The starting_after parameter points to the next page.
// Generic paginator for any list endpoint
async function* paginateContacts(
client: IntercomClient,
perPage = 50
): AsyncGenerator<Intercom.Contact> {
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:
// SDK auto-pagination (articles, contacts, etc.)
const response = await client.articles.list();
for await (const article of response) {
console.log(article.title);
}
import { IntercomError } from "intercom-client";
async function safeIntercomCall<T>(
operation: () => Promise<T>,
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"
);
async function withRetry<T>(
operation: () => Promise<T>,
config = { maxRetries: 3, baseDelayMs: 1000 }
): Promise<T> {
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");
}
// 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" },
});
const clientCache = new Map<string, IntercomClient>();
export function getClientForWorkspace(
workspaceToken: string
): IntercomClient {
if (!clientCache.has(workspaceToken)) {
clientCache.set(
workspaceToken,
new IntercomClient({ token: workspaceToken })
);
}
return clientCache.get(workspaceToken)!;
}
| Operator | Meaning | Example |
|---|---|---|
= | Equals | email = "[email protected]" |
!= | 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"] |
| 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 |
Apply patterns in intercom-core-workflow-a for contact management workflows.