Make(旧Integromat)とZapierによる業務自動化完全ガイド:エンタープライズ向けワークフロー構築

はじめに

ノーコード・ローコード自動化プラットフォームの Make と Zapier は、企業の業務プロセスを劇的に効率化する強力なツールです。本記事では、両プラットフォームの特徴を比較しながら、実践的な自動化ワークフローの構築方法を詳しく解説します。

Make vs Zapier:プラットフォーム比較

主要な違い

特徴 Make Zapier
ビジュアルエディタ 高度なフローチャート形式 リニアなステップ形式
価格体系 オペレーション数ベース タスク数ベース
複雑なロジック 条件分岐、ループ、エラー処理が柔軟 シンプルな条件分岐
データ処理 高度なデータ変換・フィルタリング 基本的な変換機能
統合サービス数 1,000+ 5,000+

Make での高度なワークフロー構築

1. CRM と会計システムの統合

{
  "name": "顧客注文処理自動化",
  "description": "Salesforceの注文をfreeeに自動転記",
  "modules": [
    {
      "id": 1,
      "type": "salesforce:watchRecords",
      "parameters": {
        "object": "Opportunity",
        "query": "StageName = 'Closed Won' AND CreatedDate >= LAST_N_DAYS:1",
        "fields": ["Id", "Name", "Amount", "AccountId", "CloseDate"]
      }
    },
    {
      "id": 2,
      "type": "salesforce:getRecord",
      "parameters": {
        "object": "Account",
        "recordId": "{{1.AccountId}}",
        "fields": ["Name", "BillingAddress", "Phone"]
      }
    },
    {
      "id": 3,
      "type": "tools:setVariable",
      "parameters": {
        "variables": [
          {
            "name": "taxRate",
            "value": "{{if(1.Amount >= 10000; 0.1; 0.08)}}"
          },
          {
            "name": "taxAmount",
            "value": "{{1.Amount * taxRate}}"
          }
        ]
      }
    },
    {
      "id": 4,
      "type": "freee:createInvoice",
      "parameters": {
        "company_id": "{{env.FREEE_COMPANY_ID}}",
        "partner_code": "{{2.Id}}",
        "invoice_date": "{{formatDate(1.CloseDate; 'YYYY-MM-DD')}}",
        "due_date": "{{addDays(1.CloseDate; 30)}}",
        "invoice_lines": [
          {
            "description": "{{1.Name}}",
            "unit_price": "{{1.Amount}}",
            "quantity": 1,
            "tax_code": "{{if(taxRate == 0.1; 'tax_10'; 'tax_8')}}"
          }
        ]
      }
    },
    {
      "id": 5,
      "type": "slack:sendMessage",
      "parameters": {
        "channel": "#sales-notifications",
        "text": "新規請求書が作成されました :tada:\n顧客: {{2.Name}}\n金額: ¥{{formatNumber(1.Amount; 0; ','; '.')}}\n請求書番号: {{4.invoice_number}}"
      }
    }
  ],
  "error_handling": {
    "global": {
      "type": "email",
      "to": "admin@company.com",
      "subject": "ワークフローエラー: {{scenario.name}}",
      "body": "エラーが発生しました。\nモジュール: {{error.module}}\nメッセージ: {{error.message}}"
    }
  }
}

2. データ集約とレポート生成

// Make カスタム関数モジュール
function aggregateSalesData(records) {
  const aggregated = {};
  
  records.forEach(record => {
    const month = new Date(record.date).toISOString().slice(0, 7);
    const product = record.product_name;
    
    if (!aggregated[month]) {
      aggregated[month] = {};
    }
    
    if (!aggregated[month][product]) {
      aggregated[month][product] = {
        quantity: 0,
        revenue: 0,
        orders: 0
      };
    }
    
    aggregated[month][product].quantity += record.quantity;
    aggregated[month][product].revenue += record.amount;
    aggregated[month][product].orders += 1;
  });
  
  // チャート用データ形式に変換
  const chartData = Object.entries(aggregated).map(([month, products]) => ({
    month,
    data: Object.entries(products).map(([product, stats]) => ({
      product,
      ...stats,
      averageOrderValue: stats.revenue / stats.orders
    }))
  }));
  
  return chartData;
}

