Marketing & Advertising8 min read

How to Integrate Iterable with Your Headless CMS

Connect Iterable to a headless CMS so campaign, product, and editorial content can update email, SMS, push, and in-app messages from one structured source.

Published April 29, 2026
01Overview

What is Iterable?

Iterable is a cross-channel marketing platform for lifecycle teams that run email, SMS, mobile push, in-app, and web push campaigns. Teams use it to build journeys, segment audiences, personalize messages, and measure engagement across customer touchpoints. It’s commonly used by growth, CRM, and marketing operations teams at consumer apps, retailers, marketplaces, and subscription businesses.


02The case for integration

Why integrate Iterable with a headless CMS?

Marketing teams usually don’t struggle because Iterable is hard to send from. They struggle because the content feeding those sends is copied from five places. A product launch might need a landing page headline, an email subject line, a push notification, a promo code, a localized hero image, and legal copy. If those fields live in spreadsheets, tickets, and pasted HTML, every campaign becomes a manual QA job.,Connecting Iterable to a headless CMS category tool fixes the handoff between content and lifecycle marketing. With Sanity as the AI Content Operating System, campaign content is structured as typed JSON in the Content Lake. That means Iterable can receive exactly the fields it needs, such as title, summary, CTA URL, image URL, locale, segment tags, expiration date, and product references. No HTML scraping. No parsing page blobs. No waiting for a nightly CSV export.,The alternative is slower and riskier. Someone copies a product description into an Iterable template, another person updates the website, and a third person edits the mobile push copy. Two days later, the price changes. Now you’ve got inconsistent messages in market. With real-time webhooks and Functions, a publish event in Sanity can update an Iterable Catalog item in seconds, so the next triggered campaign uses the current approved content.


03Architecture

Architecture overview

A typical Sanity and Iterable integration starts when an editor publishes a campaign, product, article, or promotion document in Sanity Studio. The published document lands in the Content Lake as structured JSON. A GROQ-powered webhook fires only for the content types you care about, for example, _type == "promotion" or _type == "product". The webhook calls a Sanity Function or your own HTTPS listener with the document ID and mutation details. The server-side handler uses @sanity/client and GROQ to fetch the exact fields Iterable needs, including joined data such as product category, image asset URL, localized copy, and campaign metadata. The handler then calls Iterable’s REST API, usually the Catalogs API for reusable campaign content, such as PUT /api/catalogs/{catalogName}/items/{itemId}. For user-level events, it can also call POST /api/events/track. Iterable templates, journeys, and triggered campaigns can reference that catalog content when sending email, SMS, push, or in-app messages to the end user. Functions are a good fit when you don’t want to run a separate server for this sync logic. If you already have middleware for customer data, you can route the webhook there instead.


04Use cases

Common use cases

📣

Launch campaigns from approved content

Publish a promotion in Sanity and sync the headline, offer copy, CTA, image URL, and expiration date to an Iterable Catalog used by email and push campaigns.

🛒

Personalized product recommendations

Send product names, descriptions, categories, image assets, and landing URLs from Sanity into Iterable Catalogs so templates can render current product content.

🌎

Localized lifecycle messaging

Sync locale-specific copy from Sanity to Iterable fields like en-US, fr-FR, and de-DE so regional campaigns don’t depend on copied spreadsheet text.

⏱️

Time-sensitive offer updates

Update promo codes, legal disclaimers, sale end dates, and landing page links in Sanity, then push the change to Iterable before the next triggered send.


05Implementation

