customerio-deploy-pipeline

Deploy Customer.io integrations to production cloud platforms. Use when deploying to Cloud Run, Vercel, AWS Lambda, or Kubernetes with proper secrets management and health checks. Trigger: "deploy customer.io", "customer.io cloud run", "customer.io kubernetes", "customer.io lambda", "customer.io vercel".

Allowed Tools

ReadWriteEditBash(npm:*)Bash(gcloud:*)Bash(kubectl:*)GlobGrep

Provided by Plugin

customerio-pack

Claude Code skill pack for Customer.io (24 skills)

saas packs v1.14.0
View Plugin

Installation

This skill is included in the customerio-pack plugin:

/plugin install customerio-pack@claude-code-plugins-plus

Click to copy

Instructions

Customer.io Deploy Pipeline

Output

  • A versioned, staged deployment of Customer.io configuration/code with owner approval, validation evidence, and a rollback reference.
  • A protected promotion path that prevents unreviewed changes from affecting production recipients.

Examples

Validate an event/template configuration in development and staging with synthetic profiles, attach schema/template test results to the change, then release through an approved canary. If the canary shows wrong audience, consent, or rendering behavior, stop promotion and restore the last approved configuration.

Overview

Deploy Customer.io integrations to production: GCP Cloud Run with Secret Manager, Vercel serverless functions, AWS Lambda with SSM, Kubernetes with external secrets, plus health check endpoints and blue-green deployment scripts.

Prerequisites

  • CI/CD pipeline configured (see customerio-ci-integration)
  • Cloud platform credentials and access
  • Production Customer.io credentials in a secrets manager

Instructions

Step 1: Deploy to Google Cloud Run


# .github/workflows/deploy-cloud-run.yml
name: Deploy to Cloud Run
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write  # Required for Workload Identity Federation
    steps:
      - uses: actions/checkout@v4

      - id: auth
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
          service_account: ${{ secrets.WIF_SA }}

      - uses: google-github-actions/setup-gcloud@v2

      - name: Build and push
        run: |
          gcloud builds submit --tag gcr.io/${{ secrets.GCP_PROJECT }}/cio-service

      - name: Deploy
        run: |
          gcloud run deploy cio-service \
            --image gcr.io/${{ secrets.GCP_PROJECT }}/cio-service \
            --region us-central1 \
            --set-secrets "CUSTOMERIO_SITE_ID=cio-site-id:latest,\
              CUSTOMERIO_TRACK_API_KEY=cio-track-key:latest,\
              CUSTOMERIO_APP_API_KEY=cio-app-key:latest" \
            --set-env-vars "CUSTOMERIO_REGION=us,NODE_ENV=production" \
            --min-instances 1 \
            --max-instances 10 \
            --memory 512Mi \
            --cpu 1 \
            --allow-unauthenticated

Step 2: Health Check Endpoint


// routes/health.ts
import { TrackClient, RegionUS } from "customerio-node";
import { Router } from "express";

const router = Router();

router.get("/health", async (_req, res) => {
  const checks: Record<string, { status: string; latency_ms?: number }> = {};

  // Check Track API
  const cio = new TrackClient(
    process.env.CUSTOMERIO_SITE_ID!,
    process.env.CUSTOMERIO_TRACK_API_KEY!,
    { region: RegionUS }
  );

  const start = Date.now();
  try {
    await cio.identify("health-check", {
      email: "health@internal.example.com",
      _health_check: true,
    });
    checks.track_api = { status: "ok", latency_ms: Date.now() - start };
  } catch (err: any) {
    checks.track_api = { status: `error: ${err.statusCode}` };
  }

  const allOk = Object.values(checks).every((c) => c.status === "ok");

  res.status(allOk ? 200 : 503).json({
    status: allOk ? "healthy" : "degraded",
    checks,
    version: process.env.npm_package_version ?? "unknown",
    uptime_seconds: Math.floor(process.uptime()),
    timestamp: new Date().toISOString(),
  });
});

export default router;

Step 3: Vercel Serverless Functions


// api/customerio/identify.ts (Vercel serverless function)
import type { VercelRequest, VercelResponse } from "@vercel/node";
import { TrackClient, RegionUS } from "customerio-node";

