Files
Portfolio/blog-posts/89-customer-success-automation.md
damjan_savicandClaude Opus 4.5 e1bbe5455d Initial commit: Portfolio Website
Vollständige Next.js 15 Portfolio-Website mit:
- Blog-System mit 100+ Artikeln
- Supabase-Integration
- Responsive Design mit Tailwind CSS
- TypeScript-Konfiguration
- Testing-Setup mit Vitest und Playwright

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 15:07:20 +01:00

785 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Customer Success Automation
**Meta-Description:** Customer Success automatisieren. Health Scores, Lifecycle Emails, Churn Prediction und Proactive Outreach für SaaS.
**Keywords:** Customer Success, Health Score, Churn Prediction, Lifecycle Marketing, Customer Retention, Automation, NPS
---
## Einführung
**Customer Success** ist der Schlüssel zu Retention und Expansion. Mit **Automation** können Health Scores berechnet, Risiko-Kunden identifiziert und proaktive Maßnahmen ergriffen werden skalierbar für tausende Kunden.
---
## Customer Success Overview
```
┌─────────────────────────────────────────────────────────────┐
│ CUSTOMER SUCCESS AUTOMATION │
├─────────────────────────────────────────────────────────────┤
│ │
│ Customer Health Score: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Score Components (0-100): │ │
│ │ ├── Product Usage (40%) │ │
│ │ │ ├── Login frequency │ │
│ │ │ ├── Feature adoption │ │
│ │ │ └── Active users/seats │ │
│ │ ├── Engagement (30%) │ │
│ │ │ ├── Support tickets │ │
│ │ │ ├── NPS score │ │
│ │ │ └── Training completion │ │
│ │ └── Business Value (30%) │ │
│ │ ├── ROI achieved │ │
│ │ ├── Expansion potential │ │
│ │ └── Contract value │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Lifecycle Stages: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Onboarding → Adoption → Growth → Renewal │ │
│ │ │ │ │ │ │ │
│ │ ↓ ↓ ↓ ↓ │ │
│ │ Welcome Training Upsell Review │ │
│ │ Emails Content Offers Campaign │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Risk Triggers: │
│ ├── Login decrease > 50% │
│ ├── Support tickets spike │
│ ├── Key user departure │
│ ├── Contract renewal in 60 days │
│ └── Low NPS score (< 7) │
│ │
│ Automated Actions: │
│ ├── Email sequences │
│ ├── In-app messages │
│ ├── CSM alerts │
│ ├── Scheduled calls │
│ └── Personalized offers │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## Health Score Calculation
```typescript
// lib/customer-success/health-score.ts
import { db } from '@/lib/db';
import { subDays, differenceInDays } from 'date-fns';
interface HealthScoreWeights {
usage: number;
engagement: number;
business: number;
}
interface HealthScoreComponents {
usage: {
loginScore: number;
featureAdoptionScore: number;
activeUsersScore: number;
};
engagement: {
supportScore: number;
npsScore: number;
trainingScore: number;
};
business: {
contractValueScore: number;
expansionScore: number;
tenureScore: number;
};
}
interface HealthScore {
score: number;
trend: 'up' | 'down' | 'stable';
riskLevel: 'healthy' | 'attention' | 'at-risk' | 'critical';
components: HealthScoreComponents;
lastUpdated: Date;
}
const DEFAULT_WEIGHTS: HealthScoreWeights = {
usage: 0.4,
engagement: 0.3,
business: 0.3
};
export async function calculateHealthScore(
customerId: string,
weights: HealthScoreWeights = DEFAULT_WEIGHTS
): Promise<HealthScore> {
const now = new Date();
const thirtyDaysAgo = subDays(now, 30);
// Get customer data
const customer = await db.customer.findUnique({
where: { id: customerId },
include: {
subscription: true,
users: true,
supportTickets: {
where: { createdAt: { gte: thirtyDaysAgo } }
},
npsResponses: {
orderBy: { createdAt: 'desc' },
take: 1
}
}
});
if (!customer) {
throw new Error('Customer not found');
}
// Calculate usage scores
const loginScore = await calculateLoginScore(customerId, thirtyDaysAgo);
const featureAdoptionScore = await calculateFeatureAdoptionScore(customerId);
const activeUsersScore = calculateActiveUsersScore(customer);
const usageScore = (loginScore + featureAdoptionScore + activeUsersScore) / 3;
// Calculate engagement scores
const supportScore = calculateSupportScore(customer.supportTickets);
const npsScore = calculateNPSHealthScore(customer.npsResponses[0]?.score);
const trainingScore = await calculateTrainingScore(customerId);
const engagementScore = (supportScore + npsScore + trainingScore) / 3;
// Calculate business scores
const contractValueScore = calculateContractValueScore(customer.subscription);
const expansionScore = await calculateExpansionScore(customerId);
const tenureScore = calculateTenureScore(customer.createdAt);
const businessScore = (contractValueScore + expansionScore + tenureScore) / 3;
// Weighted total
const totalScore = Math.round(
usageScore * weights.usage +
engagementScore * weights.engagement +
businessScore * weights.business
);
// Calculate trend
const previousScore = await getPreviousHealthScore(customerId);
const trend = getTrend(totalScore, previousScore);
// Determine risk level
const riskLevel = getRiskLevel(totalScore);
const healthScore: HealthScore = {
score: totalScore,
trend,
riskLevel,
components: {
usage: { loginScore, featureAdoptionScore, activeUsersScore },
engagement: { supportScore, npsScore, trainingScore },
business: { contractValueScore, expansionScore, tenureScore }
},
lastUpdated: now
};
// Store for history
await db.healthScoreHistory.create({
data: {
customerId,
score: totalScore,
components: healthScore.components,
calculatedAt: now
}
});
return healthScore;
}
async function calculateLoginScore(
customerId: string,
since: Date
): Promise<number> {
const logins = await db.userActivity.count({
where: {
user: { customerId },
activityType: 'login',
createdAt: { gte: since }
}
});
// Expected: at least 20 logins per month for healthy usage
return Math.min(100, (logins / 20) * 100);
}
async function calculateFeatureAdoptionScore(
customerId: string
): Promise<number> {
const totalFeatures = await db.feature.count({
where: { isCore: true }
});
const usedFeatures = await db.featureUsage.groupBy({
by: ['featureId'],
where: {
user: { customerId }
}
});
return Math.round((usedFeatures.length / totalFeatures) * 100);
}
function calculateActiveUsersScore(customer: any): number {
const totalSeats = customer.subscription?.seats || 1;
const activeUsers = customer.users.filter((u: any) =>
u.lastActiveAt && differenceInDays(new Date(), u.lastActiveAt) < 7
).length;
return Math.round((activeUsers / totalSeats) * 100);
}
function calculateSupportScore(tickets: any[]): number {
if (tickets.length === 0) return 100;
// Fewer tickets = higher score, but some engagement is good
const ticketCount = tickets.length;
if (ticketCount <= 2) return 100;
if (ticketCount <= 5) return 80;
if (ticketCount <= 10) return 60;
return 40;
}
function calculateNPSHealthScore(npsScore?: number): number {
if (npsScore === undefined) return 50; // Neutral if no data
if (npsScore >= 9) return 100;
if (npsScore >= 7) return 75;
if (npsScore >= 5) return 50;
return 25;
}
function getRiskLevel(score: number): HealthScore['riskLevel'] {
if (score >= 80) return 'healthy';
if (score >= 60) return 'attention';
if (score >= 40) return 'at-risk';
return 'critical';
}
function getTrend(current: number, previous?: number): HealthScore['trend'] {
if (!previous) return 'stable';
const diff = current - previous;
if (diff > 5) return 'up';
if (diff < -5) return 'down';
return 'stable';
}
```
---
## Lifecycle Email Automation
```typescript
// lib/customer-success/lifecycle.ts
import { db } from '@/lib/db';
import { addDays, differenceInDays } from 'date-fns';
interface LifecycleStage {
id: string;
name: string;
triggers: TriggerCondition[];
actions: Action[];
}
interface TriggerCondition {
type: 'days_since_signup' | 'event' | 'health_score' | 'subscription';
value: any;
}
interface Action {
type: 'email' | 'in_app' | 'slack_notify' | 'task';
template: string;
delay?: number;
}
const lifecycleStages: LifecycleStage[] = [
{
id: 'onboarding',
name: 'Onboarding',
triggers: [{ type: 'days_since_signup', value: { min: 0, max: 14 } }],
actions: [
{ type: 'email', template: 'welcome', delay: 0 },
{ type: 'email', template: 'getting-started', delay: 1 },
{ type: 'email', template: 'first-project', delay: 3 },
{ type: 'email', template: 'invite-team', delay: 7 },
{ type: 'email', template: 'onboarding-check-in', delay: 14 }
]
},
{
id: 'adoption',
name: 'Adoption',
triggers: [
{ type: 'days_since_signup', value: { min: 14, max: 60 } },
{ type: 'event', value: 'onboarding_completed' }
],
actions: [
{ type: 'email', template: 'feature-spotlight-1', delay: 0 },
{ type: 'email', template: 'success-story', delay: 7 },
{ type: 'email', template: 'feature-spotlight-2', delay: 14 },
{ type: 'email', template: 'tips-tricks', delay: 21 }
]
},
{
id: 'growth',
name: 'Growth',
triggers: [
{ type: 'days_since_signup', value: { min: 60 } },
{ type: 'health_score', value: { min: 70 } }
],
actions: [
{ type: 'email', template: 'upgrade-benefits', delay: 0 },
{ type: 'email', template: 'case-study', delay: 14 },
{ type: 'in_app', template: 'upgrade-prompt', delay: 7 }
]
},
{
id: 'renewal',
name: 'Renewal',
triggers: [
{ type: 'subscription', value: 'renewal_in_60_days' }
],
actions: [
{ type: 'email', template: 'renewal-reminder-60', delay: 0 },
{ type: 'task', template: 'csm-outreach', delay: 0 },
{ type: 'email', template: 'renewal-reminder-30', delay: 30 },
{ type: 'email', template: 'renewal-reminder-7', delay: 53 }
]
},
{
id: 'at-risk',
name: 'At Risk',
triggers: [
{ type: 'health_score', value: { max: 40 } }
],
actions: [
{ type: 'slack_notify', template: 'at-risk-alert', delay: 0 },
{ type: 'email', template: 'we-miss-you', delay: 1 },
{ type: 'task', template: 'csm-intervention', delay: 0 },
{ type: 'email', template: 'how-can-we-help', delay: 3 }
]
}
];
// Process lifecycle for all customers
export async function processLifecycleAutomation(): Promise<void> {
const customers = await db.customer.findMany({
include: {
subscription: true,
healthScore: true,
lifecycleState: true
}
});
for (const customer of customers) {
await processCustomerLifecycle(customer);
}
}
async function processCustomerLifecycle(customer: any): Promise<void> {
// Determine current stage
const currentStage = determineLifecycleStage(customer);
if (!currentStage) return;
// Check if stage changed
const previousStage = customer.lifecycleState?.stageId;
if (currentStage.id !== previousStage) {
// Update stage
await db.customerLifecycleState.upsert({
where: { customerId: customer.id },
create: {
customerId: customer.id,
stageId: currentStage.id,
enteredAt: new Date()
},
update: {
stageId: currentStage.id,
enteredAt: new Date()
}
});
// Execute stage entry actions
for (const action of currentStage.actions) {
await scheduleAction(customer.id, action);
}
}
}
function determineLifecycleStage(customer: any): LifecycleStage | null {
const daysSinceSignup = differenceInDays(new Date(), customer.createdAt);
const healthScore = customer.healthScore?.score || 50;
const renewalDays = customer.subscription?.currentPeriodEnd
? differenceInDays(customer.subscription.currentPeriodEnd, new Date())
: null;
// Check stages in priority order
if (healthScore < 40) {
return lifecycleStages.find(s => s.id === 'at-risk')!;
}
if (renewalDays !== null && renewalDays <= 60) {
return lifecycleStages.find(s => s.id === 'renewal')!;
}
if (daysSinceSignup < 14) {
return lifecycleStages.find(s => s.id === 'onboarding')!;
}
if (daysSinceSignup < 60) {
return lifecycleStages.find(s => s.id === 'adoption')!;
}
return lifecycleStages.find(s => s.id === 'growth')!;
}
async function scheduleAction(
customerId: string,
action: Action
): Promise<void> {
const scheduledFor = action.delay
? addDays(new Date(), action.delay)
: new Date();
await db.scheduledAction.create({
data: {
customerId,
actionType: action.type,
template: action.template,
scheduledFor,
status: 'pending'
}
});
}
```
---
## Churn Prediction
```typescript
// lib/customer-success/churn-prediction.ts
import { db } from '@/lib/db';
interface ChurnPrediction {
customerId: string;
churnProbability: number;
riskFactors: RiskFactor[];
recommendedActions: string[];
}
interface RiskFactor {
factor: string;
impact: 'high' | 'medium' | 'low';
currentValue: number;
threshold: number;
}
export async function predictChurn(customerId: string): Promise<ChurnPrediction> {
const riskFactors: RiskFactor[] = [];
let churnScore = 0;
// Factor 1: Login frequency decline
const loginDecline = await calculateLoginDecline(customerId);
if (loginDecline > 50) {
riskFactors.push({
factor: 'Login frequency decreased',
impact: 'high',
currentValue: loginDecline,
threshold: 50
});
churnScore += 25;
}
// Factor 2: Feature usage decline
const featureDecline = await calculateFeatureUsageDecline(customerId);
if (featureDecline > 30) {
riskFactors.push({
factor: 'Feature usage decreased',
impact: 'medium',
currentValue: featureDecline,
threshold: 30
});
churnScore += 15;
}
// Factor 3: Support sentiment
const supportSentiment = await analyzeSupportSentiment(customerId);
if (supportSentiment < 0.3) {
riskFactors.push({
factor: 'Negative support interactions',
impact: 'high',
currentValue: supportSentiment,
threshold: 0.3
});
churnScore += 20;
}
// Factor 4: NPS score
const npsScore = await getLatestNPS(customerId);
if (npsScore !== null && npsScore < 7) {
riskFactors.push({
factor: 'Low NPS score',
impact: npsScore < 5 ? 'high' : 'medium',
currentValue: npsScore,
threshold: 7
});
churnScore += npsScore < 5 ? 25 : 15;
}
// Factor 5: Key user departure
const keyUserLeft = await checkKeyUserDeparture(customerId);
if (keyUserLeft) {
riskFactors.push({
factor: 'Key user left organization',
impact: 'high',
currentValue: 1,
threshold: 0
});
churnScore += 20;
}
// Factor 6: Contract value change
const contractDecreased = await checkContractDecrease(customerId);
if (contractDecreased) {
riskFactors.push({
factor: 'Contract value decreased',
impact: 'medium',
currentValue: 1,
threshold: 0
});
churnScore += 15;
}
const churnProbability = Math.min(100, churnScore);
// Generate recommended actions
const recommendedActions = generateRecommendations(riskFactors);
return {
customerId,
churnProbability,
riskFactors,
recommendedActions
};
}
function generateRecommendations(riskFactors: RiskFactor[]): string[] {
const recommendations: string[] = [];
const factorTypes = riskFactors.map(f => f.factor);
if (factorTypes.includes('Login frequency decreased')) {
recommendations.push('Schedule re-engagement call');
recommendations.push('Send personalized win-back email');
}
if (factorTypes.includes('Feature usage decreased')) {
recommendations.push('Offer feature training session');
recommendations.push('Share relevant use case examples');
}
if (factorTypes.includes('Negative support interactions')) {
recommendations.push('Escalate to senior support');
recommendations.push('CSM personal outreach');
}
if (factorTypes.includes('Low NPS score')) {
recommendations.push('Schedule NPS follow-up call');
recommendations.push('Address specific feedback');
}
if (factorTypes.includes('Key user left organization')) {
recommendations.push('Identify and onboard new champion');
recommendations.push('Executive sponsor outreach');
}
return recommendations;
}
// Batch process all customers
export async function runChurnPredictions(): Promise<void> {
const customers = await db.customer.findMany({
where: { subscription: { status: 'active' } }
});
for (const customer of customers) {
const prediction = await predictChurn(customer.id);
// Store prediction
await db.churnPrediction.upsert({
where: { customerId: customer.id },
create: {
customerId: customer.id,
probability: prediction.churnProbability,
riskFactors: prediction.riskFactors,
predictedAt: new Date()
},
update: {
probability: prediction.churnProbability,
riskFactors: prediction.riskFactors,
predictedAt: new Date()
}
});
// Alert if high risk
if (prediction.churnProbability > 70) {
await createChurnAlert(customer.id, prediction);
}
}
}
```
---
## NPS Survey Automation
```tsx
// components/NPS/NPSSurvey.tsx
'use client';
import { useState } from 'react';
interface NPSSurveyProps {
onSubmit: (score: number, feedback?: string) => void;
onDismiss: () => void;
}
export function NPSSurvey({ onSubmit, onDismiss }: NPSSurveyProps) {
const [score, setScore] = useState<number | null>(null);
const [feedback, setFeedback] = useState('');
const [step, setStep] = useState<'score' | 'feedback'>('score');
const handleScoreSelect = (value: number) => {
setScore(value);
setStep('feedback');
};
const handleSubmit = () => {
if (score !== null) {
onSubmit(score, feedback || undefined);
}
};
return (
<div className="fixed bottom-4 right-4 w-96 bg-white rounded-lg shadow-xl p-6">
<button
onClick={onDismiss}
className="absolute top-2 right-2 text-gray-400"
>
×
</button>
{step === 'score' ? (
<>
<h3 className="text-lg font-semibold mb-4">
How likely are you to recommend us?
</h3>
<div className="flex gap-1 mb-4">
{Array.from({ length: 11 }, (_, i) => (
<button
key={i}
onClick={() => handleScoreSelect(i)}
className={`w-8 h-8 rounded text-sm font-medium transition ${
i <= 6 ? 'hover:bg-red-100' :
i <= 8 ? 'hover:bg-yellow-100' : 'hover:bg-green-100'
} ${score === i ? 'ring-2 ring-blue-500' : 'bg-gray-100'}`}
>
{i}
</button>
))}
</div>
<div className="flex justify-between text-xs text-gray-500">
<span>Not at all likely</span>
<span>Extremely likely</span>
</div>
</>
) : (
<>
<h3 className="text-lg font-semibold mb-2">
{score! >= 9 ? "That's great! What do you love most?" :
score! >= 7 ? "Thanks! How can we improve?" :
"We're sorry to hear that. What went wrong?"}
</h3>
<textarea
value={feedback}
onChange={(e) => setFeedback(e.target.value)}
placeholder="Your feedback helps us improve..."
className="w-full p-3 border rounded-lg mb-4"
rows={3}
/>
<div className="flex gap-2">
<button
onClick={() => setStep('score')}
className="px-4 py-2 text-gray-600"
>
Back
</button>
<button
onClick={handleSubmit}
className="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg"
>
Submit
</button>
</div>
</>
)}
</div>
);
}
```
---
## Best Practices
| Area | Recommendation |
|------|----------------|
| **Health Score** | Update daily, weight factors appropriately |
| **Lifecycle** | Personalize based on segment and behavior |
| **Churn** | Act on predictions within 48 hours |
| **NPS** | Survey at key moments, not randomly |
| **Automation** | Always allow human override |
---
## Fazit
Customer Success Automation ermöglicht:
1. **Health Scores**: Frühzeitige Risiko-Erkennung
2. **Lifecycle Emails**: Skalierbare Kommunikation
3. **Churn Prediction**: Proaktive Intervention
4. **NPS**: Kontinuierliches Feedback
Automatisierung skaliert Customer Success ohne Qualitätsverlust.
---
## Bildprompts
1. "Customer health score dashboard, gauge visualization with risk levels"
2. "Lifecycle email automation flow, triggered sequences"
3. "Churn prediction model, risk factors highlighted"
---
## Quellen
- [Gainsight Customer Success](https://www.gainsight.com/customer-success/)
- [ChurnZero Best Practices](https://churnzero.com/resources/)
- [Customer Success Metrics](https://www.custify.com/blog/customer-success-metrics/)
- [NPS Best Practices](https://www.netpromoter.com/know/)