From cc350cfc6c8f42ac39c929b4cb4589cb75a49e9c Mon Sep 17 00:00:00 2001 From: "Customer.io Open Source Bot" Date: Fri, 5 Jun 2026 09:39:45 -0700 Subject: [PATCH] Add Customer.io CLI source CioCliPublicExport-RevId: 190433fdea9dde14a8be3ae72e36c8eb743341c4 --- skills/cio/SKILL.md | 121 +++++-------- skills/cio/billing.md | 106 ------------ skills/cio/integration.md | 356 -------------------------------------- skills/cio/onboarding.md | 221 ----------------------- 4 files changed, 46 insertions(+), 758 deletions(-) delete mode 100644 skills/cio/billing.md delete mode 100644 skills/cio/integration.md delete mode 100644 skills/cio/onboarding.md diff --git a/skills/cio/SKILL.md b/skills/cio/SKILL.md index 343d41e..6f07888 100644 --- a/skills/cio/SKILL.md +++ b/skills/cio/SKILL.md @@ -1,88 +1,59 @@ --- name: cio description: > - Customer.io CLI — use for Customer.io, Journeys, or CDP Pipelines tasks, - including getting started phrases like "I want to build with Customer.io", - onboarding, signup, sending a first email, campaigns, broadcasts, segments, - people, environments, billing, pricing, plans, signing secrets, sources, - destinations, track/identify events, `sa_live_` tokens, and - `fly.customer.io` / `cdp.customer.io` errors, even when the user does not - name the CLI. + Customer.io CLI - use for Customer.io, Journeys, CDP Pipelines, builder + onboarding, sandbox testing, app integration, SDK setup, transactional + messaging, billing activation, going live, service account tokens, sources, + destinations, identify/track events, and fly.customer.io / cdp.customer.io + errors. Trigger even when the user does not name the CLI. --- # Customer.io (`cio`) -Run `cio prime` first. It dumps the full LLM-ready reference -- commands, -flags, schemas, pagination, error shapes -- so you don't have to guess or -spelunk through `--help`. - -If `cio` is not installed, run `npm i -g @customerio/cli`. After installing, -run `cio prime`. If installation fails, fetch the README for other install -options: +This is the installable bootstrap skill for agents using the Customer.io CLI. +Run `cio prime` first; it prints the compact CLI reference, JSON output rules, +command syntax, schema introspection, pagination, error shapes, and +skill-reading guidance. ```bash -curl -fsSL https://raw.githubusercontent.com/customerio/cli/main/README.md +cio prime ``` -Everything below handles setup cases you might hit. - -## If the user wants to get started with Customer.io - -Follow [onboarding.md](onboarding.md) when the intent is about -starting, building, trying, or setting up Customer.io — even if they -have a project open. Account creation, authentication, domain setup, -and sending a first email all live here. If the user isn't -authenticated yet, onboarding is almost certainly the right path. - -Examples: "I want to build with Customer.io", "help me set up -Customer.io", "I want to try Customer.io", "set up a new account", -"send my first email". - -## If the user asks about plans, pricing, billing, or going live - -Follow [billing.md](billing.md) when the user asks what plan they are on, -whether they need to pay, how to launch, how to unlock external sends, or -anything about billing, subscriptions, credits, invoices, receipts, -payment methods, or plan limits. This includes billing questions that come up -during onboarding. - -Examples: "do I need to purchase a plan to go live?", "what does my plan -include?", "how much does this cost?", "can I send to real customers?", -"upgrade my plan", "buy credits", "what plan am I on?". - -## If the user wants to integrate Customer.io into their app - -Follow [integration.md](integration.md) when the user wants to add, -integrate, install, wire, hook up, or connect Customer.io to an existing -app or codebase. This covers SDK install, identify/track instrumentation, -in-app messaging, push notifications, and transactional sends from app -code. The signal is "I have a project open and want Customer.io in it" — -the user does not need to name an SDK. +If `cio` is not installed, fetch current installation instructions from the +README: -Examples: "add Customer.io to my app", "integrate Customer.io", "wire -Customer.io into this project", "install the Customer.io SDK", "send -transactional emails from my app", "track events from my backend", -"hook up identify calls". - -## If the user wants to open the Customer.io UI - -Run `cio auth login`. When the user is already authenticated, it prints -a one-click browser link — no password needed. If not authenticated, it -starts the interactive login flow (see auth error section below). - -Examples: "open the UI", "log into Customer.io", "open the dashboard", -"take me to Customer.io". - -## If a command fails with an auth error - -`cio auth login` auto-discovers the data center, so don't pass a region -flag. Offer the user two paths: - -- **User runs it themselves** (keeps the token out of chat): - `cio auth login` -- **You run it** — only if they explicitly paste the token. Pipe via - stdin so it doesn't hit shell history: - `echo "$TOKEN" | cio auth login --with-token` +```bash +curl -fsSL https://raw.githubusercontent.com/customerio/cli/main/README.md +``` -Service account tokens (`sa_live_...`) are at **Customer.io UI → Account -Settings → Manage API Credentials → Service Accounts**. +Use runtime references instead of copying large playbooks into this skill: + +- `cio schema` for commands, flags, generic API routes, and payload shapes. +- `cio skills` to list currently available Customer.io reference skills. +- `cio skills read cio` for builder onboarding, sandbox, first integration, + transactional proof, billing activation checks, and go-live playbooks. +- `cio skills read ` for the specific service-hosted reference needed. +- `cio api ` for API coverage that is not wrapped by a bespoke command. + +Common routing: + +- Builder/startup setup, signup, sandbox, first app integration, + transactional proof, billing activation checks, and go-live start with + `cio prime` and `cio skills read cio`. +- Existing Journeys resources such as campaigns, segments, templates, + customers, newsletters, and subscription topics should use + `cio skills read fly-api`. +- CDP Pipelines, sources, destinations, identify, track, page, and screen work + should use `cio skills read cdp-api`. +- Design Studio email/component editing should use + `cio skills read design-studio`. +- Analysis workflows, campaign review, goal-based segments, and Liquid should + use `cio skills read recipes`. + +Credential rule: + +- Service account tokens (`sa_live_...`) authenticate the CLI and account-level + management operations. +- Do not embed service account tokens in customer application code. +- Use CDP source write keys for SDK identify/track/page/screen ingestion. +- Use workspace-scoped App API keys for backend transactional sends. diff --git a/skills/cio/billing.md b/skills/cio/billing.md deleted file mode 100644 index 7fdcb5a..0000000 --- a/skills/cio/billing.md +++ /dev/null @@ -1,106 +0,0 @@ -# Billing: Plans, pricing, and go-live costs - -Use this when the user asks about pricing, plans, current limits, billing -status, invoices, receipts, payment methods, credits, or whether they need to -pay before launching. - -Keep the answer short, account-specific, and grounded in current sources. Do not -paste raw billing JSON unless the user asks for it. Do not quote prices, -minimums, message rates, or allowances from memory. - -## Workflow - -1. Check the account's current billing state. -2. If the account is on Builder, check the Builders page. Otherwise check the - current pricing page for the relevant plan or product path. -3. Explain what the user's current plan allows for their goal. -4. Point them to the next product-supported step in Account Settings, the - pricing page, or sales. - -Do not assume the user is on Builder, Essentials, Premium, or any other plan. -Do not infer a payment path from route names alone. -Do not say Builder is only for testing. Builder has a test mode and a -pay-as-you-send go-live path; use the current Builders page for the exact -minimum, rate, and unlock steps. - -## Check Billing State - -Get the account ID if you need it: - -```bash -cio auth status --jq '.account_id' -``` - -Then inspect billing. Match `subscription.plan_id` to `plans[].id`; do not -treat every item in `plans` as the current plan. - -```bash -cio api /v1/accounts/{account_id}/billing/info \ - --jq '. as $b | { - subscription: $b.subscription, - billing_info: ($b.billing_info | { - active, - active_subscription, - basic_trial, - premium_trial, - billing_estimates, - estimated_invoice_cost, - overdue, - has_failed_overage_charge, - can_retry_payment - }), - current_plan: ([$b.plans[]? | select(.id == ($b.subscription.plan_id // null) or .current == true)] | first) - }' -``` - -If the plan is still unclear, list account plan options: - -```bash -cio api /v1/accounts/{account_id}/billing/plans -``` - -Translate the result into plain language: - -- current plan or billing model -- active, trial, or inactive subscription state -- limits, allowances, or coverage shown for the account -- current usage or overage estimate, if relevant -- the next step shown by the product or current pricing page - -Do not call `tiers` or `tiers_count` usage, limits, or overages. Tiers are plan -pricing rows unless an endpoint explicitly labels them as current account usage. -Do not turn boolean flags like `has_overage` into a bill estimate by themselves; -use `billing_estimates` or an explicit overage endpoint for amounts. - -## Check Current Pricing - -Use WebFetch, a browser, or a search/docs tool to read these pages before -quoting exact pricing, limits, minimum purchases, or rates: - -- Builder / pay-as-you-send: https://customer.io/builders -- Paid plans: https://customer.io/pricing -- Billing mechanics and overages: https://docs.customer.io/accounts-and-workspaces/how-we-bill/ - -Do not scrape marketing pages with `curl`, `grep`, or partial HTML and then -guess. If you cannot read the page, link the source and avoid exact prices. - -## Going Live - -Say what the current account state means for the launch the user described: - -- If they can continue testing, say so. -- If `current_plan.name`, `display_name`, or `plan_type` is Builder, check the - Builders page first. Do not say Builder is testing-only or that Builder must - upgrade to a paid plan to send externally; use the Builders page for the - current pay-as-you-send/external-send unlock path. If you cannot read the - page, link it and say you could not verify exact current pricing. -- If the product directs them to upgrade, say which upgrade path the current - pricing page shows and link to that flow. -- If they need a payment method, billing access, or sales contact, say that. -- If they ask for exact cost, quote the current pricing source you checked and - link to it. - -Do not issue `POST`, `PUT`, `PATCH`, or `DELETE` requests to billing endpoints. -If the user wants to change billing, direct them to the Customer.io billing UI, -pricing page, or sales path unless a product owner explicitly instructs -otherwise. diff --git a/skills/cio/integration.md b/skills/cio/integration.md deleted file mode 100644 index 585600d..0000000 --- a/skills/cio/integration.md +++ /dev/null @@ -1,356 +0,0 @@ -# Integration: Add Customer.io SDK to the user's project - -Help the user integrate a Customer.io SDK into their application so they can identify users, track events, and optionally enable in-app messaging and push notifications. Adapt to the user's tech stack -- detect it from the codebase or ask. - -## Before you start - -1. Confirm `cio` is installed and load the CLI reference: - ```bash - command -v cio && cio prime - ``` - -2. Confirm auth is working: - ```bash - cio auth status - ``` - -3. Find the workspace (environment) ID: - ```bash - cio api /v1/accounts/{account_id}/environments --jq '.environments[] | {id, name}' - ``` - ---- - -## Step 1 -- Detect the user's tech stack - -Scan the project for framework markers: - -| Marker | SDK | -|--------|-----| -| `Podfile`, `Package.swift`, `.xcodeproj` | **iOS** (Swift) | -| `build.gradle`, `AndroidManifest.xml` | **Android** (Kotlin) | -| `react-native` in package.json | **React Native** | -| `expo` in package.json or `app.json` | **Expo** | -| `pubspec.yaml` with `flutter` | **Flutter** | -| `package.json` with server-side entry (no browser) | **Node.js** | -| `requirements.txt`, `pyproject.toml`, `setup.py` | **Python** | -| `go.mod` | **Go** | -| `index.html`, browser-side JS/TS project | **JavaScript** (browser) | - -If multiple apply (e.g. a monorepo with web + API, or mobile + backend), explore the project structure to understand the full stack and recommend the right integration points. For example, a Next.js app with a Python backend might need the browser SDK on the frontend for page views and the Python SDK on the backend for server-side events -- recommend both, but start with whichever is most natural for the user's immediate goal. Don't over-instrument: pick the minimal set of SDKs that covers their use case. One SDK is often enough. - -If the project doesn't match any of these, fall back to the **HTTP API** (cURL) approach. - ---- - -## Step 2 -- Get or create a CDP source - -Customer.io uses CDP Data Pipelines sources to ingest SDK data. Each SDK type maps to a source `option_id`: `ios`, `android`, `reactnative`, `expo`, `flutter`, `javascript`, `node`, `python`, `go`, `http`. - -### Check for existing sources - -```bash -cio api /cdp/api/workspaces/{workspace_id}/sources \ - --params '{"workspace_id": ""}' \ - --jq '.sources[] | {id, name, option_id: .slug, enabled}' -``` - -If a source matching the detected SDK already exists, use it. Skip to Step 3. - -### Create a new source - -```bash -cio api /cdp/api/workspaces/{workspace_id}/sources \ - --params '{"workspace_id": ""}' \ - --json '{"source": {"name": " Source", "option_id": ""}}' \ - --dry-run -``` - -Remove `--dry-run` after confirming with the user. The response includes a `source.id`. - -### Get the write key (API key) - -```bash -cio api /cdp/api/workspaces/{workspace_id}/sources/{source_id}/api_keys \ - --params '{"workspace_id": "", "source_id": ""}' \ - --jq '.api_keys[] | {id, api_key}' -``` - -If no API key exists, create one: - -```bash -cio api /cdp/api/workspaces/{workspace_id}/sources/{source_id}/api_keys \ - --params '{"workspace_id": "", "source_id": ""}' \ - -X POST -``` - -Save the `api_key` value -- this is the **write key** (also called `cdpApiKey` in mobile SDKs). - ---- - -## Step 3 -- Get credentials for the SDK - -The SDK needs different credentials depending on platform: - -### All SDKs need - -- **Write key** (CDP source API key from Step 2) -- **Region** -- US or EU - -Determine the region from the stored config or the account: -```bash -cio auth status --jq '.region' -``` - -### Mobile SDKs also need - -Mobile SDKs (iOS, Android, React Native, Expo, Flutter) additionally require a **site ID** for in-app messaging. Get it from the Journeys workspace: - -```bash -cio api /v1/environments/{environment_id}/site_ids \ - --params '{"environment_id": ""}' \ - --jq '.site_ids[0]' -``` - ---- - -## Step 4 -- Install the SDK and add initialization code - -Install the SDK package and add initialization code. Fetch the README for the SDK you need -- it has the install command and setup instructions. **Run the install command** (`npm install`, `pip install`, `flutter pub add`, etc.) -- don't just add an import and leave the package missing. Replace placeholders with the real write key, region, and site ID from Steps 2-3. - -| SDK | README | -|-----|--------| -| JavaScript (browser) | `https://raw.githubusercontent.com/customerio/cdp-analytics-js/main/packages/browser/README.md` | -| Node.js | `https://raw.githubusercontent.com/customerio/cdp-analytics-js/main/packages/node/README.md` | -| Python | `https://raw.githubusercontent.com/customerio/cdp-analytics-python/master/README.md` | -| Go | `https://raw.githubusercontent.com/customerio/cdp-analytics-go/master/README.md` | -| iOS (Swift) | `https://raw.githubusercontent.com/customerio/customerio-ios/main/README.md` | -| Android (Kotlin) | `https://raw.githubusercontent.com/customerio/customerio-android/main/README.md` | -| React Native | `https://raw.githubusercontent.com/customerio/customerio-reactnative/main/README.md` | -| Expo | `https://raw.githubusercontent.com/customerio/customerio-expo-plugin/main/README.md` | -| Flutter | `https://raw.githubusercontent.com/customerio/customerio-flutter/main/README.md` | - -For the **HTTP API** (no SDK), use the CDP API docs: https://docs.customer.io/api/cdp/ - -### Key parameters to supply - -- **Write key** (also called `cdpApiKey` in mobile SDKs) -- from Step 2 -- **Region** -- US or EU (the SDK README will explain how to configure this) -- **Site ID** (mobile SDKs only, for in-app messaging) -- from Step 3 - -### Store secrets properly - -Never hardcode API keys or write keys in source code. Store them using whatever secret/config mechanism the project already uses: - -- **`.env` / `.env.local`** (Next.js, Node, Python, etc.) -- write the key there directly. Use the framework's naming convention (e.g. `NEXT_PUBLIC_` prefix for client-side keys in Next.js). -- **`local.properties`** or Gradle build config (Android) -- write it there. -- **Xcode build settings / `.xcconfig`** (iOS) -- write it there. -- **`--dart-define` or `.env` with `envied`/`flutter_dotenv`** (Flutter) -- write it there. - -If the project already has a pattern for secrets (e.g. an existing `.env` file, a secrets manager, a config service), follow that pattern. If it doesn't have one and you can't determine the right approach, tell the user the keys they need to store and where, and let them decide. - ---- - -## Step 5 -- Instrument the app - -Don't stop at initialization. Explore the user's codebase and wire in actual calls at the right places. The goal is a working integration, not a README. - -### What to wire in - -- **`identify`** -- find where users sign up or log in (auth callbacks, session creation, login handlers) and add an `identify` call with the user's ID and profile attributes (email, name, etc.). Use a stable database ID as the userId, not email. -- **`track`** -- find the key user actions in the app (form submissions, purchases, content views, button clicks) and add `track` calls with descriptive event names and relevant properties. -- **`page`/`screen`** -- for web apps, fire `page()` on route changes. For mobile, fire `screen()`. If the app uses a router (Next.js, React Router, etc.), hook into navigation events. - -### How to find instrumentation points - -Read the app's code to understand the user flows. Look for: -- Auth logic (sign up, login, OAuth callbacks, session hydration) -- Form handlers and API calls that represent user actions -- Route definitions and navigation guards -- E-commerce flows (add to cart, checkout, purchase) -- Content interactions (views, likes, shares) - -Pick the highest-value events for the app's domain. A blog needs "Post Viewed"; an e-commerce app needs "Order Completed". Don't over-instrument -- start with 3-5 meaningful events. - -### Important notes - -- Always call `identify` before `track` so events are attributed to the right person -- Server-side SDKs (Node, Python, Go) require `userId` on every `track` call; client-side SDKs remember it after `identify` -- `created_at` should be a Unix timestamp (seconds) for proper timeline display - ---- - -## Step 6 -- Send transactional emails (optional) - -If the user wants to send transactional emails (password resets, order confirmations, receipts, etc.), this is done via a direct HTTP call to the **Track API**. None of the modern SDKs support transactional sending -- it's HTTP-only. - -The CLI has built-in commands for this, or the user can call the API directly from their code. - -### Using the CLI - -```bash -# One-off email (no template needed) -cio send email --environment-id \ - --to user@example.com \ - --from "Acme " \ - --subject "Your order shipped" \ - --body "