const cio = new TrackClient(
  process.env.CUSTOMERIO_SITE_ID!,
  process.env.CUSTOMERIO_TRACK_API_KEY!,
  { region: RegionUS }
);

export default async function handler(req: VercelRequest, res: VercelResponse) {
  if (req.method !== "POST") {
    return res.status(405).json({ error: "Method not allowed" });
  }

  const { userId, attributes } = req.body;
  if (!userId || !attributes?.email) {
    return res.status(400).json({ error: "userId and attributes.email required" });
  }

  try {
    await cio.identify(userId, {
      ...attributes,
      last_seen_at: Math.floor(Date.now() / 1000),
    });
    return res.status(200).json({ success: true });
  } catch (err: any) {
    return res.status(err.statusCode ?? 500).json({ error: err.message });
  }
}

Step 4: Kubernetes Deployment


# k8s/customerio-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: customerio-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: customerio-service
  template:
    metadata:
      labels:
        app: customerio-service
    spec:
      containers:
        - name: app
          image: gcr.io/my-project/cio-service:latest
          ports:
            - containerPort: 3000
          env:
            - name: CUSTOMERIO_SITE_ID
              valueFrom:
                secretKeyRef:
                  name: customerio-secrets
                  key: site-id
            - name: CUSTOMERIO_TRACK_API_KEY
              valueFrom:
                secretKeyRef:
                  name: customerio-secrets
                  key: track-api-key
            - name: CUSTOMERIO_APP_API_KEY
              valueFrom:
                secretKeyRef:
                  name: customerio-secrets
                  key: app-api-key
            - name: CUSTOMERIO_REGION
              value: "us"
            - name: NODE_ENV
              value: "production"
          resources:
            requests:
              cpu: 100m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 512Mi
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 15
            periodSeconds: 30
---
apiVersion: v1
kind: Service
metadata:
  name: customerio-service
spec:
  selector:
    app: customerio-service
  ports:
    - port: 80
      targetPort: 3000

Step 5: Blue-Green Deployment


#!/usr/bin/env bash
set -euo pipefail
# scripts/blue-green-deploy.sh

SERVICE="cio-service"
REGION="us-central1"
IMAGE="gcr.io/${GCP_PROJECT}/${SERVICE}:${COMMIT_SHA}"

echo "=== Blue-Green Deploy: ${SERVICE} ==="

# 1. Deploy with no traffic
gcloud run deploy "${SERVICE}" \
  --image "${IMAGE}" \
  --region "${REGION}" \
  --no-traffic \
  --tag "canary"

echo "Deployed canary. Running health check..."

# 2. Health check on canary
CANARY_URL=$(gcloud run services describe "${SERVICE}" \
  --region "${REGION}" --format 'value(status.url)' \
  | sed 's|https://|https://canary---|')

HEALTH=$(curl -s -o /dev/null -w "%{http_code}" "${CANARY_URL}/health")
if [ "${HEALTH}" != "200" ]; then
  echo "FAIL: Health check returned ${HEALTH}. Aborting."
  exit 1
fi

# 3. Shift traffic: 10% → 50% → 100%
for pct in 10 50 100; do
  echo "Shifting ${pct}% traffic to canary..."
  gcloud run services update-traffic "${SERVICE}" \
    --region "${REGION}" \
    --to-tags "canary=${pct}"
  sleep 30
done

echo "Deploy complete. 100% traffic on new revision."

Deployment Checklist

  • [ ] Production secrets in secrets manager (not env files)
  • [ ] Health check endpoint responds 200
  • [ ] Readiness and liveness probes configured
  • [ ] Resource limits set (CPU, memory)
  • [ ] Min instances > 0 (avoid cold starts)
  • [ ] Blue-green or canary deployment configured
  • [ ] Rollback procedure documented
  • [ ] Post-deploy smoke test automated

Error Handling

Issue Solution
Secret not found Verify secret name in secrets manager
Health check timeout Increase initialDelaySeconds, check CIO connectivity
Cold start latency Set --min-instances 1 (Cloud Run) or keep-alive
Memory OOM Increase memory limits, check for event queue buildup

Resources

Next Steps

After deployment, proceed to customerio-webhooks-events for webhook handling.

Ready to use customerio-pack?