mcpField
C
customer-success

customer-success

github.com/travisjneumanその他106133 回インストール今日更新
リポジトリ

customer-success について

カスタマーサポートとサクセス運用のシステム構築を支援するスキルです。チケット管理、ナレッジベース、チャットボット、SLA、顧客ヘルススコア、フィードバックの振り分けを扱います。

主な機能

  1. 01サポートチケットの連携と振り分け
  2. 02ナレッジベースとアプリ内ヘルプの設計
  3. 03SLA管理とエスカレーションの自動化
  4. 04顧客ヘルススコアモデル
  5. 05フィードバック収集と製品傾向の分析

活用シーン

  1. 01サポートシステムの構築や統合
  2. 02チャットボットとセルフサービスポータルの設計
  3. 03SLA追跡とエスカレーション通知の導入
  4. 04解約リスクのある顧客の特定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/travisjneuman/.claude --skill customer-success

インストール後、Claude Code で/customer-successを入力して読み込まれたか確認します。

詳細

カスタマーサクセスエンジニアリング

概要

このスキルでは、カスタマーサポートとカスタマーサクセスの業務を支える技術システムの構築について説明します。サポートチケットシステムとの連携(Zendesk、Intercom、Freshdesk)、ナレッジベースのアーキテクチャ、会話型 AI チャットボットの設計、顧客フィードバックの収集と振り分け、SLA の管理と適用、エスカレーションワークフローの自動化、セルフサービスポータルの実装、顧客ヘルススコアモデルを扱います。

サポートシステムの構築や統合、チャットボットのフロー設計、セルフサービス用ドキュメントポータルの作成、SLA トラッキングの実装、顧客ヘルスダッシュボードの構築、サポートワークフローの自動化を行う際に、このスキルを使用してください。


基本原則

  1. セルフサービスを優先する - 最善のサポート対応とは、そもそも発生しない対応です。人によるサポートを拡大する前に、検索可能なナレッジベース、アプリ内ヘルプ、状況に応じたガイダンスに投資しましょう。
  2. 解決ではなくトリアージを自動化する - AI はチケットの分類、優先順位付け、振り分けを効果的に行えます。複雑な問題の解決は人に任せましょう。解決まで過度に自動化すると、顧客の不満につながります。
  3. チケット数ではなく解決までの時間を測定する - 顧客の問題が解決していなければ、チケットをすばやくクローズしても意味がありません。初回対応での解決率、顧客の労力スコア、再オープン率を追跡しましょう。
  4. チケットにはコンテキストを引き継ぐ - あらゆる引き継ぎ(ボットから人へ、L1 から L2 へ)では、会話履歴全体、ユーザーアカウントデータ、試行済みの解決策を含める必要があります。同じ情報を何度も伝えさせることは、顧客から最も多く寄せられる不満です。
  5. フィードバックはプロダクトのシグナルである - サポートチケットは、構造化されていないプロダクトフィードバックです。タグ付けと分類を行い、傾向をプロダクトチームに共有しましょう。最も多いサポートトピックを、次のプロダクト改善につなげるべきです。

主なパターン

パターン 1:ナレッジベースのアーキテクチャ

使用する場面: 顧客(セルフサービス)とサポートエージェント(社内リファレンス)の両方に役立つ、検索可能なドキュメントを構築するとき。

実装:

// Knowledge base article schema
interface Article {
  id: string;
  slug: string;
  title: string;
  content: string;          // Markdown
  excerpt: string;          // For search results
  category: string;
  subcategory: string;
  tags: string[];
  audience: "customer" | "internal" | "both";
  visibility: "public" | "authenticated" | "internal";
  relatedArticles: string[];
  metadata: {
    createdAt: Date;
    updatedAt: Date;
    author: string;
    reviewedAt: Date | null;
    helpfulVotes: number;
    notHelpfulVotes: number;
    viewCount: number;
  };
}

