How to Integrate Customer.io with Your Headless CMS
Connect Customer.io to your headless CMS so published offers, newsletter modules, and audience-specific content can trigger email, SMS, push, and in-app campaigns without copy-paste.
What is Customer.io?
Customer.io is a customer engagement platform for sending behavior-based email, SMS, push, in-app, and webhook messages. Marketing, growth, and product teams use it to build customer journeys from profile attributes, events, segments, and campaign data. It sits in the marketing automation category alongside tools like Braze, Iterable, and Salesforce Marketing Cloud, with a strong focus on event-triggered messaging and data flexibility.
Why integrate Customer.io with a headless CMS?
Marketing campaigns break down when content lives in one place and customer journeys live somewhere else. A product launch page might be updated in your content system at 10:00 AM, while the Customer.io email still has last week's headline, expired pricing, or the wrong CTA. Someone ends up copying fields into Customer.io by hand, and every manual step adds delay and risk.
Architecture overview
A typical flow starts when an editor publishes campaign content in Sanity Studio. A Sanity webhook fires on the publish mutation, or a Sanity Function runs server-side from the same content event. The handler receives the document ID, uses GROQ to fetch only the fields required for the campaign, and sends those fields to Customer.io. For one-to-one lifecycle messages, you can call Customer.io's Track API through the customerio-node SDK and attach the content payload to an event. For a campaign sent to a segment, you can call Customer.io's App API, such as the API-triggered broadcast endpoint, with the campaign data and recipient segment. Customer.io then uses that payload in Journeys, newsletters, Liquid variables, email templates, SMS, push, in-app messages, or downstream webhooks. The end user receives a message that reflects the same structured content powering your site, app, and other channels.
Common use cases
Launch announcements
Publish a product announcement in Sanity, then trigger a Customer.io campaign with the headline, hero image, CTA, and target segment.
A/B campaign content
Model subject lines, preview text, CTAs, and content variants in Sanity, then pass the selected variant into Customer.io experiments.
Localized lifecycle messages
Use Sanity locale fields to send Customer.io the right copy, image, and link for each market without duplicating campaign setup.
Behavior-based recommendations
Combine Customer.io events with Sanity product, guide, or offer content so abandoned-cart, trial, and renewal messages use current copy.
Step-by-step integration
- 1
Set up Customer.io access
Create or open your Customer.io workspace, then copy your Site ID and Track API Key from Workspace Settings. If you're triggering broadcasts or campaigns from the App API, create an App API key and note your region, such as US or EU.
- 2
Install the packages
In your webhook handler or middleware project, install @sanity/client and customerio-node. Keep SANITY_PROJECT_ID, SANITY_DATASET, SANITY_READ_TOKEN, CUSTOMERIO_SITE_ID, and CUSTOMERIO_TRACK_API_KEY in environment variables.
- 3
Model campaign content in Sanity Studio
Create schema fields for campaignTitle, summary, image, ctaText, ctaUrl, locale, audienceKey, customerIoSegmentId, and publishedAt. Use references for products, categories, or offers so GROQ can join the related data into one payload.
- 4
Create the sync trigger
Add a Sanity webhook filtered to published campaign documents, for example _type == "campaignMessage" && !(_id in path("drafts.**")). Send a compact payload with the document ID to a Sanity Function, Next.js route, serverless function, or your own webhook listener.
- 5
Send the content to Customer.io
In the handler, fetch the full document from the Content Lake with GROQ, map Sanity fields to Customer.io event data or broadcast data, then call the Track API or App API. Add retries for 429 and 5xx responses, and log the Sanity document ID with the Customer.io request ID when available.
- 6
Test the end-to-end experience
Publish a test document in Sanity, confirm the webhook fired, inspect the Customer.io event or API-triggered campaign, and send to a test profile. Check Liquid variables, links, UTM values, images, localization, and unsubscribe behavior before sending to a real segment.
Code example
import { createClient } from "@sanity/client";
import { CustomerIO } from "customerio-node";
const sanity = createClient({
projectId: process.env.SANITY_PROJECT_ID!,
dataset: process.env.SANITY_DATASET!,
apiVersion: "2025-01-01",
token: process.env.SANITY_READ_TOKEN!,
useCdn: false
});
const cio = new CustomerIO(
process.env.CUSTOMERIO_SITE_ID!,
process.env.CUSTOMERIO_TRACK_API_KEY!
);
export async function POST(req: Request) {
const { _id, customerId } = await req.json();
const campaign = await sanity.fetch(
`*[_id == $id][0]{
_id,
campaignTitle,
summary,
ctaText,
ctaUrl,
locale,
"imageUrl": image.asset->url,
"product": product->{name, slug}
}`,
{ id: _id.replace("drafts.", "") }
);
await cio.track(customerId, {
name: "sanity_campaign_published",
data: {
contentId: campaign._id,
title: campaign.campaignTitle,
summary: campaign.summary,
ctaText: campaign.ctaText,
ctaUrl: campaign.ctaUrl,
imageUrl: campaign.imageUrl,
locale: campaign.locale,
productName: campaign.product?.name
}
});
return Response.json({ ok: true });
}How Sanity + Customer.io works
Build your Customer.io integration on Sanity
Sanity gives you the structured content foundation, real-time event system, and flexible APIs to connect Customer.io campaigns with the content your teams publish every day.
Start building free โCMS approaches to Customer.io
| Capability | Traditional CMS | Sanity |
|---|---|---|
| Campaign content structure | Campaign copy often lives inside pages or rich text blocks, so teams may need to copy fields into Customer.io manually. | Schemas in code let you model Customer.io-ready fields, such as audienceKey, customerIoSegmentId, locale, CTA, and offer references. |
| Real-time sync on publish | Updates may require scheduled exports, plugins, or manual handoffs before Customer.io has current content. | Webhooks and Functions can react to content mutations and send data to Customer.io without a separate job server. |
| Field-level payload control | Integrations may receive full pages, rendered HTML, or fields that need cleanup before use in messages. | GROQ can fetch exactly the fields Customer.io needs, including referenced products, offers, categories, and image URLs in one query. |
| Localization for campaigns | Localized email content is often duplicated across pages, spreadsheets, and campaign builders. | Editors can work with localized fields in Sanity Studio, then the integration can send locale-specific payloads to Customer.io. |
| AI content operations | AI-assisted updates often happen outside the editorial source, which can create review and governance gaps. | Content Agent and Agent API can help audit, transform, and translate structured campaign content before it reaches Customer.io. |
Keep building
Explore related integrations to complete your content stack.
Sanity + Google Ads
Send structured campaign landing page data from Sanity into Google Ads workflows for more consistent ad copy and destinations.
Sanity + HubSpot
Connect Sanity content with HubSpot forms, CRM records, and nurture flows for lead-generation campaigns.
Sanity + Mailchimp
Use Sanity as the source for newsletter modules, product updates, and localized email content sent through Mailchimp.