Order #123 is on its way

" - -# Template-based transactional email -cio transactional send email --environment-id \ - --transactional-message-id 1 \ - --to user@example.com \ - --message-data '{"name":"Alice","order_id":"123"}' - -# List available transactional templates -cio transactional list --environment-id -``` - -### From code (HTTP API) - -For production backend code, **issue a workspace-scoped App API key** — do not embed the `sa_live_` SA token. SA tokens are account-level (full access, all workspaces); App API keys are scoped to a single workspace and are the right fit for backend integrations. - -Create one via the CLI (one-time setup): - -```bash -cio api /v1/environments/{environment_id}/ext_api_keys -X POST \ - --json '{"ext_api_key":{"name":"backend-prod"}}' -``` - -The response includes the full Bearer value on creation — capture it and copy into the backend's env var (e.g. `CIO_APP_API_KEY`). Subsequent `GET .../ext_api_keys` calls return only a hint (last few characters), not the full key. Lost keys can't be recovered — issue a new one. - -Endpoints — one per channel, all share a common base shape: - -| Channel | Path | -|---------|------| -| Email | `POST /v1/send/email` | -| Push | `POST /v1/send/push` | -| SMS | `POST /v1/send/sms` | -| In-app | `POST /v1/send/in_app` | -| Inbox | `POST /v1/send/inbox_message` | - -App API base URL varies by region: - -| Region | App API base URL | -|--------|------------------| -| US | `https://api.customer.io` | -| EU | `https://api-eu.customer.io` | - -The HTTP shape is the API contract — produce the equivalent in whatever language the user is using. - -```http -POST https://api.customer.io/v1/send/email -Authorization: Bearer -Content-Type: application/json - -{ - "transactional_message_id": "order_confirmation", - "auto_create": true, - "identifiers": { "id": "user-123" }, - "to": "user@example.com", - "message_data": { "name": "Alice", "order_id": "123" } -} -``` - -For one-off emails without a template, include the content inline (no `transactional_message_id` needed): - -```http -POST https://api.customer.io/v1/send/email -Authorization: Bearer -Content-Type: application/json - -{ - "to": "user@example.com", - "identifiers": { "email": "user@example.com" }, - "from": "Acme ", - "subject": "Your order shipped", - "body": "