// Search implementation with vector + full-text hybrid
async function searchKnowledgeBase(
  query: string,
  options?: { category?: string; audience?: string; limit?: number }
): Promise<SearchResult[]> {
  const limit = options?.limit ?? 10;

  // 1. Semantic search (catches paraphrased queries)
  const embedding = await getEmbedding(query);
  const semanticResults = await vectorDb.search({
    vector: embedding,
    filter: {
      audience: options?.audience ?? "customer",
      ...(options?.category && { category: options.category }),
    },
    limit,
  });

  // 2. Full-text search (catches exact terminology)
  const textResults = await db.$queryRaw`
    SELECT id, title, excerpt,
           ts_rank(search_vector, plainto_tsquery('english', ${query})) AS rank
    FROM articles
    WHERE search_vector @@ plainto_tsquery('english', ${query})
      AND audience IN ('customer', 'both')
      ${options?.category ? Prisma.sql`AND category = ${options.category}` : Prisma.empty}
    ORDER BY rank DESC
    LIMIT ${limit}
  `;

  // 3. Merge and deduplicate results
  const merged = mergeSearchResults(semanticResults, textResults);

  // 4. Track search for analytics
  await trackSearch(query, merged.length);

  return merged;
}

// Feedback loop - track article helpfulness
async function rateArticle(
  articleId: string,
  helpful: boolean,
  feedback?: string
): Promise<void> {
  await db.articleFeedback.create({
    data: {
      articleId,
      helpful,
      feedback,
      createdAt: new Date(),
    },
  });

  // Update aggregate counts
  await db.article.update({
    where: { id: articleId },
    data: helpful
      ? { helpfulVotes: { increment: 1 } }
      : { notHelpfulVotes: { increment: 1 } },
  });

  // Flag articles with low helpfulness for review
  const article = await db.article.findUnique({ where: { id: articleId } });
  if (article) {
    const total = article.helpfulVotes + article.notHelpfulVotes;
    const helpfulRate = total > 10 ? article.helpfulVotes / total : 1;

    if (helpfulRate < 0.5 && total > 10) {
      await createReviewTask(articleId, "Low helpfulness score");
    }
  }
}
// In-app contextual help widget
function HelpWidget({ context }: { context: string }) {
  const [articles, setArticles] = useState<Article[]>([]);
  const [isOpen, setIsOpen] = useState(false);

  useEffect(() => {
    // Fetch relevant articles based on current page/feature context
    if (isOpen) {
      searchKnowledgeBase("", { category: context, limit: 5 })
        .then(setArticles);
    }
  }, [isOpen, context]);

  return (
    <div className="help-widget">
      <button
        onClick={() => setIsOpen(!isOpen)}
        aria-label="Help"
        aria-expanded={isOpen}
      >
        ?
      </button>
      {isOpen && (
        <div role="dialog" aria-label="Help articles">
          <SearchInput onSearch={(q) => searchKnowledgeBase(q, { category: context }).then(setArticles)} />
          <ArticleList articles={articles} />
          <a href="/support">Contact support</a>
        </div>
      )}
    </div>
  );
}

理由: ハイブリッド検索(セマンティック検索+全文検索)なら、「プロジェクトを共有するにはどうすればよいですか?」のような概念的なクエリと、「SSO SAML configuration」のような正確な用語のクエリの両方に対応できます。フィードバックループにより、品質の低い記事にフラグを立てて改善できます。状況に応じたヘルプを表示すれば、ユーザーが検索しなくても関連する記事を見つけられます。


パターン 2:サポートチケットの連携

使用する場面: 構造化されたサポートワークフローを実現するために、アプリケーションをチケット管理システムに接続するとき。

実装:

// Unified support ticket interface (abstracts provider)
interface SupportTicket {
  id: string;
  externalId: string;       // Provider's ticket ID
  subject: string;
  description: string;
  status: "open" | "pending" | "in_progress" | "resolved" | "closed";
  priority: "low" | "normal" | "high" | "urgent";
  category: string;
  tags: string[];
  requester: {
    id: string;
    email: string;
    name: string;
  };
  assignee?: {
    id: string;
    name: string;
    group: string;
  };
  metadata: {
    plan: string;
    accountAge: number;
    mrr: number;            // Monthly recurring revenue
    previousTickets: number;
  };
  createdAt: Date;
  updatedAt: Date;
  firstResponseAt?: Date;
  resolvedAt?: Date;
}

