Implement Apollo.io reference architecture. Use when designing Apollo integrations, establishing patterns, or building production-grade sales intelligence systems. Trigger with phrases like "apollo architecture", "apollo system design", "apollo integration patterns", "apollo best practices architecture".
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
skills listSkill Instructions
name: apollo-reference-architecture description: | Implement Apollo.io reference architecture. Use when designing Apollo integrations, establishing patterns, or building production-grade sales intelligence systems. Trigger with phrases like "apollo architecture", "apollo system design", "apollo integration patterns", "apollo best practices architecture". allowed-tools: Read, Write, Edit, Bash(gh:), Bash(curl:) version: 1.0.0 license: MIT author: Jeremy Longshore jeremy@intentsolutions.io
Apollo Reference Architecture
Overview
Production-ready reference architecture for Apollo.io integrations covering system design, data flows, and integration patterns.
Architecture Diagram
+------------------+ +------------------+ +------------------+
| Frontend | | API Gateway | | Apollo API |
| (React/Vue) |---->| (Express) |---->| (External) |
+------------------+ +------------------+ +------------------+
| |
v |
+------------------+ |
| Apollo Service |<----------------+
| (Business Logic)|
+------------------+
| | |
+-------------+ | +-------------+
v v v
+------------+ +------------+ +------------+
| Cache | | Database | | Queue |
| (Redis) | | (Postgres) | | (Bull) |
+------------+ +------------+ +------------+
Project Structure
src/
├── lib/
│ └── apollo/
│ ├── client.ts # Apollo API client
│ ├── cache.ts # Caching layer
│ ├── rate-limiter.ts # Rate limiting
│ ├── errors.ts # Custom errors
│ └── types.ts # TypeScript types
├── services/
│ └── apollo/
│ ├── search.service.ts # People/org search
│ ├── enrich.service.ts # Enrichment logic
│ ├── sequence.service.ts # Email sequences
│ └── sync.service.ts # Data synchronization
├── jobs/
│ └── apollo/
│ ├── enrich.job.ts # Background enrichment
│ ├── sync.job.ts # Periodic sync
│ └── cleanup.job.ts # Cache cleanup
├── routes/
│ └── api/
│ └── apollo/
│ ├── search.ts # Search endpoints
│ ├── enrich.ts # Enrichment endpoints
│ └── webhooks.ts # Webhook handlers
├── models/
│ ├── contact.model.ts # Contact entity
│ ├── company.model.ts # Company entity
│ └── engagement.model.ts # Email engagement
└── config/
└── apollo.config.ts # Apollo configuration
Core Components
1. Apollo Service Layer
// src/services/apollo/apollo.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ApolloClient } from '../../lib/apollo/client';
import { ApolloCache } from '../../lib/apollo/cache';
import { Contact } from '../../models/contact.model';
import { Company } from '../../models/company.model';
@Injectable()
export class ApolloService {
constructor(
private readonly client: ApolloClient,
private readonly cache: ApolloCache,
@InjectRepository(Contact)
private readonly contactRepo: Repository<Contact>,
@InjectRepository(Company)
private readonly companyRepo: Repository<Company>,
) {}
async searchAndEnrich(criteria: SearchCriteria): Promise<EnrichedLead[]> {
// 1. Search Apollo
const searchResults = await this.client.searchPeople(criteria);
// 2. Filter and score
const qualified = this.qualifyLeads(searchResults.people, criteria);
// 3. Enrich top leads
const enriched = await Promise.all(
qualified.slice(0, 25).map(lead => this.enrichLead(lead))
);
// 4. Persist to database
await this.persistLeads(enriched);
return enriched;
}
private async enrichLead(lead: RawLead): Promise<EnrichedLead> {
// Check cache
const cached = await this.cache.get(`lead:${lead.id}`);
if (cached) return cached;
// Enrich from Apollo
const [personData, companyData] = await Promise.all([
this.client.enrichPerson({ id: lead.id }),
lead.organization?.primary_domain
? this.client.enrichOrganization(lead.organization.primary_domain)
: null,
]);
const enriched = this.mergeData(lead, personData, companyData);
// Cache result
await this.cache.set(`lead:${lead.id}`, enriched, 86400); // 24h
return enriched;
}
private async persistLeads(leads: EnrichedLead[]): Promise<void> {
for (const lead of leads) {
// Upsert contact
await this.contactRepo.upsert({
apolloId: lead.id,
email: lead.email,
name: lead.name,
title: lead.title,
linkedinUrl: lead.linkedinUrl,
companyId: lead.company?.id,
enrichedAt: new Date(),
}, ['apolloId']);
// Upsert company
if (lead.company) {
await this.companyRepo.upsert({
apolloId: lead.company.id,
name: lead.company.name,
domain: lead.company.domain,
industry: lead.company.industry,
employeeCount: lead.company.employeeCount,
enrichedAt: new Date(),
}, ['apolloId']);
}
}
}
}
2. Background Job Processing
// src/jobs/apollo/enrich.job.ts
import { Job, Queue } from 'bull';
import { Process, Processor } from '@nestjs/bull';
import { ApolloService } from '../../services/apollo/apollo.service';
interface EnrichJobData {
contactIds: string[];
priority: 'high' | 'normal' | 'low';
}
@Processor('apollo-enrich')
export class EnrichProcessor {
constructor(private readonly apolloService: ApolloService) {}
@Process('enrich-contacts')
async handleEnrich(job: Job<EnrichJobData>): Promise<void> {
const { contactIds, priority } = job.data;
// Process in batches to respect rate limits
const batchSize = priority === 'high' ? 10 : 5;
for (let i = 0; i < contactIds.length; i += batchSize) {
const batch = contactIds.slice(i, i + batchSize);
await Promise.all(
batch.map(id => this.apolloService.enrichContact(id))
);
// Update progress
await job.progress(((i + batchSize) / contactIds.length) * 100);
// Rate limit delay
if (i + batchSize < contactIds.length) {
await new Promise(r => setTimeout(r, 1000));
}
}
}
}
// Queue producer
@Injectable()
export class EnrichQueue {
constructor(@InjectQueue('apollo-enrich') private queue: Queue) {}
async enqueueContacts(contactIds: string[], priority: 'high' | 'normal' | 'low' = 'normal') {
await this.queue.add('enrich-contacts', {
contactIds,
priority,
}, {
priority: priority === 'high' ? 1 : priority === 'normal' ? 5 : 10,
attempts: 3,
backoff: {
type: 'exponential',
delay: 5000,
},
});
}
}
3. Data Models
// src/models/contact.model.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, Index } from 'typeorm';
import { Company } from './company.model';
@Entity('contacts')
export class Contact {
@PrimaryGeneratedColumn('uuid')
id: string;
@Index({ unique: true })
@Column()
apolloId: string;
@Index()
@Column({ nullable: true })
email: string;
@Column()
name: string;
@Column({ nullable: true })
firstName: string;
@Column({ nullable: true })
lastName: string;
@Column({ nullable: true })
title: string;
@Column({ nullable: true })
seniority: string;
@Column({ nullable: true })
linkedinUrl: string;
@Column({ nullable: true })
phone: string;
@Column({ type: 'jsonb', nullable: true })
customFields: Record<string, any>;
@ManyToOne(() => Company, company => company.contacts)
company: Company;
@Column()
companyId: string;
@Column({ type: 'timestamp' })
enrichedAt: Date;
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
updatedAt: Date;
}
// src/models/company.model.ts
@Entity('companies')
export class Company {
@PrimaryGeneratedColumn('uuid')
id: string;
@Index({ unique: true })
@Column()
apolloId: string;
@Column()
name: string;
@Index()
@Column()
domain: string;
@Column({ nullable: true })
industry: string;
@Column({ nullable: true })
subIndustry: string;
@Column({ nullable: true })
employeeCount: number;
@Column({ nullable: true })
annualRevenue: number;
@Column({ nullable: true })
foundedYear: number;
@Column({ type: 'text', nullable: true })
description: string;
@Column({ type: 'jsonb', nullable: true })
technologies: string[];
@Column({ type: 'jsonb', nullable: true })
location: {
city: string;
state: string;
country: string;
};
@OneToMany(() => Contact, contact => contact.company)
contacts: Contact[];
}
4. API Routes
// src/routes/api/apollo/search.ts
import { Router } from 'express';
import { ApolloService } from '../../../services/apollo/apollo.service';
import { validateRequest } from '../../../middleware/validation';
const router = Router();
router.post('/search', validateRequest(SearchSchema), async (req, res) => {
const { domains, titles, locations, minEmployees, maxEmployees } = req.body;
const results = await apolloService.searchAndEnrich({
domains,
titles,
locations,
minEmployees,
maxEmployees,
});
res.json({
success: true,
data: results,
meta: {
count: results.length,
timestamp: new Date().toISOString(),
},
});
});
router.post('/enrich/bulk', validateRequest(BulkEnrichSchema), async (req, res) => {
const { contactIds, priority } = req.body;
// Queue for background processing
await enrichQueue.enqueueContacts(contactIds, priority);
res.json({
success: true,
message: `Queued ${contactIds.length} contacts for enrichment`,
jobId: 'job-id-here',
});
});
export default router;
Integration Patterns
CRM Integration (Salesforce)
// src/integrations/salesforce.ts
export class SalesforceIntegration {
async syncContact(contact: Contact): Promise<void> {
const sfContact = await this.salesforce.sobject('Contact').upsert({
Email: contact.email,
FirstName: contact.firstName,
LastName: contact.lastName,
Title: contact.title,
Apollo_ID__c: contact.apolloId,
LinkedIn_URL__c: contact.linkedinUrl,
}, 'Email');
console.log(`Synced contact ${contact.email} to Salesforce`);
}
async syncCompany(company: Company): Promise<void> {
const sfAccount = await this.salesforce.sobject('Account').upsert({
Name: company.name,
Website: `https://${company.domain}`,
Industry: company.industry,
NumberOfEmployees: company.employeeCount,
Apollo_ID__c: company.apolloId,
}, 'Website');
}
}
Event-Driven Architecture
// src/events/apollo.events.ts
export const APOLLO_EVENTS = {
CONTACT_ENRICHED: 'apollo.contact.enriched',
COMPANY_ENRICHED: 'apollo.company.enriched',
SEARCH_COMPLETED: 'apollo.search.completed',
SEQUENCE_STARTED: 'apollo.sequence.started',
EMAIL_ENGAGEMENT: 'apollo.email.engagement',
};
// Event handlers
eventBus.on(APOLLO_EVENTS.CONTACT_ENRICHED, async (contact) => {
// Sync to CRM
await salesforceIntegration.syncContact(contact);
// Update search index
await searchIndex.indexContact(contact);
// Notify relevant teams
if (contact.score >= 80) {
await slackNotifier.sendHighValueLead(contact);
}
});
Output
- Layered architecture (client, service, job, model)
- Background job processing with Bull
- Database models with TypeORM
- RESTful API endpoints
- CRM integration patterns
- Event-driven architecture
Error Handling
| Layer | Strategy |
|---|---|
| Client | Retry with backoff |
| Service | Graceful degradation |
| Jobs | Dead letter queue |
| API | Structured error responses |
Resources
Next Steps
Proceed to apollo-multi-env-setup for environment configuration.
More by jeremylongshore
View allOauth Callback Handler - Auto-activating skill for API Integration. Triggers on: oauth callback handler, oauth callback handler Part of the API Integration skill category.
Rabbitmq Queue Setup - Auto-activating skill for Backend Development. Triggers on: rabbitmq queue setup, rabbitmq queue setup Part of the Backend Development skill category.
evaluating-machine-learning-models: This skill allows Claude to evaluate machine learning models using a comprehensive suite of metrics. It should be used when the user requests model performance analysis, validation, or testing. Claude can use this skill to assess model accuracy, precision, recall, F1-score, and other relevant metrics. Trigger this skill when the user mentions "evaluate model", "model performance", "testing metrics", "validation results", or requests a comprehensive "model evaluation".
building-neural-networks: This skill allows Claude to construct and configure neural network architectures using the neural-network-builder plugin. It should be used when the user requests the creation of a new neural network, modification of an existing one, or assistance with defining the layers, parameters, and training process. The skill is triggered by requests involving terms like "build a neural network," "define network architecture," "configure layers," or specific mentions of neural network types (e.g., "CNN," "RNN," "transformer").