Order #123 is on its way

" -} -``` - -The `X-Workspace-Id` header is **not** needed when using an App API key — the key is already workspace-scoped. (The header is only needed for SA-token-based calls, which the CLI itself handles internally.) - -### The `auto_create` paradigm — simplest pattern for backend sends - -Pick a stable string identifier (`"order_confirmation"`, `"password_reset"`, `"shipping_update"`) and pass it as `transactional_message_id` along with `auto_create: true`. The first call creates a transactional message in the workspace with that name and the channel matching the endpoint you hit (`/v1/send/email` → email-typed message, `/v1/send/push` → push, etc.). Subsequent calls find and reuse the existing message. Keep `auto_create: true` on every send — it's idempotent. - -Name constraints: must be a non-numeric string, non-empty, ≤ 191 unicode characters. A name that parses as a number (e.g. `"42"`) is rejected; `auto_create` is text-only. - -**When NOT to use `auto_create`:** - -- **In-app and inbox messages** — auto-created templates have no `body_json`, so deliveries queue successfully but render empty. Create the message explicitly via the management API or UI first, then configure the template. -- **When the template is authored ahead of time** (e.g. the email body is built in Design Studio or the UI). Create the message explicitly and call with the resulting numeric ID or string name; omit `auto_create`. - -### Channel notes - -- **Email:** can send *with* a `transactional_message_id` (templated) or *without* (inline content via `to`, `from`, `subject`, `body`, `body_plain`). -- **Push, SMS, in-app, inbox:** always require a `transactional_message_id`. -- The full per-channel field list (push `custom_payload`, SMS `tracked`, etc.) is in the API reference: https://customer.io/docs/api/app/#tag/Transactional - -Docs: https://docs.customer.io/journeys/transactional-email/ · https://customer.io/docs/api/app/#tag/Transactional - -### Important notes - -- For backend code, use a workspace-scoped App API key (Bearer auth against `api.customer.io`) — not the `sa_live_` SA token. The CLI's own `cio send` and `cio transactional send` commands use the SA token internally, which is fine for testing, but production code should use a per-workspace App API key. -- Do NOT retry failed sends automatically -- retrying a POST risks duplicate deliveries -- For EU regions, use `https://api-eu.customer.io` - ---- - -## Step 7 -- Verify the integration - -### Check source status - -```bash -cio api /cdp/api/workspaces/{workspace_id}/sources/{source_id}/status \ - --params '{"workspace_id": "", "source_id": ""}' -``` - -### Check for the identified user in the workspace - -After sending an `identify` call from the app, verify the profile was created: - -```bash -cio api /v1/environments/{environment_id}/customers \ - --params '{"environment_id": "", "filter": "email = \"user@example.com\""}' \ - --jq '.customers[] | {id, email}' -``` - -### Check recent events on a source - -```bash -cio api /cdp/api/workspaces/{workspace_id}/sources/{source_id}/events \ - --params '{"workspace_id": "", "source_id": "", "limit": "5"}' \ - --jq '.events[] | {type, event: .properties.event, userId: .properties.userId, timestamp}' -``` - ---- - -## Troubleshooting - -### Events not appearing - -1. Verify write key is correct (not the App API key or site ID) -2. Check region matches -- US workspace needs US endpoint, EU needs EU -3. Ensure `identify()` is called before `track()` -4. For server-side SDKs, check that the SDK is flushing (call `close()` or `flush()`) - -### Auth errors (401) - -- Write key goes in Basic auth header as username, password is empty -- Mobile SDKs use `cdpApiKey` parameter, not Basic auth -- Don't confuse the CDP write key with the Journeys Track API key or App API key - -### In-app messages not showing - -- `identify()` must be called first -- SDK must be initialized with `siteId` (mobile) or the In-App Plugin (web) -- User must be in the foreground with the app/page visible -- Check in-app messaging is provisioned: verify via the Customer.io UI under Settings > In-App - -### Push notifications not arriving - -- **iOS**: test on a real device (not simulator), add Notification Service Extension -- **Android**: verify `google-services.json` placement, create notification channels, request `POST_NOTIFICATIONS` on Android 13+ -- **React Native**: run `pod install` for iOS after package changes -- **Expo**: use Development Build, not Expo Go; run `npx expo prebuild` diff --git a/skills/cio/onboarding.md b/skills/cio/onboarding.md deleted file mode 100644 index 81c8bca..0000000 --- a/skills/cio/onboarding.md +++ /dev/null @@ -1,221 +0,0 @@ -# Onboarding: Sign up, set up a domain, send your first email - -Guided interactive onboarding for Customer.io. The goal is always to **send a test email** — that's the finish line. Ask before acting, confirm each step succeeds before moving on. - -## Project context - -If you have access to the user's project codebase (e.g. they invoked this from a repo directory), use it to personalize the experience from the start: - -- **Company name**: infer from `package.json`, `setup.py`, `go.mod`, repo name, or similar metadata -- **Sending domain**: infer from the project's domain, homepage URL, or email references in the code -- **User name/email**: infer from git config (`user.name`, `user.email`) or environment - -Present these as suggestions and **wait for the user to confirm or override before proceeding**. Never use inferred values to call a command without explicit confirmation first. - -## Before you start - -1. Load the full CLI reference: - ```bash - cio prime - ``` - -2. Check where the user is and skip completed steps (tell the user what you're skipping and why): - - `cio auth status` returns `"verified": true` -> skip to Step 2; note the authenticated email for Step 5 - - Already knows their environment ID -> skip to Step 3 - - Already has a verified sending domain -> skip to Step 4 - ---- - -## Step 1 -- Authenticate - -Determine if the user has an existing account or needs a new one. - -### Path A: New account (signup) - -**1a. Collect signup details** -- before calling any command, confirm the following with the user: - -- **Email address** to sign up with (suggest from project context if available, but ask) -- **New account or existing?** — make sure the user actually wants to create a new account - -Only proceed to 1b after the user confirms. - -**1b. Start signup** -- emails a 6-digit verification code: - -```bash -cio auth signup start --json '{"email":""}' -``` - -If the email is already in use (4xx), switch to Path B. If you get a 429 (rate limited), too many signups were attempted recently — tell the user to wait a few minutes and try again. If the verification email never arrived, wait, check spam, retry. - -**1c. Verify** -- creates the account and returns a bootstrap token: - -```bash -cio auth signup verify --json '{ - "email": "", - "code": "<6-digit-code>", - "company_name": "", - "first_name": "", - "last_name": "", - "data_center": "" -}' -``` - -Ask the user which data center (US or EU). Default to US only if they have no preference. On success, credentials are saved to `~/.cio/config.json`. - -### Path B: Existing account (login) - -```bash -cio auth login -``` - -Prints a URL. User opens it, logs in, copies the `sa_live_...` token, pastes it back. CLI stores it and auto-discovers the data center. Alternatively: - -```bash -echo "sa_live_..." | cio auth login --with-token -``` - -> **Note on tokens:** the `sa_live_` token authenticates the CLI itself (account-level, used internally for `cio api`, `cio send`, etc.). It should **not** be embedded in the user's production backend code. For backend integrations, issue a workspace-scoped App API key — see [`integration.md`](integration.md) Step 6. - -### Verify auth - -```bash -cio auth status -``` - -If 401: token may be invalid or expired -- retry `cio auth login`. - ---- - -## Step 2 -- Find the workspace - -```bash -cio api /v1/accounts/{account_id}/environments --jq '.environments[] | {id, name}' -``` - -New accounts typically have one workspace. If multiple, pick the first one that's ready to send (has a verified domain + from address). Only ask the user to choose if none are ready or you need to decide which to set up. - -### Check existing setup (Path B only) - -For existing accounts, check what's already configured: - -```bash -cio domains --env-id list --jq '.domains[] | {id, domain, verified}' -cio domains --env-id from_addresses list --jq '.identities[] | {id, name, email}' -``` - -- Verified domain + from address -> go directly to Step 5 and send the test email. Do not present a menu of options. The user asked to build with Customer.io — sending is the goal. -- Domain but no from address -> skip to Step 4 -- Domain with unverified DNS -> offer `cio domains --env-id configure --cname email`, or skip to Step 4 - ---- - -## Step 3 -- Add and configure a sending domain - -Ask which domain they'll send from. Suggest a domain from the company name if known. - -### 3a. Add the domain - -```bash -cio domains --env-id add -``` - -The response includes a `domain_connect_url` — ignore it. Always use `cio domains configure` (next step) for the one-click DNS setup flow, which supports link tracking. - -### 3b. Configure DNS with one-click setup - -```bash -cio domains --env-id configure -``` - -Prints a URL for the one-click DNS setup flow (auto-configures MX, SPF, DKIM, DMARC). Tell the user to open it. - -**Link tracking (optional):** Only pass `--cname` if the user specifically asks for link tracking. When used, it requires a subdomain value (e.g. `--cname email` for `email.`, or `--cname track` for `track.`). Do not pass `--cname` by default. - -### 3c. Verify DNS records - -```bash -cio domains --env-id verify -``` - -If records are failing, show what's missing. DNS propagation may take hours -- proceed and re-run verify later. - ---- - -## Step 4 -- Add a from address - -Use the company name as display name and `hello@` as the email. Confirm with the user before creating. - -```bash -cio domains --env-id from_addresses add \ - --name "" \ - --email "" -``` - ---- - -## Step 5 -- Send a test email - -Send a test email to the email the user signed up with or authenticated as — it is automatically opted in. - -**Note:** The send command uses `--environment-id` (not `--env-id` like domain commands). Pass `--from` as a bare email address with no display name. - -Compose a polished HTML body — clean typography, a centered card, friendly tone. Personalize the subject and body with the company name when known. Don't reuse the literal example below verbatim; treat it as a baseline and write something that feels designed, not debug output. - -```bash -cio send email --environment-id \ - --to \ - --from \ - --subject "Welcome to Customer.io" \ - --body '