// Auto-triage new tickets with AI classification
async function triageTicket(ticket: SupportTicket): Promise<TriageResult> {
  const classification = await classifyTicket(ticket.subject, ticket.description);

  // Priority escalation rules
  let adjustedPriority = classification.priority;

  // Enterprise customers get elevated priority
  if (ticket.metadata.mrr >= 5000) {
    adjustedPriority = elevate(adjustedPriority);
  }

  // Revenue at risk detection
  if (containsCancellationIntent(ticket.description)) {
    adjustedPriority = "urgent";
    classification.tags.push("churn-risk");
  }

  // Route to appropriate team
  const team = determineTeam(classification.category, adjustedPriority);

  return {
    category: classification.category,
    priority: adjustedPriority,
    team,
    tags: classification.tags,
    suggestedArticles: await findRelatedArticles(ticket.subject),
    autoResponse: classification.confidence > 0.9
      ? generateAutoResponse(classification, ticket)
      : null,
  };
}

// SLA enforcement
interface SLAPolicy {
  priority: string;
  firstResponseMinutes: number;
  resolutionMinutes: number;
}

const SLA_POLICIES: SLAPolicy[] = [
  { priority: "urgent", firstResponseMinutes: 30, resolutionMinutes: 240 },
  { priority: "high", firstResponseMinutes: 120, resolutionMinutes: 480 },
  { priority: "normal", firstResponseMinutes: 480, resolutionMinutes: 1440 },
  { priority: "low", firstResponseMinutes: 1440, resolutionMinutes: 4320 },
];

async function checkSLABreaches(): Promise<SLABreach[]> {
  const openTickets = await getOpenTickets();
  const breaches: SLABreach[] = [];

  for (const ticket of openTickets) {
    const policy = SLA_POLICIES.find((p) => p.priority === ticket.priority);
    if (!policy) continue;

    const now = new Date();
    const age = (now.getTime() - ticket.createdAt.getTime()) / 60000;

    // First response SLA
    if (!ticket.firstResponseAt && age > policy.firstResponseMinutes) {
      breaches.push({
        ticketId: ticket.id,
        type: "first_response",
        minutesOverdue: Math.round(age - policy.firstResponseMinutes),
      });
    }

    // Resolution SLA
    if (!ticket.resolvedAt && age > policy.resolutionMinutes) {
      breaches.push({
        ticketId: ticket.id,
        type: "resolution",
        minutesOverdue: Math.round(age - policy.resolutionMinutes),
      });
    }
  }

  return breaches;
}

理由: AI を活用したトリアージによってチケットを適切なチームにすばやく振り分け、初回応答までの時間を短縮できます。収益に基づく優先度のエスカレーションにより、価値の高い顧客に適切な対応を提供できます。SLA の監視によって説明責任を果たし、体系的な問題を明らかにできます(特定のカテゴリで SLA 違反が繰り返し発生する場合、プロダクトやドキュメントに不足があることを示しています)。


パターン 3:顧客ヘルススコアリング

使用する場面: 顧客が解約する前に、離反リスクのある顧客を先回りして特定するとき。

実装:

// Health score calculation
interface HealthSignal {
  name: string;
  weight: number;
  score: (customer: Customer) => number; // 0-100
}

const healthSignals: HealthSignal[] = [
  {
    name: "product_usage",
    weight: 0.3,
    score: (c) => {
      const weeklyActive = c.loginDaysLast30 / 30 * 100;
      return Math.min(100, weeklyActive * 1.5);
    },
  },
  {
    name: "feature_adoption",
    weight: 0.2,
    score: (c) => {
      const total = CORE_FEATURES.length;
      const adopted = c.featuresUsed.filter((f) => CORE_FEATURES.includes(f)).length;
      return (adopted / total) * 100;
    },
  },
  {
    name: "support_sentiment",
    weight: 0.15,
    score: (c) => {
      if (c.ticketsLast90Days === 0) return 70; // Neutral
      const resolved = c.resolvedTicketsLast90Days / c.ticketsLast90Days;
      return resolved * 100;
    },
  },
  {
    name: "expansion_signals",
    weight: 0.15,
    score: (c) => {
      const usageGrowth = c.currentMonthUsage / (c.previousMonthUsage || 1);
      if (usageGrowth > 1.2) return 100;
      if (usageGrowth > 1.0) return 70;
      if (usageGrowth > 0.8) return 40;
      return 10;
    },
  },
  {
    name: "payment_health",
    weight: 0.2,
    score: (c) => {
      if (c.failedPaymentsLast90Days > 2) return 20;
      if (c.failedPaymentsLast90Days > 0) return 60;
      return 100;
    },
  },
];

