revops
Design and optimize systems connecting marketing, sales, and customer success into a unified revenue engine.
apollo-migration-deep-dive
Comprehensive Apollo.io migration strategies. Use when migrating from other CRMs to Apollo, consolidating data sources, or executing large-scale data migrations. Trigger with phrases like "apollo migration", "migrate to apollo", "apollo data import", "crm to apollo", "apollo migration strategy".
Full skill instructions
Comprehensive guide for migrating to Apollo.io from other CRMs and data sources, including data mapping, validation, and rollback strategies.
// scripts/migration-assessment.ts
interface MigrationAssessment {
source: {
system: string;
recordCount: number;
dataQuality: DataQualityReport;
fieldMapping: FieldMappingAnalysis;
};
target: {
apolloPlan: string;
creditBudget: number;
apiLimits: APILimits;
};
risk: {
level: 'low' | 'medium' | 'high';
factors: string[];
mitigations: string[];
};
timeline: {
estimatedDuration: string;
phases: Phase[];
};
}
async function assessMigration(sourceConfig: any): Promise<MigrationAssessment> {
// Analyze source data
const sourceAnalysis = await analyzeSourceData(sourceConfig);
// Check Apollo capacity
const apolloCapacity = await checkApolloCapacity();
// Calculate risks
const risks = calculateRisks(sourceAnalysis, apolloCapacity);
// Estimate timeline
const timeline = estimateTimeline(sourceAnalysis, apolloCapacity);
return {
source: sourceAnalysis,
target: apolloCapacity,
risk: risks,
timeline,
};
}
async function analyzeSourceData(config: any): Promise<SourceAnalysis> {
const records = await fetchSourceRecords(config);
return {
system: config.system,
recordCount: records.length,
dataQuality: {
emailValid: records.filter(r => isValidEmail(r.email)).length / records.length,
emailPresent: records.filter(r => r.email).length / records.length,
phonePresent: records.filter(r => r.phone).length / records.length,
companyPresent: records.filter(r => r.company).length / records.length,
duplicates: findDuplicates(records).length,
},
fieldMapping: analyzeFields(records),
};
}
// src/migration/field-mapper.ts
interface FieldMapping {
sourceField: string;
targetField: string;
transform?: (value: any) => any;
required: boolean;
validation?: (value: any) => boolean;
}
const SALESFORCE_TO_APOLLO: FieldMapping[] = [
{
sourceField: 'Email',
targetField: 'email',
required: true,
validation: isValidEmail,
},
{
sourceField: 'FirstName',
targetField: 'first_name',
required: false,
},
{
sourceField: 'LastName',
targetField: 'last_name',
required: false,
},
{
sourceField: 'Title',
targetField: 'title',
required: false,
transform: normalizeTitle,
},
{
sourceField: 'Phone',
targetField: 'phone',
required: false,
transform: normalizePhone,
},
{
sourceField: 'Account.Name',
targetField: 'organization_name',
required: false,
},
{
sourceField: 'Account.Website',
targetField: 'organization_domain',
transform: extractDomain,
required: false,
},
{
sourceField: 'LinkedIn_URL__c',
targetField: 'linkedin_url',
required: false,
validation: isValidLinkedInUrl,
},
];
const HUBSPOT_TO_APOLLO: FieldMapping[] = [
{ sourceField: 'properties.email', targetField: 'email', required: true },
{ sourceField: 'properties.firstname', targetField: 'first_name', required: false },
{ sourceField: 'properties.lastname', targetField: 'last_name', required: false },
{ sourceField: 'properties.jobtitle', targetField: 'title', required: false },
{ sourceField: 'properties.phone', targetField: 'phone', required: false },
{ sourceField: 'properties.company', targetField: 'organization_name', required: false },
{ sourceField: 'properties.website', targetField: 'organization_domain', transform: extractDomain, required: false },
];
function transformRecord(record: any, mappings: FieldMapping[]): any {
const transformed: any = {};
const errors: string[] = [];
for (const mapping of mappings) {
const value = getNestedValue(record, mapping.sourceField);
if (mapping.required && !value) {
errors.push(`Missing required field: ${mapping.sourceField}`);
continue;
}
if (value) {
let transformedValue = mapping.transform ? mapping.transform(value) : value;
if (mapping.validation && !mapping.validation(transformedValue)) {
errors.push(`Invalid value for ${mapping.sourceField}: ${value}`);
continue;
}
transformed[mapping.targetField] = transformedValue;
}
}
return { data: transformed, errors };
}
// src/migration/phased-migration.ts
interface MigrationPhase {
name: string;
percentage: number;
criteria: any;
validation: () => Promise<boolean>;
rollbackPlan: () => Promise<void>;
}
const MIGRATION_PHASES: MigrationPhase[] = [
{
name: 'Pilot',
percentage: 1,
criteria: { createdAt: { gt: '2024-01-01' }, hasEmail: true },
validation: async () => {
const migrated = await getMigratedCount('pilot');
const errors = await getErrorCount('pilot');
return errors / migrated < 0.01; // Less than 1% error rate
},
rollbackPlan: async () => {
await deleteApolloContacts({ tag: 'migration-pilot' });
},
},
{
name: 'Early Adopters',
percentage: 10,
criteria: { hasEmail: true, engagementScore: { gt: 50 } },
validation: async () => {
// Validate data integrity
const sample = await sampleMigratedRecords(100);
const integrity = await validateDataIntegrity(sample);
return integrity.score > 0.95;
},
rollbackPlan: async () => {
await deleteApolloContacts({ tag: 'migration-early' });
},
},
{
name: 'Main Migration',
percentage: 75,
criteria: { hasEmail: true },
validation: async () => {
// Full validation suite
return await runFullValidation();
},
rollbackPlan: async () => {
// This is risky - need careful planning
console.warn('Full rollback required - contact support');
},
},
{
name: 'Cleanup',
percentage: 14,
criteria: {}, // Remaining records
validation: async () => true,
rollbackPlan: async () => {},
},
];
async function executePhasedMigration(): Promise<void> {
for (const phase of MIGRATION_PHASES) {
console.log(`Starting phase: ${phase.name} (${phase.percentage}%)`);
// Get records for this phase
const records = await getRecordsForPhase(phase);
console.log(`Found ${records.length} records for ${phase.name}`);
// Migrate in batches
const batchSize = 100;
for (let i = 0; i < records.length; i += batchSize) {
const batch = records.slice(i, i + batchSize);
await migrateBatch(batch, phase.name);
// Progress update
console.log(`Progress: ${i + batch.length}/${records.length}`);
// Rate limit
await sleep(1000);
}
// Validate phase
const isValid = await phase.validation();
if (!isValid) {
console.error(`Phase ${phase.name} validation failed!`);
await phase.rollbackPlan();
throw new Error(`Migration failed at phase: ${phase.name}`);
}
console.log(`Phase ${phase.name} completed successfully`);
}
}
// src/migration/batch-worker.ts
import { Queue, Worker, Job } from 'bullmq';
interface MigrationJob {
records: any[];
phase: string;
batchNumber: number;
}
const migrationQueue = new Queue('apollo-migration');
// Producer
async function enqueueMigrationBatch(
records: any[],
phase: string,
batchNumber: number
): Promise<void> {
await migrationQueue.add('migrate', {
records,
phase,
batchNumber,
}, {
attempts: 3,
backoff: {
type: 'exponential',
delay: 5000,
},
removeOnComplete: 100,
removeOnFail: false,
});
}
// Consumer
const worker = new Worker('apollo-migration', async (job: Job<MigrationJob>) => {
const { records, phase, batchNumber } = job.data;
const results = {
success: 0,
failed: 0,
errors: [] as any[],
};
for (const record of records) {
try {
// Transform record
const transformed = transformRecord(record, getMapping(record.source));
if (transformed.errors.length > 0) {
results.failed++;
results.errors.push({ record, errors: transformed.errors });
continue;
}
// Create in Apollo
await apollo.createContact(transformed.data);
// Store mapping for rollback
await storeMigrationMapping(record.id, transformed.data.id, phase);
results.success++;
} catch (error: any) {
results.failed++;
results.errors.push({ record, error: error.message });
}
// Update job progress
await job.updateProgress((results.success + results.failed) / records.length * 100);
}
// Log results
console.log(`Batch ${batchNumber}: ${results.success} success, ${results.failed} failed`);
if (results.errors.length > 0) {
await storeFailedRecords(results.errors, phase, batchNumber);
}
return results;
});
// src/migration/validation.ts
interface ValidationResult {
totalSource: number;
totalTarget: number;
matched: number;
mismatched: number;
missing: number;
extra: number;
fieldDiscrepancies: FieldDiscrepancy[];
}
interface FieldDiscrepancy {
recordId: string;
field: string;
sourceValue: any;
targetValue: any;
}
async function validateMigration(): Promise<ValidationResult> {
// Get all source records
const sourceRecords = await fetchAllSourceRecords();
const sourceMap = new Map(sourceRecords.map(r => [r.email, r]));
// Get all migrated records from Apollo
const apolloRecords = await fetchAllApolloContacts();
const apolloMap = new Map(apolloRecords.map(r => [r.email, r]));
const result: ValidationResult = {
totalSource: sourceRecords.length,
totalTarget: apolloRecords.length,
matched: 0,
mismatched: 0,
missing: 0,
extra: 0,
fieldDiscrepancies: [],
};
// Check source records exist in Apollo
for (const [email, sourceRecord] of sourceMap) {
const apolloRecord = apolloMap.get(email);
if (!apolloRecord) {
result.missing++;
continue;
}
// Compare fields
const discrepancies = compareRecords(sourceRecord, apolloRecord);
if (discrepancies.length === 0) {
result.matched++;
} else {
result.mismatched++;
result.fieldDiscrepancies.push(...discrepancies);
}
}
// Check for extra records in Apollo
for (const [email] of apolloMap) {
if (!sourceMap.has(email)) {
result.extra++;
}
}
return result;
}
function compareRecords(source: any, target: any): FieldDiscrepancy[] {
const discrepancies: FieldDiscrepancy[] = [];
const fieldsToCompare = ['first_name', 'last_name', 'title', 'phone'];
for (const field of fieldsToCompare) {
const sourceValue = normalizeValue(source[field]);
const targetValue = normalizeValue(target[field]);
if (sourceValue !== targetValue) {
discrepancies.push({
recordId: source.id,
field,
sourceValue,
targetValue,
});
}
}
return discrepancies;
}
// src/migration/rollback.ts
interface RollbackPlan {
phase: string;
recordIds: string[];
timestamp: Date;
}
async function createRollbackPlan(phase: string): Promise<RollbackPlan> {
const mappings = await prisma.migrationMapping.findMany({
where: { phase },
});
return {
phase,
recordIds: mappings.map(m => m.apolloId),
timestamp: new Date(),
};
}
async function executeRollback(plan: RollbackPlan): Promise<void> {
console.log(`Rolling back ${plan.recordIds.length} records from phase: ${plan.phase}`);
// Delete from Apollo in batches
const batchSize = 50;
for (let i = 0; i < plan.recordIds.length; i += batchSize) {
const batch = plan.recordIds.slice(i, i + batchSize);
await Promise.all(
batch.map(async (id) => {
try {
// Apollo may not have delete API - mark as inactive instead
await apollo.updateContact(id, { status: 'inactive' });
} catch (error) {
console.error(`Failed to rollback contact ${id}:`, error);
}
})
);
console.log(`Rollback progress: ${i + batch.length}/${plan.recordIds.length}`);
await sleep(1000);
}
// Update migration mappings
await prisma.migrationMapping.updateMany({
where: { phase: plan.phase },
data: { rolledBack: true, rolledBackAt: new Date() },
});
console.log(`Rollback complete for phase: ${plan.phase}`);
}
// src/routes/admin/migration.ts
router.get('/migration/status', async (req, res) => {
const status = {
phases: await getMigrationPhaseStatus(),
progress: await getOverallProgress(),
errors: await getRecentErrors(50),
queue: await getQueueStatus(),
};
res.json(status);
});
router.post('/migration/pause', async (req, res) => {
await migrationQueue.pause();
res.json({ status: 'paused' });
});
router.post('/migration/resume', async (req, res) => {
await migrationQueue.resume();
res.json({ status: 'resumed' });
});
router.post('/migration/rollback/:phase', async (req, res) => {
const plan = await createRollbackPlan(req.params.phase);
await executeRollback(plan);
res.json({ status: 'rolled back', plan });
});
| Issue | Resolution |
|---|---|
| Field mapping error | Review and fix mapping |
| Batch failure | Retry with smaller batch |
| Validation mismatch | Investigate and re-migrate |
| Rollback needed | Execute phase rollback |
This completes the Apollo skill pack. All 24 skills are now available for Claude Code users integrating with Apollo.io.
Design and optimize systems connecting marketing, sales, and customer success into a unified revenue engine.
Strategic planning and optimization for email capture offers and gated content.
Directory submissions: the foundation layer of distribution that passes backlinks and discovery surface area into your marketing pages.
Sequence and choreograph multi-step animations with precise timing control and nested timelines.
Structured framework for planning product launches, feature announcements, and go-to-market strategies.
Upload files to Google Drive with automatic MIME type detection and metadata.
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.
firecrawl lead gen
firecrawl lead research
firecrawl demo walkthrough
Team leadership persona with standup coordination, meeting prep, task delegation, and team communication.
Identify large Google Drive files consuming storage quota.
Revolutionize Video Creation with AI-Generated Avatars and Voices
Social media on autopilot
Design custom logos instantly with AI.
Text-heavy marketing and design images
Boost Your SEO with Content and SEO Silos
Revolutionize Your Marketing with AI-Powered MarketingBlocks!
10X faster & free AI content creation for 100+ use cases
Tribescaler | Make Your Tweets Go Viral