Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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".
.claude/skills/jeremylongshore-customerio-deploy-pipeline/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 50% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 65% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 60% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 31% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 230% | 0% |
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.
customerio-ci-integration)yaml# .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
typescript// 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;
typescript// 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 }); } }
yaml# 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
bash#!/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."
| 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 |
After deployment, proceed to customerio-webhooks-events for webhook handling.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 12,998 | 7,972 | -39% | 1 | 1 | 0% | 2,723 | 4,097 | +50% | 0 | 0 | — |
case-02 | fail→pass | 13,725 | 10,191 | -26% | 1 | 1 | 0% | 2,897 | 4,771 | +65% | 0 | 0 | — |
case-03 | pass→pass | 11,779 | 8,156 | -31% | 1 | 1 | 0% | 2,540 | 4,277 | +68% | 0 | 0 | — |
case-04 | fail→pass | 16,273 | 12,717 | -22% | 1 | 1 | 0% | 3,334 | 5,345 | +60% | 0 | 0 | — |
case-05 | pass→pass | 16,056 | 12,928 | -19% | 1 | 1 | 0% | 3,260 | 5,016 | +54% | 0 | 0 | — |
case-06 | fail→pass | 16,769 | 8,476 | -49% | 1 | 1 | 0% | 2,974 | 3,899 | +31% | 0 | 0 | — |
case-07 | pass→pass | 14,976 | 11,109 | -26% | 1 | 1 | 0% | 2,690 | 4,293 | +60% | 0 | 0 | — |
case-08 | pass→pass | 8,185 | 7,755 | -5% | 1 | 1 | 0% | 1,627 | 3,937 | +142% | 0 | 0 | — |
case-09 | pass→pass | 10,674 | 11,102 | +4% | 1 | 1 | 0% | 1,848 | 4,245 | +130% | 0 | 0 | — |
case-14 | pass→pass | 11,034 | 6,004 | -46% | 1 | 1 | 0% | 2,109 | 3,440 | +63% | 0 | 0 | — |
case-10 | pass→pass | 9,605 | 2,463 | -74% | 1 | 1 | 0% | 1,989 | 2,788 | +40% | 0 | 0 | — |
case-11 | fail→pass | 4,432 | 2,460 | -44% | 1 | 1 | 0% | 847 | 2,794 | +230% | 0 | 0 | — |
case-12 | pass→pass | 7,228 | 4,034 | -44% | 1 | 1 | 0% | 1,571 | 3,185 | +103% | 0 | 0 | — |
case-13 | fail→pass | 4,816 | 2,745 | -43% | 1 | 1 | 0% | 829 | 2,835 | +242% | 0 | 0 | — |
case-15 | pass→fail | 11,344 | 10,984 | -3% | 1 | 1 | 0% | 2,086 | 4,186 | +101% | 0 | 0 | — |
case-16 | pass→pass | 3,104 | 3,124 | +1% | 1 | 1 | 0% | 548 | 2,801 | +411% | 0 | 0 | — |
case-17 | pass→pass | 12,784 | 9,496 | -26% | 1 | 1 | 0% | 2,505 | 4,032 | +61% | 0 | 0 | — |
case-18 | pass→pass | 6,052 | 2,984 | -51% | 1 | 1 | 0% | 1,073 | 2,875 | +168% | 0 | 0 | — |
case-19 | pass→pass | 2,668 | 3,666 | +37% | 1 | 1 | 0% | 476 | 2,873 | +504% | 0 | 0 | — |
case-20 | pass→pass | 13,516 | 11,046 | -18% | 1 | 1 | 0% | 2,795 | 4,592 | +64% | 0 | 0 | — |
case-21 | pass→pass | 13,773 | 17,661 | +28% | 1 | 1 | 0% | 2,862 | 6,028 | +111% | 0 | 0 | — |
case-22 | fail→pass | 10,499 | 10,268 | -2% | 1 | 1 | 0% | 1,930 | 4,079 | +111% | 0 | 0 | — |
case-23 | pass→pass | 10,183 | 9,843 | -3% | 1 | 1 | 0% | 1,949 | 4,441 | +128% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +26 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.