function calculateHealthScore(customer: Customer): {
  score: number;
  status: "healthy" | "at-risk" | "critical";
  signals: Array<{ name: string; score: number; weight: number }>;
} {
  const signals = healthSignals.map((signal) => ({
    name: signal.name,
    score: signal.score(customer),
    weight: signal.weight,
  }));

  const weightedScore = signals.reduce(
    (total, s) => total + s.score * s.weight,
    0
  );

  return {
    score: Math.round(weightedScore),
    status: weightedScore >= 70 ? "healthy" : weightedScore >= 40 ? "at-risk" : "critical",
    signals,
  };
}

// Scheduled job: calculate health scores and alert CSMs
async function updateHealthScores(): Promise<void> {
  const customers = await db.customer.findMany({
    where: { subscriptionStatus: "active" },
    include: { usage: true, tickets: true, payments: true },
  });

  for (const customer of customers) {
    const health = calculateHealthScore(customer);

    await db.customerHealth.upsert({
      where: { customerId: customer.id },
      create: { customerId: customer.id, ...health },
      update: health,
    });

    // Alert CSM for critical customers
    if (health.status === "critical" && customer.mrr >= 1000) {
      await notifyCSM(customer.csmId, {
        customer: customer.name,
        score: health.score,
        signals: health.signals.filter((s) => s.score < 40),
      });
    }
  }
}

理由: ヘルススコアリングにより、受け身のサポート(解約を待つ)から、先回りするカスタマーサクセス(解約前に介入する)へと転換できます。利用状況、サポートへの感情、機能の利用状況、支払い状況を組み合わせた総合スコアによって、全体像を把握できます。重要なアカウントに対するアラートを自動化すれば、価値の高い顧客に CSM が適切なタイミングで対応できます。


アンチパターン

アンチパターン問題点より良い方法
人間へのエスカレーションがない、ボットだけのサポート顧客の不満、解約につながりますボットで振り分けとナレッジ提供を行い、解決は人間が担当します
エスカレーション時にコンテキストを渡さない顧客がすべてを繰り返し説明することになります会話履歴とアカウントデータをすべて渡します
対応完了チケット数で成功を測る対応の早すぎる終了を促します初回問い合わせでの解決率と顧客の労力を測定します
フィードバックループのないナレッジベース古く役に立たない記事が残り続けます役立ち度の評価を追跡し、評価の低い記事にフラグを付けます
すべての顧客に同じ SLA を適用するエンタープライズ顧客と無料顧客が同じ優先度になりますプランと収益に基づいて SLA を段階化します
サポートと製品フィードバックを切り離すチケットを未然に防げるパターンを見逃しますチケットに製品フィードバックのタグを付け、傾向を可視化します

チェックリスト

  • ナレッジベースをハイブリッド検索(セマンティック検索+全文検索)で検索できる
  • 主要ページにアプリ内のコンテキストに応じたヘルプウィジェットがある
  • サポートチケットが AI による分類で自動振り分けされる
  • 優先度ごとに SLA ポリシーが定義されている
  • SLA 違反を監視し、エスカレーションアラートを送信する
  • 顧客ヘルススコアをスケジュールに沿って算出する
  • リスクの高い重要顧客に対して CSM アラートを発生させる
  • すべてのエスカレーションで完全なコンテキスト(会話+アカウントデータ)を共有する
  • フィードバックループを通じて記事の役立ち度を追跡する
  • サポートの傾向を毎月プロダクトチームに共有する

関連リソース

  • スキル: llm-app-development(チャットボットの RAG)、product-analytics(サポートファネル分析)
  • スキル: email-systems(サポート通知メール)、accessibility-a11y(アクセシブルなヘルプウィジェット)
  • ルール: docs/reference/stacks/react-typescript.md(ヘルプウィジェットのコンポーネント)

コメント