// Google Sheets への書き込み
function writeToGoogleSheets(spreadsheetId, sheetName, data) {
  const headers = ['月', '商品名', '数量', '売上', '注文数', '平均注文額'];
  const rows = [];
  
  data.forEach(monthData => {
    monthData.data.forEach(product => {
      rows.push([
        monthData.month,
        product.product,
        product.quantity,
        product.revenue,
        product.orders,
        product.averageOrderValue
      ]);
    });
  });
  
  return {
    range: `${sheetName}!A1`,
    values: [headers, ...rows]
  };
}

3. 複雑な承認フロー

{
  "name": "経費精算承認ワークフロー",
  "modules": [
    {
      "id": 1,
      "type": "webhook",
      "parameters": {
        "name": "expense_submission"
      }
    },
    {
      "id": 2,
      "type": "router",
      "routes": [
        {
          "condition": "{{1.amount <= 50000}}",
          "modules": [
            {
              "id": 3,
              "type": "email:send",
              "parameters": {
                "to": "{{getManagerEmail(1.employee_id)}}",
                "subject": "経費精算承認依頼",
                "body": "承認URL: {{env.APP_URL}}/approve/{{1.request_id}}"
              }
            }
          ]
        },
        {
          "condition": "{{1.amount > 50000 && 1.amount <= 200000}}",
          "modules": [
            {
              "id": 4,
              "type": "iterator",
              "parameters": {
                "array": "{{getApprovalChain(1.employee_id; 2)}}"
              }
            },
            {
              "id": 5,
              "type": "http:request",
              "parameters": {
                "url": "{{env.WORKFLOW_API}}/create-approval",
                "method": "POST",
                "body": {
                  "request_id": "{{1.request_id}}",
                  "approver_id": "{{4.value}}",
                  "level": "{{4.index + 1}}",
                  "amount": "{{1.amount}}"
                }
              }
            }
          ]
        },
        {
          "condition": "{{1.amount > 200000}}",
          "modules": [
            {
              "id": 6,
              "type": "teams:sendMessage",
              "parameters": {
                "team": "Finance",
                "channel": "経費精算",
                "message": "高額経費精算の申請があります。\n金額: ¥{{formatNumber(1.amount)}}\n申請者: {{1.employee_name}}\n役員承認が必要です。"
              }
            }
          ]
        }
      ]
    }
  ]
}

Zapier での効率的な自動化

1. マルチステップZap

// Zapier Code Step - JavaScript
// 顧客データのエンリッチメント
const enrichCustomerData = async (inputData) => {
  // 企業ドメインから追加情報を取得
  const domain = inputData.email.split('@')[1];
  
  try {
    // Clearbit API を使用した企業情報取得
    const response = await fetch(`https://company.clearbit.com/v2/companies/find?domain=${domain}`, {
      headers: {
        'Authorization': `Bearer ${process.env.CLEARBIT_API_KEY}`
      }
    });
    
    const companyData = await response.json();
    
    return {
      originalData: inputData,
      enrichedData: {
        companyName: companyData.name,
        industry: companyData.category.industry,
        employeeCount: companyData.metrics.employees,
        annualRevenue: companyData.metrics.annualRevenue,
        technologies: companyData.tech || [],
        socialProfiles: {
          linkedin: companyData.linkedin?.handle,
          twitter: companyData.twitter?.handle
        }
      },
      scoreData: calculateLeadScore(inputData, companyData)
    };
  } catch (error) {
    console.error('Enrichment failed:', error);
    return {
      originalData: inputData,
      enrichedData: null,
      error: error.message
    };
  }
};

// リードスコアリング
const calculateLeadScore = (leadData, companyData) => {
  let score = 0;
  const factors = [];
  
  // 企業規模によるスコアリング
  if (companyData.metrics.employees > 1000) {
    score += 30;
    factors.push('大企業 (+30)');
  } else if (companyData.metrics.employees > 100) {
    score += 20;
    factors.push('中堅企業 (+20)');
  }
  
  // 業界によるスコアリング
  const targetIndustries = ['Software', 'Financial Services', 'Healthcare'];
  if (targetIndustries.includes(companyData.category.industry)) {
    score += 25;
    factors.push(`ターゲット業界: ${companyData.category.industry} (+25)`);
  }
  
  // エンゲージメントによるスコアリング
  if (leadData.downloaded_whitepaper) {
    score += 15;
    factors.push('ホワイトペーパーダウンロード (+15)');
  }
  
  if (leadData.attended_webinar) {
    score += 20;
    factors.push('ウェビナー参加 (+20)');
  }
  
  return {
    totalScore: score,
    grade: score >= 70 ? 'A' : score >= 50 ? 'B' : score >= 30 ? 'C' : 'D',
    factors: factors
  };
};

