Skip to content
Contact Management logo

Contact Management

Organizing and tracking interactions with customers and prospects including data models, segmentation, enrichment, GDPR compliance, and contact lifecycle management.

SKILL.md

Full skill instructions

Contact Management

Current Level: Intermediate
Domain: CRM / Sales


Overview

Contact management organizes and tracks interactions with customers and prospects. This guide covers data models, segmentation, enrichment, and GDPR compliance for managing customer relationships effectively.

Contact Data Model

-- contacts table (extended)
CREATE TABLE contacts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  
  -- Basic info
  first_name VARCHAR(100) NOT NULL,
  last_name VARCHAR(100) NOT NULL,
  email VARCHAR(255) UNIQUE NOT NULL,
  phone VARCHAR(50),
  mobile VARCHAR(50),
  
  -- Professional info
  title VARCHAR(100),
  department VARCHAR(100),
  company_id UUID REFERENCES companies(id),
  
  -- Address
  address_line1 VARCHAR(255),
  address_line2 VARCHAR(255),
  city VARCHAR(100),
  state VARCHAR(100),
  postal_code VARCHAR(20),
  country VARCHAR(100),
  
  -- CRM fields
  lifecycle_stage VARCHAR(50),
  lead_status VARCHAR(50),
  lead_source VARCHAR(50),
  owner_id UUID REFERENCES users(id),
  
  -- Social
  linkedin_url VARCHAR(255),
  twitter_handle VARCHAR(100),
  
  -- Engagement
  last_contacted_at TIMESTAMP,
  last_activity_at TIMESTAMP,
  email_opt_in BOOLEAN DEFAULT TRUE,
  
  -- GDPR
  consent_given BOOLEAN DEFAULT FALSE,
  consent_date TIMESTAMP,
  data_processing_consent BOOLEAN DEFAULT FALSE,
  
  -- Custom fields
  custom_fields JSONB,
  
  -- Metadata
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW(),
  deleted_at TIMESTAMP,
  
  INDEX idx_email (email),
  INDEX idx_company (company_id),
  INDEX idx_owner (owner_id),
  INDEX idx_lifecycle (lifecycle_stage),
  FULLTEXT idx_search (first_name, last_name, email)
);

-- contact_tags table
CREATE TABLE contact_tags (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name VARCHAR(100) UNIQUE NOT NULL,
  color VARCHAR(7),
  
  created_at TIMESTAMP DEFAULT NOW()
);

-- contact_tag_relations table
CREATE TABLE contact_tag_relations (
  contact_id UUID REFERENCES contacts(id) ON DELETE CASCADE,
  tag_id UUID REFERENCES contact_tags(id) ON DELETE CASCADE,
  
  created_at TIMESTAMP DEFAULT NOW(),
  
  PRIMARY KEY (contact_id, tag_id)
);

