Claude-code-plugins-plus-skills intercom-data-handling
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-data-handling" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-intercom-data-handling && rm -rf "$T"
manifest:
plugins/saas-packs/intercom-pack/skills/intercom-data-handling/SKILL.mdsource content
Intercom Data Handling
Overview
Handle sensitive contact data in Intercom integrations with GDPR/CCPA compliance, data export via the Data Export API, contact deletion, PII redaction in logs, and data retention policies.
Prerequisites
- Understanding of GDPR/CCPA requirements
SDK installedintercom-client- Database for audit logging
- Familiarity with Intercom's contact and conversation data model
Data Classification for Intercom
| Category | Intercom Fields | Handling |
|---|---|---|
| PII | , , , | Encrypt at rest, redact in logs |
| Identifiers | , , | Use for lookups, no display |
| Conversation content | , | May contain PII, scan before logging |
| Custom attributes | User-defined | Depends on content |
| System metadata | , , | Standard handling |
Instructions
Step 1: GDPR Data Subject Access Request (DSAR)
Export all Intercom data for a specific user.
import { IntercomClient } from "intercom-client"; const client = new IntercomClient({ token: process.env.INTERCOM_ACCESS_TOKEN!, }); async function exportContactData(contactId: string): Promise<{ contact: any; conversations: any[]; tags: any[]; segments: any[]; events: any[]; }> { // 1. Get contact profile const contact = await client.contacts.find({ contactId }); // 2. Get conversations for this contact const conversations = []; const convList = await client.conversations.search({ query: { field: "contact_ids", operator: "=", value: contactId, }, }); for (const convo of convList.conversations) { // Get full conversation with parts const full = await client.conversations.find({ conversationId: convo.id, }); conversations.push(full); } // 3. Get tags const tags = await client.contacts.listTags({ contactId }); // 4. Get segments const segments = await client.contacts.listSegments({ contactId }); // 5. Get data events const events = await client.dataEvents.list({ type: "user", userId: contact.externalId, }); return { contact: { id: contact.id, email: contact.email, name: contact.name, phone: contact.phone, role: contact.role, external_id: contact.externalId, custom_attributes: contact.customAttributes, location: contact.location, created_at: contact.createdAt, last_seen_at: contact.lastSeenAt, }, conversations, tags: tags.data || [], segments: segments.data || [], events: events.data || [], }; }
Step 2: Right to Deletion (GDPR Article 17)
async function deleteContactData(contactId: string): Promise<{ deleted: boolean; auditRecord: any; }> { // 1. Export data for audit trail BEFORE deletion const exportedData = await exportContactData(contactId); // 2. Delete from Intercom await client.contacts.delete({ contactId }); // 3. Delete from local cache/database await localDb.intercomContacts.deleteMany({ intercom_id: contactId }); await localDb.intercomCache.deleteMany({ contact_id: contactId }); // 4. Record audit entry (required by GDPR to prove deletion) const auditRecord = { action: "GDPR_DELETION", contact_id: contactId, contact_email_hash: hashEmail(exportedData.contact.email), // Hash, don't store deleted_at: new Date().toISOString(), data_sources_purged: ["intercom", "local_cache", "local_db"], conversations_affected: exportedData.conversations.length, }; await localDb.auditLog.insert(auditRecord); return { deleted: true, auditRecord }; }
Step 3: Intercom Data Export API (Bulk)
// Export all messages for a date range (bulk export) async function bulkExportMessages( startDate: string, endDate: string ): Promise<string> { // POST /export/messages/data const response = await fetch("https://api.intercom.io/export/messages/data", { method: "POST", headers: { Authorization: `Bearer ${process.env.INTERCOM_ACCESS_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ created_at_after: Math.floor(new Date(startDate).getTime() / 1000), created_at_before: Math.floor(new Date(endDate).getTime() / 1000), }), }); const data = await response.json(); // Returns: { job_identifier: "abc123", status: "pending", download_url: null } // Poll for completion return data.job_identifier; } async function checkExportStatus(jobId: string): Promise<{ status: string; downloadUrl?: string; }> { const response = await fetch( `https://api.intercom.io/export/messages/data/${jobId}`, { headers: { Authorization: `Bearer ${process.env.INTERCOM_ACCESS_TOKEN}` }, } ); const data = await response.json(); // When complete: { status: "complete", download_url: "https://..." } // Download URL provides a CSV file return { status: data.status, downloadUrl: data.download_url }; }
Step 4: PII Redaction in Logs
// Fields to always redact from log output const PII_FIELDS = new Set([ "email", "name", "phone", "location", "ip_address", "custom_attributes.address", "custom_attributes.ssn", ]); function redactIntercomData(data: Record<string, any>): Record<string, any> { const redacted = { ...data }; for (const field of PII_FIELDS) { const parts = field.split("."); let current: any = redacted; for (let i = 0; i < parts.length - 1; i++) { current = current[parts[i]]; if (!current) break; } if (current && current[parts[parts.length - 1]]) { current[parts[parts.length - 1]] = "[REDACTED]"; } } return redacted; } // Use in all logging console.log("Contact data:", redactIntercomData(contact)); // Output: { id: "abc", email: "[REDACTED]", name: "[REDACTED]", role: "user" }
Step 5: Data Retention Policy
// Retention periods for cached Intercom data const RETENTION = { contact_cache: 30, // days - cached contact profiles conversation_cache: 90, // days - cached conversations webhook_events: 30, // days - processed webhook records audit_log: 2555, // days (7 years) - compliance requirement data_export: 7, // days - export download files }; async function enforceRetention(): Promise<{ deleted: Record<string, number> }> { const results: Record<string, number> = {}; for (const [type, days] of Object.entries(RETENTION)) { if (type === "audit_log") continue; // Never auto-delete audit logs const cutoff = new Date(); cutoff.setDate(cutoff.getDate() - days); const result = await localDb.collection(type).deleteMany({ created_at: { $lt: cutoff }, }); results[type] = result.deletedCount; } return { deleted: results }; } // Schedule daily at 3 AM // cron: "0 3 * * *"
Data Minimization
// Only sync the fields you actually need from Intercom async function syncContactMinimal(contactId: string) { const contact = await client.contacts.find({ contactId }); // Store only necessary fields return { intercom_id: contact.id, external_id: contact.externalId, role: contact.role, plan: contact.customAttributes?.plan, last_seen_at: contact.lastSeenAt, // DO NOT store: email, name, phone, location }; }
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| Export job stuck in "pending" | Large dataset | Poll every 30s, timeout at 1h |
| Deletion returns 404 | Already deleted | Log and continue (idempotent) |
| PII in conversation bodies | User-submitted content | Scan with regex, redact in logs |
| Audit log gap | Failed write | Use write-ahead log or queue |
Resources
Next Steps
For enterprise access control, see
intercom-enterprise-rbac.