Enterprise Sales Automation for ChatGPT Apps: B2B Growth Guide
Enterprise sales is fundamentally different from SMB sales. Deal cycles stretch 3-12 months, involve 6-10 stakeholders, and require sophisticated nurturing. Manual sales processes break down at enterprise scale. You need automation that handles lead scoring, multi-threading, contract customization, and renewal tracking while your team focuses on relationship building.
ChatGPT apps offer a unique automation advantage: conversational AI can qualify leads, personalize outreach, manage complex sales sequences, and provide real-time insights to sales teams. This guide shows you how to build a complete enterprise sales automation system for B2B ChatGPT apps—from behavioral lead scoring to automated renewal management.
We'll cover lead scoring engines that identify your best prospects, CRM integrations that eliminate data silos, sales sequences that nurture deals through long cycles, pipeline automation that forecasts revenue accurately, and contract systems that handle custom enterprise agreements. Every example is production-ready TypeScript and Python code you can deploy today.
Whether you're selling a $50K/year ChatGPT app or building a multi-million dollar enterprise platform, this guide provides the automation infrastructure to scale your B2B sales without scaling headcount proportionally. Let's build the sales engine that closes enterprise deals while you sleep.
Lead Scoring: Identify Your Best Enterprise Prospects
Enterprise lead scoring combines behavioral signals (product usage, content consumption), firmographic data (company size, industry, tech stack), and intent signals (job changes, budget cycles, competitive evaluations) to rank prospects by likelihood to close. A sophisticated scoring model prevents your team from wasting time on tire-kickers while ensuring high-value opportunities get immediate attention.
Your ChatGPT app generates unique behavioral data: conversation depth, feature exploration, API usage patterns, and integration attempts. This signals genuine buyer intent better than traditional marketing qualified leads (MQLs). Combined with enriched firmographic data and intent monitoring, you can identify which prospects are evaluating solutions vs. casually browsing.
Here's a production-ready lead scoring engine that combines all three signal types:
// lead-scoring-engine.ts - Multi-dimensional enterprise lead scoring
import { EventEmitter } from 'events';
interface Lead {
id: string;
email: string;
company: string;
createdAt: Date;
lastActivityAt: Date;
}
interface BehavioralSignal {
eventType: 'conversation' | 'feature_use' | 'api_call' | 'integration_attempt' | 'export' | 'invite_user';
timestamp: Date;
metadata: Record<string, any>;
}
interface FirmographicData {
employeeCount: number;
industry: string;
revenue: number;
techStack: string[];
fundingStage: 'seed' | 'series_a' | 'series_b' | 'series_c' | 'ipo' | 'private';
}
interface IntentSignal {
signalType: 'job_posting' | 'tech_investment' | 'competitor_mention' | 'budget_approval' | 'exec_change';
timestamp: Date;
confidence: number;
source: string;
}
interface LeadScore {
leadId: string;
totalScore: number;
behavioralScore: number;
firmographicScore: number;
intentScore: number;
grade: 'A' | 'B' | 'C' | 'D';
recommendedAction: string;
scoringBreakdown: Record<string, number>;
lastScoredAt: Date;
}
class LeadScoringEngine extends EventEmitter {
private behavioralWeights = {
conversation: 5,
feature_use: 8,
api_call: 12,
integration_attempt: 15,
export: 10,
invite_user: 20
};
private firmographicWeights = {
employeeCount: 0.3,
revenue: 0.4,
techStack: 0.2,
fundingStage: 0.1
};
private intentWeights = {
job_posting: 10,
tech_investment: 15,
competitor_mention: 12,
budget_approval: 20,
exec_change: 8
};
async scoreLead(
lead: Lead,
behaviors: BehavioralSignal[],
firmographics: FirmographicData,
intents: IntentSignal[]
): Promise<LeadScore> {
const behavioralScore = this.calculateBehavioralScore(behaviors);
const firmographicScore = this.calculateFirmographicScore(firmographics);
const intentScore = this.calculateIntentScore(intents);
const totalScore = behavioralScore + firmographicScore + intentScore;
const grade = this.assignGrade(totalScore);
const recommendedAction = this.getRecommendedAction(grade, behaviors, intents);
const score: LeadScore = {
leadId: lead.id,
totalScore,
behavioralScore,
firmographicScore,
intentScore,
grade,
recommendedAction,
scoringBreakdown: {
conversation: this.countSignalType(behaviors, 'conversation') * this.behavioralWeights.conversation,
featureUse: this.countSignalType(behaviors, 'feature_use') * this.behavioralWeights.feature_use,
apiCall: this.countSignalType(behaviors, 'api_call') * this.behavioralWeights.api_call,
integration: this.countSignalType(behaviors, 'integration_attempt') * this.behavioralWeights.integration_attempt,
export: this.countSignalType(behaviors, 'export') * this.behavioralWeights.export,
inviteUser: this.countSignalType(behaviors, 'invite_user') * this.behavioralWeights.invite_user
},
lastScoredAt: new Date()
};
this.emit('lead_scored', score);
return score;
}
private calculateBehavioralScore(behaviors: BehavioralSignal[]): number {
let score = 0;
const recencyBonus = this.calculateRecencyBonus(behaviors);
behaviors.forEach(signal => {
const basePoints = this.behavioralWeights[signal.eventType] || 0;
score += basePoints;
});
// Apply recency multiplier (recent activity = higher intent)
score = score * (1 + recencyBonus);
// Apply frequency bonus (consistent usage = serious evaluation)
const uniqueDays = new Set(behaviors.map(b => b.timestamp.toDateString())).size;
if (uniqueDays >= 7) score *= 1.3;
else if (uniqueDays >= 3) score *= 1.15;
return Math.round(score);
}
private calculateFirmographicScore(firmographics: FirmographicData): number {
let score = 0;
// Employee count scoring (enterprise = higher score)
if (firmographics.employeeCount >= 5000) score += 50;
else if (firmographics.employeeCount >= 1000) score += 40;
else if (firmographics.employeeCount >= 500) score += 30;
else if (firmographics.employeeCount >= 100) score += 20;
else score += 10;
// Revenue scoring
if (firmographics.revenue >= 1000000000) score += 60; // $1B+
else if (firmographics.revenue >= 100000000) score += 50; // $100M+
else if (firmographics.revenue >= 10000000) score += 40; // $10M+
else score += 20;
// Tech stack alignment (modern stack = better fit)
const modernTech = ['aws', 'azure', 'gcp', 'kubernetes', 'terraform', 'react', 'node', 'python'];
const matchCount = firmographics.techStack.filter(tech =>
modernTech.some(modern => tech.toLowerCase().includes(modern))
).length;
score += matchCount * 5;
// Funding stage (well-funded = budget available)
const fundingScores = { seed: 10, series_a: 20, series_b: 30, series_c: 40, ipo: 50, private: 35 };
score += fundingScores[firmographics.fundingStage] || 0;
return Math.round(score);
}
private calculateIntentScore(intents: IntentSignal[]): number {
let score = 0;
intents.forEach(signal => {
const basePoints = this.intentWeights[signal.signalType] || 0;
const confidenceMultiplier = signal.confidence;
score += basePoints * confidenceMultiplier;
});
return Math.round(score);
}
private calculateRecencyBonus(behaviors: BehavioralSignal[]): number {
if (behaviors.length === 0) return 0;
const now = new Date();
const mostRecent = behaviors.reduce((latest, signal) =>
signal.timestamp > latest ? signal.timestamp : latest,
behaviors[0].timestamp
);
const daysSinceActivity = (now.getTime() - mostRecent.getTime()) / (1000 * 60 * 60 * 24);
if (daysSinceActivity <= 1) return 0.5; // 50% bonus
if (daysSinceActivity <= 3) return 0.3; // 30% bonus
if (daysSinceActivity <= 7) return 0.15; // 15% bonus
return 0;
}
private countSignalType(behaviors: BehavioralSignal[], type: string): number {
return behaviors.filter(b => b.eventType === type).length;
}
private assignGrade(totalScore: number): 'A' | 'B' | 'C' | 'D' {
if (totalScore >= 200) return 'A';
if (totalScore >= 125) return 'B';
if (totalScore >= 75) return 'C';
return 'D';
}
private getRecommendedAction(
grade: 'A' | 'B' | 'C' | 'D',
behaviors: BehavioralSignal[],
intents: IntentSignal[]
): string {
const hasHighIntent = intents.some(i => i.signalType === 'budget_approval' || i.signalType === 'tech_investment');
const hasIntegrationAttempt = behaviors.some(b => b.eventType === 'integration_attempt');
if (grade === 'A' && hasHighIntent) {
return 'URGENT: Assign to enterprise AE immediately. High intent + engagement.';
} else if (grade === 'A') {
return 'Assign to enterprise AE within 24 hours. High-value prospect.';
} else if (grade === 'B' && hasIntegrationAttempt) {
return 'Schedule technical demo. Prospect is evaluating integrations.';
} else if (grade === 'B') {
return 'Add to nurture sequence. Mid-funnel prospect.';
} else if (grade === 'C') {
return 'Continue automated nurture. Low priority for manual outreach.';
} else {
return 'Monitor for increased engagement. Not sales-ready.';
}
}
}
export { LeadScoringEngine, Lead, BehavioralSignal, FirmographicData, IntentSignal, LeadScore };
This scoring engine prioritizes leads who are actively evaluating (API calls, integrations), work at enterprise companies (employee count, revenue), and show buying intent (budget approvals, tech investments). A-grade leads get immediate sales attention, while D-grade leads stay in automated nurture until they show stronger signals.
Integrate this with your ChatGPT app analytics dashboard to automatically score every user, and connect to your automated email sequences to trigger personalized outreach based on score changes.
CRM Integration: Eliminate Enterprise Data Silos
Enterprise sales teams live in their CRM—Salesforce, HubSpot, or Microsoft Dynamics. If your ChatGPT app doesn't sync bidirectionally with their CRM, your product data disappears into a black hole. Sales reps won't adopt tools that require manual data entry, and executives won't trust forecasts built on incomplete data.
A proper CRM integration syncs leads, accounts, opportunities, activities, and custom fields in real-time. When a prospect upgrades their trial, creates their first API integration, or invites a teammate, that signal must flow into Salesforce immediately so reps can follow up while intent is hot. Conversely, when reps update deal stages or add notes, your app should reflect that context.
Here's a production-ready Salesforce integration with bidirectional sync:
// salesforce-integration.ts - Enterprise CRM bidirectional sync
import jsforce from 'jsforce';
import { EventEmitter } from 'events';
interface SalesforceConfig {
loginUrl: string;
username: string;
password: string;
securityToken: string;
clientId?: string;
clientSecret?: string;
}
interface LeadData {
email: string;
firstName?: string;
lastName?: string;
company: string;
title?: string;
phone?: string;
website?: string;
leadSource: string;
status: string;
customFields?: Record<string, any>;
}
interface OpportunityData {
name: string;
accountId: string;
amount: number;
closeDate: Date;
stageName: string;
probability: number;
type: string;
description?: string;
customFields?: Record<string, any>;
}
interface ActivityData {
leadId?: string;
opportunityId?: string;
subject: string;
activityType: 'email' | 'call' | 'meeting' | 'demo' | 'trial_started' | 'integration_completed';
activityDate: Date;
description: string;
}
class SalesforceIntegration extends EventEmitter {
private conn: jsforce.Connection;
private config: SalesforceConfig;
private isConnected = false;
constructor(config: SalesforceConfig) {
super();
this.config = config;
this.conn = new jsforce.Connection({
loginUrl: config.loginUrl,
version: '58.0'
});
}
async connect(): Promise<void> {
try {
await this.conn.login(
this.config.username,
this.config.password + this.config.securityToken
);
this.isConnected = true;
this.emit('connected');
console.log('✅ Connected to Salesforce');
} catch (error) {
this.emit('error', error);
throw new Error(`Salesforce connection failed: ${error.message}`);
}
}
async createLead(leadData: LeadData): Promise<string> {
this.ensureConnected();
try {
const lead = {
Email: leadData.email,
FirstName: leadData.firstName || '',
LastName: leadData.lastName || 'Unknown',
Company: leadData.company,
Title: leadData.title || '',
Phone: leadData.phone || '',
Website: leadData.website || '',
LeadSource: leadData.leadSource,
Status: leadData.status,
...this.mapCustomFields(leadData.customFields)
};
const result = await this.conn.sobject('Lead').create(lead);
if (!result.success) {
throw new Error(`Lead creation failed: ${result.errors?.join(', ')}`);
}
this.emit('lead_created', { leadId: result.id, email: leadData.email });
return result.id;
} catch (error) {
this.emit('error', error);
throw error;
}
}
async updateLead(leadId: string, updates: Partial<LeadData>): Promise<void> {
this.ensureConnected();
try {
const updateData = {
Id: leadId,
...this.mapLeadUpdates(updates)
};
const result = await this.conn.sobject('Lead').update(updateData);
if (!result.success) {
throw new Error(`Lead update failed: ${result.errors?.join(', ')}`);
}
this.emit('lead_updated', { leadId, updates });
} catch (error) {
this.emit('error', error);
throw error;
}
}
async convertLeadToOpportunity(
leadId: string,
opportunityData: OpportunityData
): Promise<{ accountId: string; contactId: string; opportunityId: string }> {
this.ensureConnected();
try {
// Salesforce lead conversion
const convertResult = await this.conn.sobject('Lead').convertLead({
leadId: leadId,
convertedStatus: 'Qualified',
doNotCreateOpportunity: false,
opportunityName: opportunityData.name
});
if (!convertResult.success) {
throw new Error(`Lead conversion failed: ${convertResult.errors?.join(', ')}`);
}
// Update opportunity with custom data
await this.updateOpportunity(convertResult.opportunityId, {
Amount: opportunityData.amount,
CloseDate: opportunityData.closeDate.toISOString().split('T')[0],
StageName: opportunityData.stageName,
Probability: opportunityData.probability,
Type: opportunityData.type,
Description: opportunityData.description || '',
...this.mapCustomFields(opportunityData.customFields)
});
this.emit('lead_converted', {
leadId,
accountId: convertResult.accountId,
contactId: convertResult.contactId,
opportunityId: convertResult.opportunityId
});
return {
accountId: convertResult.accountId,
contactId: convertResult.contactId,
opportunityId: convertResult.opportunityId
};
} catch (error) {
this.emit('error', error);
throw error;
}
}
async createOpportunity(opportunityData: OpportunityData): Promise<string> {
this.ensureConnected();
try {
const opportunity = {
Name: opportunityData.name,
AccountId: opportunityData.accountId,
Amount: opportunityData.amount,
CloseDate: opportunityData.closeDate.toISOString().split('T')[0],
StageName: opportunityData.stageName,
Probability: opportunityData.probability,
Type: opportunityData.type,
Description: opportunityData.description || '',
...this.mapCustomFields(opportunityData.customFields)
};
const result = await this.conn.sobject('Opportunity').create(opportunity);
if (!result.success) {
throw new Error(`Opportunity creation failed: ${result.errors?.join(', ')}`);
}
this.emit('opportunity_created', { opportunityId: result.id, name: opportunityData.name });
return result.id;
} catch (error) {
this.emit('error', error);
throw error;
}
}
async updateOpportunity(opportunityId: string, updates: any): Promise<void> {
this.ensureConnected();
try {
const updateData = {
Id: opportunityId,
...updates
};
const result = await this.conn.sobject('Opportunity').update(updateData);
if (!result.success) {
throw new Error(`Opportunity update failed: ${result.errors?.join(', ')}`);
}
this.emit('opportunity_updated', { opportunityId, updates });
} catch (error) {
this.emit('error', error);
throw error;
}
}
async logActivity(activity: ActivityData): Promise<string> {
this.ensureConnected();
try {
const task = {
WhoId: activity.leadId || null,
WhatId: activity.opportunityId || null,
Subject: activity.subject,
Type: this.mapActivityType(activity.activityType),
ActivityDate: activity.activityDate.toISOString().split('T')[0],
Description: activity.description,
Status: 'Completed',
Priority: 'Normal'
};
const result = await this.conn.sobject('Task').create(task);
if (!result.success) {
throw new Error(`Activity logging failed: ${result.errors?.join(', ')}`);
}
this.emit('activity_logged', { taskId: result.id, activityType: activity.activityType });
return result.id;
} catch (error) {
this.emit('error', error);
throw error;
}
}
private ensureConnected(): void {
if (!this.isConnected) {
throw new Error('Not connected to Salesforce. Call connect() first.');
}
}
private mapCustomFields(customFields?: Record<string, any>): Record<string, any> {
if (!customFields) return {};
// Map custom field names to Salesforce API names (e.g., "leadScore" -> "Lead_Score__c")
const mapped: Record<string, any> = {};
Object.keys(customFields).forEach(key => {
const apiName = `${key.charAt(0).toUpperCase() + key.slice(1)}__c`;
mapped[apiName] = customFields[key];
});
return mapped;
}
private mapLeadUpdates(updates: Partial<LeadData>): Record<string, any> {
const mapped: Record<string, any> = {};
if (updates.email) mapped.Email = updates.email;
if (updates.firstName) mapped.FirstName = updates.firstName;
if (updates.lastName) mapped.LastName = updates.lastName;
if (updates.company) mapped.Company = updates.company;
if (updates.title) mapped.Title = updates.title;
if (updates.phone) mapped.Phone = updates.phone;
if (updates.website) mapped.Website = updates.website;
if (updates.status) mapped.Status = updates.status;
return { ...mapped, ...this.mapCustomFields(updates.customFields) };
}
private mapActivityType(activityType: ActivityData['activityType']): string {
const typeMap = {
email: 'Email',
call: 'Call',
meeting: 'Meeting',
demo: 'Other',
trial_started: 'Other',
integration_completed: 'Other'
};
return typeMap[activityType] || 'Other';
}
}
export { SalesforceIntegration, SalesforceConfig, LeadData, OpportunityData, ActivityData };
This integration creates leads when users sign up, logs activities when they use key features (trial started, integration completed), converts leads to opportunities when they request pricing, and updates deal stages when contracts are signed. Your sales team sees complete product usage data without leaving Salesforce.
Connect this to your customer onboarding automation to sync onboarding milestones back to CRM, and integrate with usage-based billing systems to track expansion revenue opportunities.
Sales Sequences: Automate Multi-Touch Enterprise Nurture
Enterprise buyers need 7-13 touchpoints before they're ready to talk to sales. A single email won't close a $100K deal. You need sophisticated sequences that combine email, LinkedIn, phone, direct mail, and in-app messaging—personalized based on behavior, industry, and stage.
Sales sequences automate the repetitive parts (sending emails, scheduling follow-ups, tracking opens) while giving reps control over timing and personalization. The best sequences trigger automatically based on behavior (downloaded whitepaper → send case study email), pause when prospects engage (replied to email → remove from sequence), and resume when prospects go cold (no activity for 14 days → re-engage email).
Here's a production-ready sales sequence automation system:
// sales-sequence-automation.ts - Multi-touch enterprise nurture
import { EventEmitter } from 'events';
interface SequenceStep {
stepNumber: number;
delayDays: number;
channel: 'email' | 'linkedin' | 'phone' | 'direct_mail' | 'in_app';
action: string;
template: string;
personalizationFields: string[];
conditions?: {
mustHaveEngaged?: boolean;
mustNotHaveReplied?: boolean;
scoreMinimum?: number;
};
}
interface Sequence {
id: string;
name: string;
description: string;
targetAudience: string;
steps: SequenceStep[];
pauseConditions: {
replied: boolean;
booked_meeting: boolean;
unsubscribed: boolean;
};
active: boolean;
}
interface Enrollment {
id: string;
sequenceId: string;
leadId: string;
currentStep: number;
enrolledAt: Date;
lastStepAt?: Date;
nextStepAt?: Date;
status: 'active' | 'paused' | 'completed' | 'opted_out';
completedSteps: number[];
}
interface EmailTemplate {
id: string;
subject: string;
body: string;
variables: string[];
}
class SalesSequenceEngine extends EventEmitter {
private sequences: Map<string, Sequence> = new Map();
private enrollments: Map<string, Enrollment> = new Map();
private templates: Map<string, EmailTemplate> = new Map();
async createSequence(sequence: Sequence): Promise<string> {
this.sequences.set(sequence.id, sequence);
this.emit('sequence_created', sequence);
return sequence.id;
}
async enrollLead(sequenceId: string, leadId: string): Promise<string> {
const sequence = this.sequences.get(sequenceId);
if (!sequence) throw new Error(`Sequence ${sequenceId} not found`);
if (!sequence.active) throw new Error(`Sequence ${sequenceId} is inactive`);
const enrollmentId = `enr_${Date.now()}_${leadId}`;
const firstStep = sequence.steps[0];
const nextStepAt = new Date();
nextStepAt.setDate(nextStepAt.getDate() + firstStep.delayDays);
const enrollment: Enrollment = {
id: enrollmentId,
sequenceId,
leadId,
currentStep: 0,
enrolledAt: new Date(),
nextStepAt,
status: 'active',
completedSteps: []
};
this.enrollments.set(enrollmentId, enrollment);
this.emit('lead_enrolled', { enrollmentId, leadId, sequenceId });
return enrollmentId;
}
async executeStep(enrollmentId: string): Promise<void> {
const enrollment = this.enrollments.get(enrollmentId);
if (!enrollment) throw new Error(`Enrollment ${enrollmentId} not found`);
if (enrollment.status !== 'active') {
console.log(`Enrollment ${enrollmentId} is ${enrollment.status}, skipping execution`);
return;
}
const sequence = this.sequences.get(enrollment.sequenceId);
if (!sequence) throw new Error(`Sequence ${enrollment.sequenceId} not found`);
const step = sequence.steps[enrollment.currentStep];
if (!step) {
// Sequence completed
enrollment.status = 'completed';
this.emit('sequence_completed', { enrollmentId, leadId: enrollment.leadId });
return;
}
// Check step conditions
if (step.conditions) {
const conditionsMet = await this.checkStepConditions(step.conditions, enrollment.leadId);
if (!conditionsMet) {
console.log(`Step ${step.stepNumber} conditions not met for ${enrollment.leadId}`);
await this.skipToNextStep(enrollment, sequence);
return;
}
}
// Execute step action
await this.performAction(step, enrollment.leadId);
// Mark step as completed
enrollment.completedSteps.push(step.stepNumber);
enrollment.lastStepAt = new Date();
enrollment.currentStep++;
// Schedule next step
const nextStep = sequence.steps[enrollment.currentStep];
if (nextStep) {
const nextStepAt = new Date();
nextStepAt.setDate(nextStepAt.getDate() + nextStep.delayDays);
enrollment.nextStepAt = nextStepAt;
} else {
enrollment.status = 'completed';
}
this.emit('step_executed', { enrollmentId, stepNumber: step.stepNumber, leadId: enrollment.leadId });
}
async pauseEnrollment(enrollmentId: string, reason: string): Promise<void> {
const enrollment = this.enrollments.get(enrollmentId);
if (!enrollment) throw new Error(`Enrollment ${enrollmentId} not found`);
enrollment.status = 'paused';
this.emit('enrollment_paused', { enrollmentId, leadId: enrollment.leadId, reason });
}
async resumeEnrollment(enrollmentId: string): Promise<void> {
const enrollment = this.enrollments.get(enrollmentId);
if (!enrollment) throw new Error(`Enrollment ${enrollmentId} not found`);
if (enrollment.status !== 'paused') throw new Error('Enrollment is not paused');
enrollment.status = 'active';
// Recalculate next step time
const sequence = this.sequences.get(enrollment.sequenceId);
if (sequence) {
const currentStep = sequence.steps[enrollment.currentStep];
if (currentStep) {
const nextStepAt = new Date();
nextStepAt.setDate(nextStepAt.getDate() + currentStep.delayDays);
enrollment.nextStepAt = nextStepAt;
}
}
this.emit('enrollment_resumed', { enrollmentId, leadId: enrollment.leadId });
}
async handleLeadAction(
leadId: string,
action: 'replied' | 'booked_meeting' | 'unsubscribed' | 'engaged'
): Promise<void> {
// Find all active enrollments for this lead
const activeEnrollments = Array.from(this.enrollments.values()).filter(
e => e.leadId === leadId && e.status === 'active'
);
for (const enrollment of activeEnrollments) {
const sequence = this.sequences.get(enrollment.sequenceId);
if (!sequence) continue;
// Check if this action should pause the sequence
if (sequence.pauseConditions[action]) {
await this.pauseEnrollment(enrollment.id, `Lead ${action}`);
}
}
}
private async performAction(step: SequenceStep, leadId: string): Promise<void> {
switch (step.channel) {
case 'email':
await this.sendEmail(step.template, leadId);
break;
case 'linkedin':
await this.sendLinkedInMessage(step.action, leadId);
break;
case 'phone':
await this.createPhoneTask(step.action, leadId);
break;
case 'direct_mail':
await this.sendDirectMail(step.action, leadId);
break;
case 'in_app':
await this.showInAppMessage(step.template, leadId);
break;
}
}
private async sendEmail(templateId: string, leadId: string): Promise<void> {
const template = this.templates.get(templateId);
if (!template) throw new Error(`Template ${templateId} not found`);
// Personalize email (fetch lead data, replace variables)
const personalizedEmail = await this.personalizeEmail(template, leadId);
// Send via email service (SendGrid, Postmark, etc.)
console.log(`📧 Sending email to ${leadId}: ${personalizedEmail.subject}`);
this.emit('email_sent', { leadId, templateId, subject: personalizedEmail.subject });
}
private async sendLinkedInMessage(message: string, leadId: string): Promise<void> {
console.log(`💼 LinkedIn message to ${leadId}: ${message}`);
this.emit('linkedin_message_sent', { leadId, message });
}
private async createPhoneTask(taskDescription: string, leadId: string): Promise<void> {
console.log(`📞 Phone task for ${leadId}: ${taskDescription}`);
this.emit('phone_task_created', { leadId, taskDescription });
}
private async sendDirectMail(mailType: string, leadId: string): Promise<void> {
console.log(`📬 Direct mail to ${leadId}: ${mailType}`);
this.emit('direct_mail_sent', { leadId, mailType });
}
private async showInAppMessage(messageTemplate: string, leadId: string): Promise<void> {
console.log(`💬 In-app message for ${leadId}: ${messageTemplate}`);
this.emit('in_app_message_shown', { leadId, messageTemplate });
}
private async personalizeEmail(
template: EmailTemplate,
leadId: string
): Promise<{ subject: string; body: string }> {
// Fetch lead data and replace template variables
// Example: {{firstName}} -> "John"
return {
subject: template.subject,
body: template.body
};
}
private async checkStepConditions(
conditions: SequenceStep['conditions'],
leadId: string
): Promise<boolean> {
// Check engagement, reply status, lead score, etc.
return true;
}
private async skipToNextStep(enrollment: Enrollment, sequence: Sequence): Promise<void> {
enrollment.currentStep++;
const nextStep = sequence.steps[enrollment.currentStep];
if (nextStep) {
const nextStepAt = new Date();
nextStepAt.setDate(nextStepAt.getDate() + nextStep.delayDays);
enrollment.nextStepAt = nextStepAt;
}
}
}
export { SalesSequenceEngine, Sequence, SequenceStep, Enrollment, EmailTemplate };
This sequence engine automates multi-touch campaigns, pauses when prospects engage, and resumes when they go cold. You can create sequences for different buyer personas (technical evaluators get integration guides, executives get ROI calculators) and trigger them based on behavior or manual enrollment.
Combine this with your lead scoring engine to automatically enroll high-score leads in VIP sequences, and integrate with webhook automation systems to trigger sequences from external events.
Deal Pipeline: Automate Forecasting and Stage Management
Enterprise sales pipelines are complex: 5-8 stages, multiple approval layers, legal review, procurement, security audits. Manual pipeline management leads to inaccurate forecasts, stalled deals, and forgotten follow-ups. Automation ensures every deal progresses on schedule, forecasts reflect reality, and reps focus on high-value activities.
Your ChatGPT app can automate stage transitions based on product signals (completed security questionnaire → move to legal review), send reminders when deals stall (no activity in 7 days → nudge rep), and generate accurate forecasts based on historical win rates by stage, industry, and deal size.
Here's a production-ready deal pipeline automation system:
// deal-pipeline-manager.ts - Automated pipeline and forecasting
import { EventEmitter } from 'events';
interface Deal {
id: string;
name: string;
accountId: string;
amount: number;
stage: DealStage;
probability: number;
closeDate: Date;
createdAt: Date;
lastActivityAt: Date;
assignedTo: string;
productSignals: ProductSignal[];
metadata: Record<string, any>;
}
type DealStage =
| 'discovery'
| 'technical_evaluation'
| 'business_case'
| 'legal_review'
| 'procurement'
| 'closed_won'
| 'closed_lost';
interface ProductSignal {
signalType: 'security_questionnaire' | 'integration_test' | 'poc_completed' | 'champion_identified';
timestamp: Date;
metadata?: Record<string, any>;
}
interface StageRule {
fromStage: DealStage;
toStage: DealStage;
autoAdvance: boolean;
requiredSignals?: ProductSignal['signalType'][];
requiredApprovals?: string[];
daysSinceLastActivity?: number;
}
interface Forecast {
period: 'month' | 'quarter' | 'year';
periodStart: Date;
periodEnd: Date;
totalPipeline: number;
weightedPipeline: number;
expectedRevenue: number;
dealsByStage: Record<DealStage, number>;
confidenceLevel: 'high' | 'medium' | 'low';
generatedAt: Date;
}
class DealPipelineManager extends EventEmitter {
private deals: Map<string, Deal> = new Map();
private stageRules: StageRule[] = [];
private historicalWinRates: Map<DealStage, number> = new Map();
constructor() {
super();
this.initializeStageRules();
this.initializeHistoricalWinRates();
}
async createDeal(dealData: Omit<Deal, 'id' | 'createdAt' | 'lastActivityAt' | 'productSignals'>): Promise<string> {
const dealId = `deal_${Date.now()}`;
const deal: Deal = {
...dealData,
id: dealId,
createdAt: new Date(),
lastActivityAt: new Date(),
productSignals: []
};
this.deals.set(dealId, deal);
this.emit('deal_created', deal);
return dealId;
}
async addProductSignal(dealId: string, signal: ProductSignal): Promise<void> {
const deal = this.deals.get(dealId);
if (!deal) throw new Error(`Deal ${dealId} not found`);
deal.productSignals.push(signal);
deal.lastActivityAt = new Date();
this.emit('product_signal_added', { dealId, signal });
// Check if this signal triggers stage advancement
await this.checkStageAdvancement(deal);
}
async updateStage(dealId: string, newStage: DealStage, reason: string): Promise<void> {
const deal = this.deals.get(dealId);
if (!deal) throw new Error(`Deal ${dealId} not found`);
const oldStage = deal.stage;
deal.stage = newStage;
deal.probability = this.calculateProbability(newStage);
deal.lastActivityAt = new Date();
this.emit('stage_updated', { dealId, oldStage, newStage, reason });
// Check for stalled deals
await this.checkForStalledDeals();
}
async generateForecast(period: 'month' | 'quarter' | 'year'): Promise<Forecast> {
const now = new Date();
const periodStart = new Date(now.getFullYear(), now.getMonth(), 1);
let periodEnd: Date;
if (period === 'month') {
periodEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0);
} else if (period === 'quarter') {
const quarter = Math.floor(now.getMonth() / 3);
periodEnd = new Date(now.getFullYear(), (quarter + 1) * 3, 0);
} else {
periodEnd = new Date(now.getFullYear(), 11, 31);
}
// Get deals closing in this period
const relevantDeals = Array.from(this.deals.values()).filter(
d => d.closeDate >= periodStart && d.closeDate <= periodEnd
);
const totalPipeline = relevantDeals.reduce((sum, d) => sum + d.amount, 0);
const weightedPipeline = relevantDeals.reduce((sum, d) => sum + (d.amount * d.probability / 100), 0);
// Calculate expected revenue based on historical win rates
const expectedRevenue = relevantDeals.reduce((sum, d) => {
const historicalWinRate = this.historicalWinRates.get(d.stage) || 0;
return sum + (d.amount * historicalWinRate);
}, 0);
const dealsByStage: Record<DealStage, number> = {
discovery: 0,
technical_evaluation: 0,
business_case: 0,
legal_review: 0,
procurement: 0,
closed_won: 0,
closed_lost: 0
};
relevantDeals.forEach(d => {
dealsByStage[d.stage] += d.amount;
});
const confidenceLevel = this.calculateConfidenceLevel(relevantDeals);
const forecast: Forecast = {
period,
periodStart,
periodEnd,
totalPipeline,
weightedPipeline,
expectedRevenue,
dealsByStage,
confidenceLevel,
generatedAt: new Date()
};
this.emit('forecast_generated', forecast);
return forecast;
}
async checkForStalledDeals(): Promise<Deal[]> {
const now = new Date();
const stalledDeals: Deal[] = [];
this.deals.forEach(deal => {
if (deal.stage === 'closed_won' || deal.stage === 'closed_lost') return;
const daysSinceActivity = (now.getTime() - deal.lastActivityAt.getTime()) / (1000 * 60 * 60 * 24);
if (daysSinceActivity >= 7) {
stalledDeals.push(deal);
this.emit('deal_stalled', { dealId: deal.id, daysSinceActivity });
}
});
return stalledDeals;
}
private async checkStageAdvancement(deal: Deal): Promise<void> {
const applicableRules = this.stageRules.filter(r => r.fromStage === deal.stage && r.autoAdvance);
for (const rule of applicableRules) {
if (rule.requiredSignals) {
const hasAllSignals = rule.requiredSignals.every(signalType =>
deal.productSignals.some(s => s.signalType === signalType)
);
if (hasAllSignals) {
await this.updateStage(deal.id, rule.toStage, `Auto-advanced: All required signals present`);
return;
}
}
}
}
private calculateProbability(stage: DealStage): number {
const probabilityMap: Record<DealStage, number> = {
discovery: 10,
technical_evaluation: 25,
business_case: 50,
legal_review: 75,
procurement: 90,
closed_won: 100,
closed_lost: 0
};
return probabilityMap[stage];
}
private calculateConfidenceLevel(deals: Deal[]): 'high' | 'medium' | 'low' {
const avgDaysToClose = deals.reduce((sum, d) => {
const daysInPipeline = (new Date().getTime() - d.createdAt.getTime()) / (1000 * 60 * 60 * 24);
return sum + daysInPipeline;
}, 0) / deals.length;
if (avgDaysToClose > 90) return 'low';
if (avgDaysToClose > 60) return 'medium';
return 'high';
}
private initializeStageRules(): void {
this.stageRules = [
{
fromStage: 'discovery',
toStage: 'technical_evaluation',
autoAdvance: true,
requiredSignals: ['champion_identified']
},
{
fromStage: 'technical_evaluation',
toStage: 'business_case',
autoAdvance: true,
requiredSignals: ['integration_test', 'poc_completed']
},
{
fromStage: 'business_case',
toStage: 'legal_review',
autoAdvance: true,
requiredSignals: ['security_questionnaire']
},
{
fromStage: 'legal_review',
toStage: 'procurement',
autoAdvance: false
},
{
fromStage: 'procurement',
toStage: 'closed_won',
autoAdvance: false
}
];
}
private initializeHistoricalWinRates(): void {
// Based on historical data (replace with real analytics)
this.historicalWinRates.set('discovery', 0.08);
this.historicalWinRates.set('technical_evaluation', 0.22);
this.historicalWinRates.set('business_case', 0.45);
this.historicalWinRates.set('legal_review', 0.68);
this.historicalWinRates.set('procurement', 0.87);
this.historicalWinRates.set('closed_won', 1.0);
this.historicalWinRates.set('closed_lost', 0.0);
}
}
export { DealPipelineManager, Deal, DealStage, ProductSignal, Forecast };
This pipeline manager auto-advances deals based on product signals (completed POC → move to business case stage), identifies stalled deals (no activity in 7+ days), and generates accurate forecasts using historical win rates. Your sales team gets proactive alerts and executives get reliable revenue projections.
Integrate this with your Salesforce CRM to sync pipeline data bidirectionally, and connect to Slack notification systems to alert reps when deals need attention.
Contract Generation and Enterprise Billing
Enterprise customers demand custom contracts: non-standard terms, volume discounts, multi-year commitments, custom SLAs, data residency clauses, and indemnification. Manual contract generation is slow (2-4 weeks), error-prone (typos, incorrect pricing), and doesn't scale. Automated quote and contract generation reduces sales cycle time by 40% while eliminating errors.
Your ChatGPT app can generate custom contracts from templates, calculate volume discounts automatically, handle multi-entity billing (parent company pays for subsidiaries), and integrate with DocuSign for electronic signatures. Combined with enterprise billing (annual invoicing, PO numbers, net-30 terms), you remove friction from the buying process.
Here's a production-ready quote generation system:
// quote-generator.ts - Enterprise contract and pricing automation
import { EventEmitter } from 'events';
interface QuoteLineItem {
productName: string;
description: string;
quantity: number;
unitPrice: number;
discount: number;
total: number;
}
interface QuoteConfig {
customerId: string;
customerName: string;
contactEmail: string;
billingAddress: string;
term: 'monthly' | 'annual' | 'multi_year';
termLength: number;
lineItems: QuoteLineItem[];
volumeDiscountTier?: 'standard' | 'growth' | 'enterprise';
customTerms?: string[];
paymentTerms: 'net_15' | 'net_30' | 'net_60' | 'prepaid';
poRequired: boolean;
}
interface GeneratedQuote {
quoteId: string;
customerId: string;
lineItems: QuoteLineItem[];
subtotal: number;
volumeDiscount: number;
tax: number;
total: number;
termLength: number;
monthlyRecurring: number;
annualRecurring: number;
generatedAt: Date;
expiresAt: Date;
contractUrl?: string;
}
class QuoteGenerator extends EventEmitter {
private volumeDiscounts = {
standard: 0,
growth: 0.15,
enterprise: 0.25
};
async generateQuote(config: QuoteConfig): Promise<GeneratedQuote> {
const quoteId = `quote_${Date.now()}`;
// Calculate subtotal
const subtotal = config.lineItems.reduce((sum, item) => sum + item.total, 0);
// Apply volume discount
const volumeDiscountRate = config.volumeDiscountTier
? this.volumeDiscounts[config.volumeDiscountTier]
: 0;
const volumeDiscount = subtotal * volumeDiscountRate;
// Calculate tax (8% for example)
const taxRate = 0.08;
const tax = (subtotal - volumeDiscount) * taxRate;
// Calculate total
const total = subtotal - volumeDiscount + tax;
// Calculate recurring revenue
let monthlyRecurring = 0;
let annualRecurring = 0;
if (config.term === 'monthly') {
monthlyRecurring = total;
annualRecurring = total * 12;
} else if (config.term === 'annual') {
monthlyRecurring = total / 12;
annualRecurring = total;
} else {
monthlyRecurring = total / (config.termLength * 12);
annualRecurring = total / config.termLength;
}
const generatedAt = new Date();
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + 30);
const quote: GeneratedQuote = {
quoteId,
customerId: config.customerId,
lineItems: config.lineItems,
subtotal,
volumeDiscount,
tax,
total,
termLength: config.termLength,
monthlyRecurring,
annualRecurring,
generatedAt,
expiresAt
};
// Generate contract document
const contractUrl = await this.generateContractDocument(config, quote);
quote.contractUrl = contractUrl;
this.emit('quote_generated', quote);
return quote;
}
private async generateContractDocument(config: QuoteConfig, quote: GeneratedQuote): Promise<string> {
// Generate PDF contract with custom terms
const contractHtml = this.buildContractHtml(config, quote);
// Convert to PDF (use puppeteer, pdfkit, etc.)
const pdfUrl = `https://contracts.example.com/${quote.quoteId}.pdf`;
console.log(`📄 Contract generated: ${pdfUrl}`);
return pdfUrl;
}
private buildContractHtml(config: QuoteConfig, quote: GeneratedQuote): string {
return `
<!DOCTYPE html>
<html>
<head>
<title>Enterprise Agreement - ${config.customerName}</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
h1 { color: #333; }
.quote-details { margin: 20px 0; }
.line-items { width: 100%; border-collapse: collapse; margin: 20px 0; }
.line-items th, .line-items td { border: 1px solid #ddd; padding: 8px; text-align: left; }
.totals { margin: 20px 0; text-align: right; }
</style>
</head>
<body>
<h1>Enterprise Software Agreement</h1>
<div class="quote-details">
<p><strong>Quote ID:</strong> ${quote.quoteId}</p>
<p><strong>Customer:</strong> ${config.customerName}</p>
<p><strong>Generated:</strong> ${quote.generatedAt.toLocaleDateString()}</p>
<p><strong>Valid Until:</strong> ${quote.expiresAt.toLocaleDateString()}</p>
</div>
<table class="line-items">
<thead>
<tr>
<th>Product</th>
<th>Description</th>
<th>Quantity</th>
<th>Unit Price</th>
<th>Discount</th>
<th>Total</th>
</tr>
</thead>
<tbody>
${config.lineItems.map(item => `
<tr>
<td>${item.productName}</td>
<td>${item.description}</td>
<td>${item.quantity}</td>
<td>$${item.unitPrice.toFixed(2)}</td>
<td>${(item.discount * 100).toFixed(0)}%</td>
<td>$${item.total.toFixed(2)}</td>
</tr>
`).join('')}
</tbody>
</table>
<div class="totals">
<p><strong>Subtotal:</strong> $${quote.subtotal.toFixed(2)}</p>
<p><strong>Volume Discount:</strong> -$${quote.volumeDiscount.toFixed(2)}</p>
<p><strong>Tax:</strong> $${quote.tax.toFixed(2)}</p>
<p><strong>Total:</strong> $${quote.total.toFixed(2)}</p>
<p><strong>Payment Terms:</strong> ${config.paymentTerms.replace('_', ' ').toUpperCase()}</p>
</div>
${config.customTerms ? `
<div class="custom-terms">
<h2>Custom Terms</h2>
<ul>
${config.customTerms.map(term => `<li>${term}</li>`).join('')}
</ul>
</div>
` : ''}
</body>
</html>
`;
}
}
export { QuoteGenerator, QuoteConfig, QuoteLineItem, GeneratedQuote };
This quote generator calculates volume discounts, applies tax, generates custom contracts with specific terms, and outputs professional PDFs ready for DocuSign. Enterprise buyers can review and sign contracts in minutes instead of weeks.
Connect this to your Stripe billing integration for automated invoicing, and integrate with contract lifecycle management systems to track renewals and expansion opportunities.
Conclusion: Build Your Enterprise Sales Engine
Enterprise sales automation isn't about replacing sales teams—it's about amplifying their effectiveness. Lead scoring ensures reps focus on high-intent prospects. CRM integration eliminates data silos and manual entry. Sales sequences nurture long buying cycles automatically. Pipeline automation provides accurate forecasts and prevents stalled deals. Contract generation removes friction from the buying process.
Your ChatGPT app generates unique behavioral signals that traditional marketing automation misses. A prospect who completes a technical integration has 10x higher intent than someone who downloaded a whitepaper. Use these signals to trigger the right automation at the right time.
Start with lead scoring and CRM integration—these provide immediate ROI by routing hot leads to sales faster. Then layer on sales sequences to automate multi-touch nurture. Finally, add pipeline automation and contract generation to scale your enterprise sales motion without scaling headcount proportionally.
Ready to automate your enterprise sales process? Build your ChatGPT app with MakeAIHQ and deploy production-ready sales automation in 48 hours. No code required—just connect your CRM, configure your sequences, and start closing enterprise deals faster.
Related Resources
- ChatGPT App Builder: Complete No-Code Platform Guide
- Customer Onboarding Automation for ChatGPT Apps
- Usage-Based Billing for ChatGPT Apps
- ChatGPT App Analytics Dashboard
- Email Marketing Automation for ChatGPT Apps
- Webhook Automation for ChatGPT Apps
- Slack Integration for ChatGPT Apps
- Stripe Billing Integration for ChatGPT Apps