You are all set 🎉

Your Customer.io account is configured and your sending domain is live.

This is the first email from your new setup — nice work.

— The Customer.io team

' \ - --watch -``` - -Email may land in spam until DNS records are verified. - ---- - -## Step 6 -- Explain go-live costs when the user is ready - -The onboarding finish line is still a test email. If the user asks about -launching, sending to real customers, plan limits, or go-live costs, follow -[billing.md](billing.md). - ---- - -## Done — what to suggest next - -**Do not present a menu of Customer.io features as next steps.** Do not -list Journeys, Campaigns, Broadcasts, Segments, In-app, Push, SMS, -Pipelines, or SDK integration as options for the user to pick from -unless you have already checked the user's plan and confirmed each one -is included on it. Listing them and then offering to "check what's -ready" is the same failure as listing them outright — it implies they -are all available. - -The right flow is the inverse: - -1. Ask the user what they want to build or do next, in their own terms - (e.g. "send a welcome email when someone signs up", "send order - receipts from my app"). Don't seed the answer with product names. -2. Once you know the goal, follow [billing.md](billing.md) to check the - account's plan and confirm — from live sources, not memory — whether - the plan supports that goal. -3. If the plan supports it, help them build it. If it doesn't, say so - plainly and link the upgrade path from [billing.md](billing.md). - Don't soften it into "might need an upgrade". - -Do not characterize the user's plan from memory or training data. In -particular, do not say any of the following unless -[billing.md](billing.md) and the live source confirm it for the account: - -- That **Builder** is a free tier, sandbox, trial, or testing-only plan. - It is a real plan with its own go-live path. -- That the user must **upgrade to a paid plan** to send to real - customers, go live, or use Customer.io for production. -- That a specific feature is included or excluded on the user's plan. - -If the user has a project codebase open, integration help that fits -their plan and goal is usually a strong next step; otherwise ask where -the project is before suggesting integration work. -[integration.md](integration.md) covers SDK and transactional-from-code -paths.