# Customer.io in a lifecycle audit

Where each audit area lives in Customer.io, how to read it without write access, and the behaviour that changes a finding. Facts were read on 2026-11-01 from the Customer.io pages in `sources.yaml` (the MCP, events and message limits pages again on 2026-11-02) unless marked "verify live".

## Read-only access

- MCP server: `https://mcp.customer.io/mcp` for US and EU accounts; `https://mcp-eu.customer.io/mcp` only when an EU account cannot connect to the first (read 2026-11-02). Each user connects with their own login and role.
- Grant only the default `read` scope. Deny `read:sensitive`, `write`, `write:live` and `configure`; they are independent scopes, and a prompt asking for analysis does not remove a write scope already granted.
- An account admin must turn on Customer.io MCP (Settings > AI on 2026-11-01; verify the path live). Leave the live-data and sensitive-data toggles off for an audit.
- Use only `cio_auth_status`, `cio_prime`, `cio_schema` and `cio_read_api`. Discover GET endpoints with `cio_schema`; never call the write or delete tools.
- For terminal agents, Customer.io's docs say its CLI is usually a better fit than MCP (read 2026-11-02). The CLI is `cio`, installed by the user with `npm install -g @customerio/cli`.
  - The user creates a user service account token with the Read-only option, which permanently restricts it to GET requests, and signs in with `cio auth login` in their own terminal. The token never passes through chat; `cio auth status` confirms the session.
  - With a read-only token, the agent may run read commands. If the user has no read-only token, use the MCP `read` scope or exports instead; do not run the CLI with a token that can write.
  - `cio schema` confirms an endpoint exists before the agent relies on it.
- Customer.io's official plugin has a workspace-health command: a read-only, Customer.io-only check of deliverability, pipelines and stalled automations from a vendor recipe. Run it alongside this audit; it does not cover other senders, cross-sender caps, preference propagation, holdouts or coverage against source counts.

## Where each area lives

- Journey coverage: workflows (campaigns) and broadcasts, with their triggers, filters, exit conditions and frequency settings.
- Triggers: event, attribute, segment, date, form, object and relationship triggers; the data in via Pipelines, Track API, SDKs or CSV imports.
- Caps: message limits in workspace settings, assigned per workflow or per message; auto-retry settings per limit.
- Preferences: the subscription centre with topics and channels; the `unsubscribed` attribute; consent records.
- Holdout: A/B test variations used as holdouts, random cohort branches, or a profile attribute filtered in every workflow.
- Deliverability: sending domains and their authentication status (verify the settings location live).

## Behaviour that changes findings

- Message limits apply to nothing until assigned to a workflow or message (FC02). Windows are relative per person from delivery timestamps, up to 31 days. In-app, inbox and Slack messages are not limited. WhatsApp counts toward the SMS limit only in workspaces that integrated Twilio before 30 July 2026; otherwise check WhatsApp's own limit assignment (verify live).
- Auto-retries of limit-blocked messages continue after the person exits the automation; the retry window is at most 48 hours, and Customer.io advises a window at least as long as the limit's time frame. Turning retries off lets scheduled attempts finish. One-time sends and transactional messages are not auto-retried (FC06).
- `unsubscribed` blocks email, SMS, WhatsApp and push but not in-app or Slack. Topics and channels combine: a person must be subscribed to both. No per-topic-per-channel preference. Opt-out topics subscribe people by default. Consent records do not decide delivery. Webhooks bypass all subscription checks (PC04, PC05, PC06).
- Events with an `id` dedupe for 30 days after receipt; through the Track API the `id` must be an uppercase ULID or it is replaced and cannot dedupe; through Pipelines and the SDKs a `messageId` over 191 characters drops the event (TD03).
- Events timestamped within the past 72 hours can trigger automations; attribute and segment triggers can admit historical profiles unless a time condition is added (TD04).
- Event, object, relationship, date and form triggers can retry an initially unmet filter for up to 30 minutes (Customer.io journeys page; verify live). This is separate from a send-time recheck later in the journey.
- For a person in several accounts, a relationship-based segment changes membership only for the first qualifying relationship, so a second matching relationship does not start another journey; a relationship-triggered automation handles each relationship (Customer.io object relationships page; verify live). Check this before rating JC02 on account journeys.
- Exit conditions: meeting any one ends the journey; with none set, people complete the workflow.
- Holdouts: the test's sending behaviour must be Queue Draft or Send Automatically; the live variant always wins on opens and clicks, so judge on conversions.

## Exports for the scripts

- Send log: delivery data through reporting webhooks, the data warehouse sync, or exports (verify the current names live), hashed in the warehouse.
- Preference log: subscription and topic changes with their source. Map Customer.io sources to the script's source names (preference page to preference_centre, unsubscribe link to link, API writes from your sync to api_sync or warehouse_sync).
- Inventory: workflow and broadcast lists with status, trigger, limits assignment and last entry.

## Verify live

API endpoints that expose message limits, holdout settings and subscription centre configuration; export and warehouse sync names; relationship-trigger behaviour; the MCP admin setting path; sending behaviour semantics for JC09; CLI command names after installing.