// Output
return await enrichCustomerData(inputData);

2. Zapier Paths(条件分岐)

# Zapier ワークフロー設定
name: "インテリジェント顧客対応"
trigger:
  app: "Typeform"
  event: "New Entry"

paths:
  - name: "エンタープライズ顧客"
    condition:
      - field: "company_size"
        operator: "greater_than"
        value: 1000
    actions:
      - app: "Salesforce"
        action: "Create Lead"
        data:
          lead_source: "Enterprise Inquiry"
          priority: "High"
          assign_to: "enterprise_team"
      
      - app: "Calendly"
        action: "Create One-Time Link"
        data:
          event_type: "enterprise_demo"
          assignee: "senior_sales_rep"
      
      - app: "Slack"
        action: "Send Direct Message"
        data:
          user: "@sales_manager"
          message: "新規エンタープライズリード: {{company_name}}"

  - name: "SMB顧客"
    condition:
      - field: "company_size"
        operator: "less_than"
        value: 100
    actions:
      - app: "HubSpot"
        action: "Create Contact"
        data:
          lifecycle_stage: "subscriber"
          
      - app: "Mailchimp"
        action: "Add Subscriber to Journey"
        data:
          journey_id: "smb_nurture_campaign"
          
      - app: "Google Sheets"
        action: "Create Spreadsheet Row"
        data:
          spreadsheet: "SMB_Leads_{{current_year}}"

3. Zapier Formatter 活用例

// 複雑なデータ変換
const transformOrderData = (orderData) => {
  // 日本のビジネス慣習に合わせたデータ変換
  const japaneseOrder = {
    受注番号: orderData.order_id,
    受注日: new Date(orderData.created_at).toLocaleDateString('ja-JP', {
      year: 'numeric',
      month: 'long',
      day: 'numeric',
      weekday: 'long'
    }),
    顧客情報: {
      会社名: orderData.company_name,
      担当者名: `${orderData.last_name} ${orderData.first_name}様`,
      郵便番号: formatPostalCode(orderData.postal_code),
      住所: formatJapaneseAddress(orderData.address)
    },
    商品明細: orderData.line_items.map(item => ({
      商品コード: item.sku,
      商品名: item.name,
      数量: item.quantity,
      単価: `¥${item.price.toLocaleString()}`,
      小計: `¥${(item.price * item.quantity).toLocaleString()}`
    })),
    合計金額: {
      小計: `¥${orderData.subtotal.toLocaleString()}`,
      消費税: `¥${orderData.tax.toLocaleString()}`,
      送料: `¥${orderData.shipping.toLocaleString()}`,
      総計: `¥${orderData.total.toLocaleString()}`
    },
    支払方法: translatePaymentMethod(orderData.payment_method),
    配送希望日時: formatDeliveryDate(orderData.delivery_date)
  };
  
  return japaneseOrder;
};

// 郵便番号フォーマット
const formatPostalCode = (postalCode) => {
  const cleaned = postalCode.replace(/\D/g, '');
  return `〒${cleaned.slice(0, 3)}-${cleaned.slice(3)}`;
};

// 住所フォーマット
const formatJapaneseAddress = (address) => {
  // 都道府県、市区町村、番地を適切な順序に整形
  return address.state + address.city + address.street;
};

実践的な統合パターン

1. イベント駆動アーキテクチャ

graph LR
    A[Webhook受信] --> B{イベントタイプ}
    B -->|注文| C[Make: 在庫確認]
    B -->|問い合わせ| D[Zapier: CRM登録]
    B -->|キャンセル| E[Make: 返金処理]
    
    C --> F[在庫API]
    C --> G[通知送信]
    
    D --> H[営業チーム通知]
    D --> I[自動返信]
    
    E --> J[決済API]
    E --> K[在庫戻し]

2. データ同期パターン

// 双方向データ同期の実装
class DataSyncManager {
  constructor(makeClient, zapierClient) {
    this.make = makeClient;
    this.zapier = zapierClient;
    this.syncLog = [];
  }
  