-- contact_lists table
CREATE TABLE contact_lists (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  
  name VARCHAR(255) NOT NULL,
  description TEXT,
  type VARCHAR(50) DEFAULT 'static',
  
  filters JSONB,
  
  created_by UUID REFERENCES users(id),
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

-- contact_list_members table
CREATE TABLE contact_list_members (
  list_id UUID REFERENCES contact_lists(id) ON DELETE CASCADE,
  contact_id UUID REFERENCES contacts(id) ON DELETE CASCADE,
  
  added_at TIMESTAMP DEFAULT NOW(),
  
  PRIMARY KEY (list_id, contact_id)
);

Contact Segmentation

// services/​contact-segmentation.service.ts
export class ContactSegmentationService {
  async createStaticList(name: string, contactIds: string[]): Promise<ContactList> {
    const list = await db.contactList.create({
      data: {
        name,
        type: 'static'
      }
    });

    // Add contacts to list
    await db.contactListMember.createMany({
      data: contactIds.map(contactId => ({
        listId: list.id,
        contactId
      }))
    });

    return list;
  }

  async createDynamicList(name: string, filters: ListFilter[]): Promise<ContactList> {
    const list = await db.contactList.create({
      data: {
        name,
        type: 'dynamic',
        filters: JSON.stringify(filters)
      }
    });

    // Populate list based on filters
    await this.refreshDynamicList(list.id);

    return list;
  }

  async refreshDynamicList(listId: string): Promise<void> {
    const list = await db.contactList.findUnique({
      where: { id: listId }
    });

    if (!list || list.type !== 'dynamic') {
      throw new Error('Not a dynamic list');
    }

    const filters = JSON.parse(list.filters as string) as ListFilter[];
    const contacts = await this.getContactsByFilters(filters);

    // Clear existing members
    await db.contactListMember.deleteMany({
      where: { listId }
    });

    // Add new members
    await db.contactListMember.createMany({
      data: contacts.map(contact => ({
        listId,
        contactId: contact.id
      }))
    });
  }

  private async getContactsByFilters(filters: ListFilter[]): Promise<Contact[]> {
    const where: any = {};

    filters.forEach(filter => {
      switch (filter.operator) {
        case 'equals':
          where[filter.field] = filter.value;
          break;
        case 'contains':
          where[filter.field] = { contains: filter.value };
          break;
        case 'greater_than':
          where[filter.field] = { gt: filter.value };
          break;
        case 'less_than':
          where[filter.field] = { lt: filter.value };
          break;
      }
    });

    return db.contact.findMany({ where });
  }
}

interface ListFilter {
  field: string;
  operator: 'equals' | 'contains' | 'greater_than' | 'less_than';
  value: any;
}

Contact Enrichment

// services/​contact-enrichment.service.ts
import axios from 'axios';

export class ContactEnrichmentService {
  async enrichContact(contactId: string): Promise<Contact> {
    const contact = await db.contact.findUnique({
      where: { id: contactId }
    });

    if (!contact) throw new Error('Contact not found');

    // Enrich with Clearbit
    const enrichedData = await this.enrichWithClearbit(contact.email);

    // Update contact
    return db.contact.update({
      where: { id: contactId },
      data: {
        title: enrichedData.title || contact.title,
        linkedinUrl: enrichedData.linkedin || contact.linkedinUrl,
        twitterHandle: enrichedData.twitter || contact.twitterHandle,
        customFields: {
          ...contact.customFields,
          ...enrichedData.customFields
        }
      }
    });
  }

  private async enrichWithClearbit(email: string): Promise<EnrichedData> {
    try {
      const response = await axios.get(
        `https://person.clearbit.com/​v2/​combined/​find?email=${email}`,
        {
          headers: {
            'Authorization': `Bearer ${process.env.CLEARBIT_API_KEY}`
          }
        }
      );

      const person = response.data.person;
      const company = response.data.company;

      return {
        title: person.employment?.title,
        linkedin: person.linkedin?.handle,
        twitter: person.twitter?.handle,
        customFields: {
          clearbit_id: person.id,
          company_name: company?.name,
          company_domain: company?.domain,
          company_industry: company?.category?.industry
        }
      };
    } catch (error) {
      console.error('Clearbit enrichment failed:', error);
      return {};
    }
  }
}

interface EnrichedData {
  title?: string;
  linkedin?: string;
  twitter?: string;
  customFields?: Record<string, any>;
}

Duplicate Management

// services/​contact-duplicate.service.ts
export class ContactDuplicateService {
  async findDuplicates(contactId: string): Promise<Contact[]> {
    const contact = await db.contact.findUnique({
      where: { id: contactId }
    });

    if (!contact) return [];

    // Find by email
    const emailDuplicates = await db.contact.findMany({
      where: {
        email: contact.email,
        id: { not: contactId }
      }
    });

    // Find by name + company
    const nameDuplicates = await db.contact.findMany({
      where: {
        firstName: contact.firstName,
        lastName: contact.lastName,
        companyId: contact.companyId,
        id: { not: contactId }
      }
    });

    // Combine and deduplicate
    const allDuplicates = [...emailDuplicates, ...nameDuplicates];
    return Array.from(new Map(allDuplicates.map(c => [c.id, c])).values());
  }

  async mergeContacts(primaryId: string, duplicateIds: string[]): Promise<Contact> {
    const primary = await db.contact.findUnique({
      where: { id: primaryId }
    });

    if (!primary) throw new Error('Primary contact not found');

    const duplicates = await db.contact.findMany({
      where: { id: { in: duplicateIds } }
    });

    // Merge data (prefer non-null values)
    const merged: any = { ...primary };

    duplicates.forEach(duplicate => {
      Object.keys(duplicate).forEach(key => {
        if (duplicate[key] && !merged[key]) {
          merged[key] = duplicate[key];
        }
      });
    });

    // Update primary contact
    const updated = await db.contact.update({
      where: { id: primaryId },
      data: merged
    });

    // Transfer relationships
    await this.transferRelationships(duplicateIds, primaryId);

    // Soft delete duplicates
    await db.contact.updateMany({
      where: { id: { in: duplicateIds } },
      data: { deletedAt: new Date() }
    });

    return updated;
  }

  private async transferRelationships(fromIds: string[], toId: string): Promise<void> {
    await Promise.all([
      // Transfer activities
      db.activity.updateMany({
        where: { contactId: { in: fromIds } },
        data: { contactId: toId }
      }),
      // Transfer tasks
      db.task.updateMany({
        where: { contactId: { in: fromIds } },
        data: { contactId: toId }
      }),
      // Transfer deals
      db.deal.updateMany({
        where: { contactId: { in: fromIds } },
        data: { contactId: toId }
      })
    ]);
  }
}

Bulk Operations

// services/​contact-bulk.service.ts
export class ContactBulkService {
  async bulkImport(contacts: ImportContactDto[]): Promise<BulkImportResult> {
    const results: BulkImportResult = {
      success: 0,
      failed: 0,
      errors: []
    };

    for (const contactData of contacts) {
      try {
        // Validate
        this.validateContact(contactData);

        // Check for duplicates
        const existing = await db.contact.findUnique({
          where: { email: contactData.email }
        });

        if (existing) {
          // Update existing
          await db.contact.update({
            where: { id: existing.id },
            data: contactData
          });
        } else {
          // Create new
          await db.contact.create({
            data: contactData
          });
        }

        results.success++;
      } catch (error) {
        results.failed++;
        results.errors.push({
          email: contactData.email,
          error: error.message
        });
      }
    }

    return results;
  }

  async bulkExport(filters?: any): Promise<Contact[]> {
    return db.contact.findMany({
      where: filters,
      include: {
        company: true,
        tags: true
      }
    });
  }

  async bulkUpdate(contactIds: string[], updates: Partial<Contact>): Promise<number> {
    const result = await db.contact.updateMany({
      where: { id: { in: contactIds } },
      data: updates
    });

    return result.count;
  }

  async bulkDelete(contactIds: string[]): Promise<number> {
    const result = await db.contact.updateMany({
      where: { id: { in: contactIds } },
      data: { deletedAt: new Date() }
    });

    return result.count;
  }

  private validateContact(contact: ImportContactDto): void {
    if (!contact.email) {
      throw new Error('Email is required');
    }

    if (!this.isValidEmail(contact.email)) {
      throw new Error('Invalid email format');
    }
  }

  private isValidEmail(email: string): boolean {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }
}

interface ImportContactDto {
  firstName: string;
  lastName: string;
  email: string;
  phone?: string;
  company?: string;
}

interface BulkImportResult {
  success: number;
  failed: number;
  errors: Array<{ email: string; error: string }>;
}

GDPR Compliance

// services/​contact-gdpr.service.ts
export class ContactGDPRService {
  async recordConsent(contactId: string, consentType: string): Promise<void> {
    await db.contact.update({
      where: { id: contactId },
      data: {
        consentGiven: true,
        consentDate: new Date(),
        dataProcessingConsent: consentType === 'full'
      }
    });

    // Log consent
    await db.consentLog.create({
      data: {
        contactId,
        consentType,
        timestamp: new Date()
      }
    });
  }

  async exportContactData(contactId: string): Promise<ContactDataExport> {
    const contact = await db.contact.findUnique({
      where: { id: contactId },
      include: {
        activities: true,
        tasks: true,
        deals: true
      }
    });

    if (!contact) throw new Error('Contact not found');

    return {
      personalData: contact,
      activities: contact.activities,
      tasks: contact.tasks,
      deals: contact.deals
    };
  }

  async deleteContactData(contactId: string): Promise<void> {
    // Anonymize instead of delete for audit trail
    await db.contact.update({
      where: { id: contactId },
      data: {
        firstName: 'DELETED',
        lastName: 'DELETED',
        email: `deleted_${contactId}@deleted.com`,
        phone: null,
        mobile: null,
        deletedAt: new Date()
      }
    });
  }
}

interface ContactDataExport {
  personalData: Contact;
  activities: Activity[];
  tasks: Task[];
  deals: Deal[];
}

Best Practices

  1. Data Quality - Validate and clean data
  2. Deduplication - Prevent and merge duplicates
  3. Enrichment - Enrich contacts automatically
  4. Segmentation - Create targeted segments
  5. GDPR - Comply with data regulations
  6. Bulk Operations - Support import/​export
  7. Search - Implement full-text search
  8. Tags - Use tags for organization
  9. Lists - Support static and dynamic lists
  10. Audit Trail - Track all changes

Quick Start

Contact Model

interface Contact {
  id: string
  firstName: string
  lastName: string
  email: string
  phone?: string
  company?: string
  tags: string[]
  customFields: Record<string, any>
  createdAt: Date
  updatedAt: Date
}

async function createContact(contact: Contact) {
  // Check for duplicates
  const existing = await findDuplicateContact(contact.email)
  if (existing) {
    return await mergeContacts(existing.id, contact)
  }
  
  return await db.contacts.create({ data: contact })
}

Contact Segmentation

async function segmentContacts(): Promise<ContactSegment[]> {
  const contacts = await db.contacts.findMany()
  
  return {
    highValue: contacts.filter(c => c.totalRevenue > 10000),
    active: contacts.filter(c => c.lastContactDate > subDays(new Date(), 30)),
    inactive: contacts.filter(c => c.lastContactDate < subDays(new Date(), 90))
  }
}

Production Checklist

  • Data Model: Flexible contact data model
  • Deduplication: Prevent and merge duplicates
  • Enrichment: Auto-enrich contact data
  • Segmentation: Contact segmentation
  • Custom Fields: Support custom fields
  • GDPR Compliance: GDPR compliance
  • Import/​Export: Bulk import/​export
  • Search: Full-text search
  • Integration: Integrate with CRM
  • Documentation: Document contact structure
  • Validation: Validate contact data
  • Privacy: Respect privacy settings

Anti-patterns

❌ Don't: No Deduplication

// ❌ Bad - Allow duplicates
await db.contacts.create({ data: contact })
// Same email multiple times!
// ✅ Good - Check duplicates
const existing = await findDuplicateContact(contact.email)
if (existing) {
  await mergeContacts(existing.id, contact)
} else {
  await db.contacts.create({ data: contact })
}

❌ Don't: Ignore GDPR

// ❌ Bad - No GDPR compliance
await db.contacts.create({ data: contact })
// No consent tracking!
// ✅ Good - GDPR compliant
if (contact.consentGiven) {
  await db.contacts.create({ data: contact })
  await trackConsent(contact.id, 'data_processing')
}

Integration Points

  • Lead Management (32-crm-integration/​lead-management/) - Lead to contact
  • Salesforce Integration (32-crm-integration/​salesforce-integration/) - CRM sync
  • Marketing Automation (28-marketing-integration/​marketing-automation/) - Marketing

Further Reading

Resources

More Sales & marketing skills

revops logo
Sales & marketing

revops

Design and optimize systems connecting marketing, sales, and customer success into a unified revenue engine.

53.3K 104.9K
View
lead-magnets logo
Sales & marketing

lead-magnets

Strategic planning and optimization for email capture offers and gated content.

53.3K 103.4K
View
directory-submissions logo
Sales & marketing

directory-submissions

Directory submissions: the foundation layer of distribution that passes backlinks and discovery surface area into your marketing pages.

53.3K 76.3K
View
gsap-timeline logo
Sales & marketing

gsap-timeline

Sequence and choreograph multi-step animations with precise timing control and nested timelines.

15.9K 58.4K
View
launch-strategy logo
Sales & marketing

launch-strategy

Structured framework for planning product launches, feature announcements, and go-to-market strategies.

53.3K 56.8K
View
gws-drive-upload logo
Sales & marketing

gws-drive-upload

Upload files to Google Drive with automatic MIME type detection and metadata.

31.2K 47.2K
View
ci-cd-and-automation logo
Sales & marketing

ci-cd-and-automation

Automates CI/CD pipeline setup. Use when setting up or modifying build and deployment pipelines. Use when you need to automate quality gates, configure test runners in CI, or establish deployment strategies.

101.4K 43.2K
View
persona-team-lead logo
Sales & marketing

persona-team-lead

Team leadership persona with standup coordination, meeting prep, task delegation, and team communication.

31.2K 29.3K
View
recipe-find-large-files logo
Sales & marketing

recipe-find-large-files

Identify large Google Drive files consuming storage quota.

31.2K 29.2K
View

Sales & marketing AI tools

KreadoAI logo
Sales & marketing

KreadoAI

Revolutionize Video Creation with AI-Generated Avatars and Voices

Paid
View
Followr logo
Sales & marketing

Followr

Social media on autopilot

Paid
View
Logodiffusion logo
Sales & marketing

Logodiffusion

Design custom logos instantly with AI.

Paid
View
I
Sales & marketing

Ideogram

Text-heavy marketing and design images

Freemium
View
Prompt Silo logo
Sales & marketing

Prompt Silo

Boost Your SEO with Content and SEO Silos

Free
View
Marketing Blocks logo
Sales & marketing

Marketing Blocks

Revolutionize Your Marketing with AI-Powered MarketingBlocks!

Paid
View
Scrip AI logo
Sales & marketing

Scrip AI

10X faster & free AI content creation for 100+ use cases

Free
View
Tribescaler logo
Sales & marketing

Tribescaler

Tribescaler | Make Your Tweets Go Viral

Paid
View