Step-by-step integration

  1. 1

    Set up Iterable access

    In Iterable, create or choose the project that will receive content. Generate a server-side API key with permission to update Catalogs and, if needed, send custom events. Confirm whether your project uses the US API base URL, https://api.iterable.com, or the EU base URL, https://api.eu.iterable.com. Create a catalog such as content_promos or products if you’ll reference synced content in templates.

  2. 2

    Model the campaign content in Sanity Studio

    Create schema fields that match how lifecycle marketers actually work. For a promotion, include title, slug, summary, heroImage, ctaUrl, promoCode, locale, segmentTags, expiresAt, and iterableCatalogName. If campaigns reference products, model those as references instead of duplicating product data in every promotion.

  3. 3

    Write the GROQ query

    Use GROQ to fetch only the fields Iterable needs. A promotion sync might project title, slug.current, summary, image asset URL, CTA URL, expiration date, locale, and referenced product fields. This keeps the payload small and prevents draft-only editorial notes from being sent to Iterable.

  4. 4

    Create the sync trigger

    Add a Sanity webhook filtered to published mutations for the content types you want to sync, or use a Sanity Function for server-side logic triggered by content changes. A typical filter is _type in ["promotion", "product"] so unrelated edits, such as internal documentation, don’t call Iterable.

  5. 5

    Push content into Iterable

    From the Function or webhook listener, call Iterable’s Catalogs API with the API key in the Api-Key header. Use a stable item ID, such as the Sanity document ID or slug, so later publishes update the same Iterable item instead of creating duplicates.

  6. 6

    Test the campaign path

    Publish a test document in Sanity, confirm the catalog item appears in Iterable, reference it in an email or push template, and send a test message to an internal user. Also test edits, unpublishes, missing images, expired offers, and locale fallback behavior.


06Code

Code example

typescriptiterable-sync.ts
import {createClient} from '@sanity/client'

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,
})

export default async function handler(req: Request) {
  const { _id } = await req.json()

  const promo = await sanity.fetch(`*[_id == $id][0]{
    _id,
    title,
    summary,
    locale,
    ctaUrl,
    expiresAt,
    "slug": slug.current,
    "imageUrl": heroImage.asset->url
  }`, {id: _id.replace('drafts.', '')})

  if (!promo) return new Response('No published document', {status: 202})

  const itemId = encodeURIComponent(promo.slug || promo._id)
  const baseUrl = process.env.ITERABLE_API_BASE || 'https://api.iterable.com'

  const iterableRes = await fetch(
    `${baseUrl}/api/catalogs/content_promos/items/${itemId}`,
    {
      method: 'PUT',
      headers: {
        'Api-Key': process.env.ITERABLE_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        value: {
          title: promo.title,
          summary: promo.summary,
          locale: promo.locale || 'en-US',
          imageUrl: promo.imageUrl,
          ctaUrl: promo.ctaUrl,
          expiresAt: promo.expiresAt,
        },
      }),
    }
  )

  if (!iterableRes.ok) {
    throw new Error(`Iterable sync failed: ${iterableRes.status}`)
  }

  return new Response('Synced to Iterable', {status: 200})
}

07Why Sanity

How Sanity + Iterable works

Build your Iterable integration on Sanity

Sanity gives you the structured content foundation, real-time event system, and flexible APIs to connect approved content with Iterable campaigns.

Start building free →

08Comparison

CMS approaches to Iterable

CapabilityTraditional CMSSanity
Campaign content structureCampaign copy often lives inside pages, making it hard to reuse the same offer in email, SMS, push, and in-app messages.Sanity Studio schemas can model promotion-specific fields like promoCode, locale, CTA URL, segment tags, legal copy, and expiration date.
Iterable catalog syncTeams often export CSV files or copy content into Iterable by hand, which creates stale offers and duplicate work.Webhooks and Functions can push published content directly to Iterable Catalogs without polling or a scheduled batch job.
Field-level controlAPIs may return rendered pages or large content objects, so the integration has to filter out layout and internal fields.GROQ can fetch the exact Iterable payload, including joined references for products, categories, images, and localized content.
Marketing team workflowEditors can publish pages, but lifecycle-specific approval steps often move to tickets, spreadsheets, or chat.Sanity Studio supports custom workflows with Comments, Tasks, Content Releases, and scheduled publishing for campaign content.
Multi-channel reuseWebsite pages are usually the main output, and other channels get copied versions of the same content.One structured back end can feed websites, apps, Iterable, Live Content API subscribers, and AI agents through Agent Context.
Setup trade-offInitial setup can feel familiar for editors, but integrations often become manual once campaigns span multiple channels.You’ll spend more time designing the schema and sync rules, but you get cleaner payloads and fewer manual campaign updates later.

09Next steps

Keep building

Explore related integrations to complete your content stack.

Ready to try Sanity?

See how Sanity's Content Operating System powers integrations with Iterable and 200+ other tools.