  async syncCustomerData(source, target) {
    const lastSync = await this.getLastSyncTimestamp(source, target);
    
    // 差分データの取得
    const sourceUpdates = await this.getUpdatedRecords(source, lastSync);
    const targetUpdates = await this.getUpdatedRecords(target, lastSync);
    
    // 競合の検出と解決
    const conflicts = this.detectConflicts(sourceUpdates, targetUpdates);
    const resolved = await this.resolveConflicts(conflicts);
    
    // 同期実行
    const syncResults = await this.executeSyncBatch([
      ...sourceUpdates.filter(u => !conflicts.includes(u.id)),
      ...resolved
    ]);
    
    // 同期ログの記録
    await this.logSyncOperation({
      timestamp: new Date(),
      source,
      target,
      recordsSync: syncResults.success.length,
      conflicts: conflicts.length,
      errors: syncResults.errors
    });
    
    return syncResults;
  }
  
  detectConflicts(sourceUpdates, targetUpdates) {
    const conflicts = [];
    
    sourceUpdates.forEach(sourceRecord => {
      const targetRecord = targetUpdates.find(t => t.id === sourceRecord.id);
      
      if (targetRecord && 
          sourceRecord.updated_at !== targetRecord.updated_at) {
        conflicts.push({
          id: sourceRecord.id,
          source: sourceRecord,
          target: targetRecord,
          conflictType: this.determineConflictType(sourceRecord, targetRecord)
        });
      }
    });
    
    return conflicts;
  }
  
  async resolveConflicts(conflicts) {
    const resolutionStrategies = {
      'field_update': this.mergeFields,
      'deletion': this.handleDeletion,
      'schema_change': this.handleSchemaChange
    };
    
    const resolved = [];
    
    for (const conflict of conflicts) {
      const strategy = resolutionStrategies[conflict.conflictType];
      const resolution = await strategy.call(this, conflict);
      resolved.push(resolution);
    }
    
    return resolved;
  }
}

3. エラーハンドリングとリトライ

// 堅牢なエラーハンドリング実装
class WorkflowErrorHandler {
  constructor(config) {
    this.maxRetries = config.maxRetries || 3;
    this.retryDelay = config.retryDelay || 1000;
    this.alertThreshold = config.alertThreshold || 5;
    this.errorLog = [];
  }
  
  async executeWithRetry(operation, context) {
    let lastError;
    
    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
      try {
        // 実行前のバリデーション
        await this.validateContext(context);
        
        // オペレーション実行
        const result = await operation();
        
        // 成功時の処理
        await this.onSuccess(context, result);
        return result;
        
      } catch (error) {
        lastError = error;
        
        // エラーログ記録
        await this.logError({
          timestamp: new Date(),
          attempt,
          context,
          error: {
            message: error.message,
            stack: error.stack,
            code: error.code
          }
        });
        
        // リトライ可能なエラーかチェック
        if (!this.isRetryableError(error) || attempt === this.maxRetries) {
          break;
        }
        
        // 指数バックオフ
        const delay = this.retryDelay * Math.pow(2, attempt - 1);
        await this.sleep(delay);
      }
    }
    
    // 最終的な失敗処理
    await this.handleFailure(context, lastError);
    throw lastError;
  }
  
  isRetryableError(error) {
    const retryableCodes = [
      'RATE_LIMIT_EXCEEDED',
      'TIMEOUT',
      'SERVICE_UNAVAILABLE',
      'GATEWAY_TIMEOUT'
    ];
    
    return retryableCodes.includes(error.code) || 
           error.statusCode >= 500;
  }
  
  async handleFailure(context, error) {
    // Dead Letter Queue への送信
    await this.sendToDeadLetterQueue({
      context,
      error,
      timestamp: new Date()
    });
    
    // アラート送信
    if (this.shouldSendAlert()) {
      await this.sendAlert({
        severity: 'critical',
        message: `Workflow failed after ${this.maxRetries} attempts`,
        context,
        error
      });
    }
  }
  
  shouldSendAlert() {
    const recentErrors = this.errorLog.filter(
      e => e.timestamp > new Date(Date.now() - 3600000) // 1時間以内
    );
    
    return recentErrors.length >= this.alertThreshold;
  }
}

パフォーマンス最適化

バッチ処理の実装

