Claude-code-plugins-plus-skills intercom-cost-tuning
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/intercom-pack/skills/intercom-cost-tuning" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-intercom-cost-tuning && rm -rf "$T"
manifest:
plugins/saas-packs/intercom-pack/skills/intercom-cost-tuning/SKILL.mdsource content
Intercom Cost Tuning
Overview
Reduce Intercom API costs through smart caching, search optimization, webhook-driven architecture, and usage monitoring. Intercom pricing is primarily seat-based and feature-based, but API efficiency reduces infrastructure costs and avoids rate limits.
Intercom Pricing Model
| Component | Pricing Basis | Cost Driver |
|---|---|---|
| Seats | Per agent/month | Number of teammates |
| Fin AI Agent | Per resolution | AI-handled conversations |
| Proactive Support | Per message sent | Outbound messages volume |
| Help Center | Included | N/A |
| API | Included (rate-limited) | Request volume determines infra cost |
Key insight: The API itself is free to use, but hitting rate limits (10K req/min) forces you to build queuing infrastructure. Reducing requests saves engineering time and infrastructure costs.
Instructions
Step 1: Audit Current API Usage
// Instrument all API calls to track usage patterns class IntercomUsageTracker { private calls = new Map<string, { count: number; totalMs: number }>(); track(endpoint: string, durationMs: number): void { const existing = this.calls.get(endpoint) || { count: 0, totalMs: 0 }; existing.count++; existing.totalMs += durationMs; this.calls.set(endpoint, existing); } report(): void { console.log("\n=== Intercom API Usage Report ==="); const sorted = [...this.calls.entries()].sort((a, b) => b[1].count - a[1].count); for (const [endpoint, stats] of sorted) { console.log( `${endpoint}: ${stats.count} calls, avg ${(stats.totalMs / stats.count).toFixed(0)}ms` ); } const total = sorted.reduce((sum, [, s]) => sum + s.count, 0); console.log(`\nTotal: ${total} API calls`); console.log(`Estimated rate: ${(total / 60).toFixed(0)} req/min (limit: 10,000)`); } }
Step 2: Replace Polling with Webhooks
// BAD: Polling for new conversations every 30 seconds // Cost: ~2,880 requests/day for ONE check setInterval(async () => { const conversations = await client.conversations.list(); // Check for new conversations... }, 30000); // GOOD: Webhook-driven (0 requests, instant notification) app.post("/webhooks/intercom", (req, res) => { const notification = req.body; if (notification.topic === "conversation.user.created") { handleNewConversation(notification.data.item); } res.status(200).json({ received: true }); });
Step 3: Cache Contact Lookups
import { LRUCache } from "lru-cache"; const contactCache = new LRUCache<string, any>({ max: 10000, ttl: 10 * 60 * 1000, // 10 min TTL }); // Before: 1 API call per contact lookup async function getContactName(contactId: string): Promise<string> { const contact = await client.contacts.find({ contactId }); return contact.name; } // After: API call only on cache miss async function getContactNameCached(contactId: string): Promise<string> { let name = contactCache.get(contactId) as string | undefined; if (!name) { const contact = await client.contacts.find({ contactId }); name = contact.name; contactCache.set(contactId, name); } return name; } // Invalidate cache via webhooks function onContactUpdated(contactId: string): void { contactCache.delete(contactId); }
Step 4: Use Search Instead of List + Filter
// BAD: Fetch all contacts, filter client-side // Cost: N pages * 1 request each let startingAfter: string | undefined; const matchingContacts = []; do { const page = await client.contacts.list({ perPage: 50, startingAfter }); matchingContacts.push(...page.data.filter(c => c.customAttributes?.plan === "pro")); startingAfter = page.pages?.next?.startingAfter; } while (startingAfter); // GOOD: Server-side search (1 request for up to 150 results) const proUsers = await client.contacts.search({ query: { operator: "AND", value: [ { field: "role", operator: "=", value: "user" }, { field: "custom_attributes.plan", operator: "=", value: "pro" }, ], }, pagination: { per_page: 150 }, });
Step 5: Batch Conversation Lookups
// BAD: N individual conversation lookups for (const id of conversationIds) { const convo = await client.conversations.find({ conversationId: id }); process(convo); } // GOOD: Search conversations with filter const conversations = await client.conversations.search({ query: { operator: "AND", value: [ { field: "state", operator: "=", value: "open" }, { field: "admin_assignee_id", operator: "=", value: adminId }, ], }, pagination: { per_page: 50 }, });
Step 6: Monitor Request Budget
class RequestBudgetMonitor { private requestsThisMinute = 0; private resetTime = Date.now() + 60000; async checkBudget(): Promise<void> { if (Date.now() > this.resetTime) { this.requestsThisMinute = 0; this.resetTime = Date.now() + 60000; } this.requestsThisMinute++; // Warn at 80% of limit if (this.requestsThisMinute > 8000) { console.warn( `[Intercom] High request rate: ${this.requestsThisMinute}/10000 per minute` ); } // Hard stop at 95% to prevent 429s if (this.requestsThisMinute > 9500) { const waitMs = this.resetTime - Date.now(); console.warn(`[Intercom] Throttling: waiting ${waitMs}ms`); await new Promise(r => setTimeout(r, waitMs)); } } }
Cost Reduction Checklist
- Replace polling loops with webhooks
- Cache contact and conversation lookups (5-10 min TTL)
- Use search instead of list + client-side filter
- Batch related lookups into single search queries
- Track API request volume per endpoint
- Set up alerts at 80% rate limit usage
- Remove unnecessary API calls in hot paths
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| Rate limited (429) | Too many requests | Implement request queuing |
| Stale cached data | TTL too long | Use webhook cache invalidation |
| High infra costs | Queue + retry infrastructure | Reduce request volume first |
| Search too slow | Complex query | Simplify filters, reduce per_page |
Resources
Next Steps
For architecture patterns, see
intercom-reference-architecture.