// 効率的なバッチ処理
class BatchProcessor {
  constructor(options = {}) {
    this.batchSize = options.batchSize || 100;
    this.concurrency = options.concurrency || 5;
    this.timeout = options.timeout || 30000;
  }
  
  async processBatch(items, processor) {
    const batches = this.createBatches(items);
    const results = [];
    
    // 並列バッチ処理
    for (let i = 0; i < batches.length; i += this.concurrency) {
      const concurrentBatches = batches.slice(i, i + this.concurrency);
      
      const batchPromises = concurrentBatches.map(batch => 
        this.processSingleBatch(batch, processor)
      );
      
      const batchResults = await Promise.allSettled(batchPromises);
      results.push(...batchResults);
    }
    
    return this.aggregateResults(results);
  }
  
  createBatches(items) {
    const batches = [];
    
    for (let i = 0; i < items.length; i += this.batchSize) {
      batches.push(items.slice(i, i + this.batchSize));
    }
    
    return batches;
  }
  
  async processSingleBatch(batch, processor) {
    const timeoutPromise = new Promise((_, reject) => 
      setTimeout(() => reject(new Error('Batch timeout')), this.timeout)
    );
    
    const processPromise = processor(batch);
    
    return Promise.race([processPromise, timeoutPromise]);
  }
  
  aggregateResults(results) {
    const summary = {
      total: results.length,
      successful: 0,
      failed: 0,
      results: [],
      errors: []
    };
    
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        summary.successful++;
        summary.results.push(result.value);
      } else {
        summary.failed++;
        summary.errors.push({
          batchIndex: index,
          error: result.reason
        });
      }
    });
    
    return summary;
  }
}

セキュリティベストプラクティス

APIキーとシークレット管理

// セキュアな認証情報管理
class SecureCredentialManager {
  constructor(vaultClient) {
    this.vault = vaultClient;
    this.cache = new Map();
    this.rotationSchedule = new Map();
  }
  
  async getCredential(serviceName) {
    // キャッシュチェック
    if (this.cache.has(serviceName)) {
      const cached = this.cache.get(serviceName);
      if (!this.isExpired(cached)) {
        return cached.value;
      }
    }
    
    // Vaultから取得
    const credential = await this.vault.read(`secret/data/${serviceName}`);
    
    // キャッシュに保存
    this.cache.set(serviceName, {
      value: credential.data,
      timestamp: Date.now(),
      ttl: credential.lease_duration * 1000
    });
    
    // ローテーションスケジュール設定
    this.scheduleRotation(serviceName, credential.lease_duration);
    
    return credential.data;
  }
  
  scheduleRotation(serviceName, leaseDuration) {
    // 期限の80%でローテーション
    const rotationTime = leaseDuration * 0.8 * 1000;
    
    const timeoutId = setTimeout(async () => {
      await this.rotateCredential(serviceName);
    }, rotationTime);
    
    this.rotationSchedule.set(serviceName, timeoutId);
  }
  
  async rotateCredential(serviceName) {
    try {
      // 新しい認証情報を生成
      const newCredential = await this.vault.rotate(`secret/data/${serviceName}`);
      
      // キャッシュ更新
      this.cache.set(serviceName, {
        value: newCredential.data,
        timestamp: Date.now(),
        ttl: newCredential.lease_duration * 1000
      });
      
      // 依存サービスに通知
      await this.notifyCredentialRotation(serviceName);
      
    } catch (error) {
      console.error(`Failed to rotate credential for ${serviceName}:`, error);
      // フォールバック処理
      await this.handleRotationFailure(serviceName, error);
    }
  }
}

まとめ

Make と Zapier は、それぞれ異なる強みを持つ自動化プラットフォームです。Make は複雑なロジックとデータ処理に優れ、Zapier は幅広いサービス統合と使いやすさが特徴です。

重要なポイント:

  • ビジネス要件に応じた適切なプラットフォーム選択
  • エラーハンドリングとリトライ戦略の実装
  • セキュリティとパフォーマンスの最適化
  • 段階的な自動化の導入とスケーリング

エンハンスド株式会社では、Make と Zapier を活用した業務自動化の設計・実装を支援しています。お気軽にお問い合わせください。


タグ: #Make #Zapier #業務自動化 #ノーコード #ワークフロー #RPA

執筆者: エンハンスド株式会社 自動化ソリューション部

公開日: 2024年12月20日