PN·MINDCORE / CMS·EVAL
Plan.Net / Mindcore · CMS Vendor Evaluation · Architecture Board Edition

BMW·MINI AEM Replacement
CMS Shortlist Datasheet

Contentful vs. Contentstack vs. Sanity — the three vendors our discovery repo already integrates behind one provider-neutral port. Written the way an architect defends it: every hard number links to the vendor's official documentation, unknowns are marked unverified and converted into meeting questions.

VERIFIED two passes: 2026-07-06 + 07-07 · official docs only
SHORTLIST repo-confirmed · 3 working adapters, runtime-switchable
SCOPE 50+ models × 30+ markets · HQ→market inheritance
SOURCING no link = our professional judgment, marked as such
WORKSHOP PACKS

This is the shared technical dossier + executive layer. Each vendor is met on a separate day — the one-day workshop pack for that day (agenda, live demo scripts, evidence checklist, scorecard) is a dedicated companion document: Contentful pack · Contentstack pack · Sanity pack. Read this dossier for the “why”; run the pack on the day for the “show us.”

DEAL RISK

Salesforce signed a definitive agreement to acquire Contentful on June 1, 2026 — per the official release, “expected to close in the third quarter of Salesforce's fiscal year 2027, subject to customary closing conditions, including the receipt of required regulatory approvals” (≈ Aug–Oct 2026 — Salesforce's fiscal year ends Jan 31 per its investor-relations releases ir; re-confirm directly before any contractual reliance). No customer-facing FAQ, price, or standalone-roadmap guarantee published as of 2026-07-07 — re-check at the expected close window (Aug–Oct 2026). Contentful's ownership, roadmap and renewal economics are in transition during BMW's decision window. salesforce.com contentful.com

CONTEXT

BMW already runs central-governance content on a shortlist vendor: Contentful's case study (vendor-published) describes 160+ BMW and 147 MINI dealer websites across EMEA centrally governed with a three-tier “fixed / flexible / free” content model — central control with dealer-level freedom contentful.com. And BMW China replaced AEM with Magnolia for dealer communication platforms, deployed on-premises (vendor-published) magnolia-cms.com. Assume the room knows both; we should reference them first.

01

Executive verdict

Not a generic two-horse race — a choice between three architectural compromises, and the weighting decides it. Contentstack is the governance-native platform: the only fully native enforced-approval machinery, and it leads the governance- and engineering-weighted scoring profiles. Contentful is the inheritance-native platform: the strongest native field-level HQ→market localization, leading the balanced and multi-market profiles — with the Salesforce acquisition as its wildcard. Sanity is the engineering-native platform: it proves BMW's inheritance pattern best (our deployed PoC) but shifts approval governance and parts of compliance onto the implementation layer. Section 11 shows the profile split live — that split, not a single winner, is the decision BMW has to make.

Contentful

The incumbent-grade composable platform: best localization machinery of the three, deepest hands-on experience on our side — now with an ownership asterisk.

Strengths

  • Native multi-hop locale fallback chains (de-CH→de-AT→de-DE) + locale-scoped roles + per-locale publishing
  • TISAX-assessed, ISO 27001:2022, SOC 2, EU residency add-on — the cleanest compliance sheet here
  • Our deepest adapter: import pipeline, locale-scoped publish, market tags all running today

Risks

  • Salesforce deal pending close — zero written roadmap/pricing commitments
  • 50 fields per content type; CMA 10 req/s per space; 4 publish rules per workflow step
  • Workflow enforcement at the API layer is not documented — admin & token bypass unclear
The board will value: the localization model and ecosystem maturity. The board will challenge: buying a company mid-acquisition, and the 50-field cap forcing model decomposition.
Contentstack

The governance machine: the only vendor whose docs natively satisfy the enforced-approval requirement, with the best permission granularity — undermined by its localization fallback semantics.

Strengths

  • Publish Rules: stage-gated publishing, approvers by role, four-eyes — fully native Draft-block
  • Permissions down to field, language, environment, taxonomy and asset folder + SCIM API (beta)
  • 500 fields/content type; native Personalize + Lytics CDP; Agent OS + MCP

Risks

  • Localizing an entry permanently breaks fallback inheritance — field-level HQ→market fails post-edit
  • CMA writes 10 req/s per ORGANIZATION (all stacks shared); bulk ops 1 req/s
  • No TISAX; SLA 99.50% except top tiers; docs vs. legal contradict (3 vs 5 envs)
The board will value: the approval workflow demo and market-editor isolation. The board will challenge: the org-wide write ceiling and the localization detach trap.
Sanity

The content platform as a codebase: strongest modeling and the HQ→market pattern we already proved in our deployed PoC — governance is real but ours to build.

Strengths

  • Schema-as-code in git, arbitrary nesting, JS validation, native focal point — no forced decomposition
  • Our Phase-0 Studio proves field-level HQ→market fallback (GROQ coalesce) end-to-end
  • Independent + funded ($85M Series C, May 2025); MCP GA, Functions, App SDK; EU (Belgium) data by default

Risks

  • No native workflow: approval = custom build; official plugin is advisory-only (UI-level, not API-enforced)
  • Governance is an Enterprise paywall; permissions stop at document level (no field-level)
  • No published SLA %, no own ISO 27001, no contractual EU region pinning
The board will value: the live coalesce demo and developer velocity. The board will challenge: "workflow is a convention," and the compliance fine print.

02

Non-negotiable decision gates

The verdict above is a hypothesis. These eight gates are how it gets tested — each must be demonstrated live in the vendor workshop, not answered on a slide. A vendor that fails a gate is not disqualified by opinion; it is disqualified by its own demo. The "who's positioned where" column is our read from the documented evidence in §04–§14 — the workshop confirms or overturns it. Detailed per-vendor demo scripts (setup → steps → expected evidence → pass/fail) live in each vendor's workshop pack.

GateWhat must be demonstrated (not described)Where each vendor stands going in
GATE 1Field-level HQ inheritance A market changes one field; every untouched field keeps inheriting HQ — after a later HQ update. Show the delivered payload before and after. Contentstack structural risk (localization detaches the whole entry); Contentful and Sanity better positioned.
GATE 2API-enforced approval Publish from Draft/Reviewed is blocked via a Management-API token, not just hidden in the UI. Show the rejection response. Contentstack likely strongest (publish rules); Contentful must prove API-level; Sanity = custom + API proof.
GATE 3Editor-grade visual preview BMW-like page preview: per market/language, inherited vs. overridden fields visible, approval state, draft/approved/published side by side. All three need a real demo, not a slide — this is an unscored gate until seen.
GATE 4DAM / AEM Assets coexistence External asset referenced, with expiry/replacement, usage tracking, and a warning before publishing a stale/expired asset. Do not accept "our CMS asset store" as the enterprise-DAM answer from any vendor.
GATE 5Migration & cutover Re-runnable idempotent import, documented rate limits, rollback, promotion model, and real 429/Retry-After logs under load. Ask for contractual rate-limit numbers and a written migration plan — not "it depends."
GATE 6China / CDN delivery Publish-to-edge propagation time, a concrete mainland-China delivery story, and cache invalidation against BMW's own CDN (Akamai). Critical for BMW — largest single market; not a secondary question. No vendor documents this today.
GATE 7Exit & reversibility Export of content, schema, assets and config — and an explicit list of what is lost: audit history, users, personalization profiles. Our provider-neutral port reduces content-layer risk; it does not erase switching costs.
GATE 8Contractual signability SLA %, EU residency, TISAX/ISO, rate-limit uplifts, support tier, pricing meters, roadmap commitments — in writing. Without written answers, the PoC winner is not automatically signable — flag as commercial follow-up.

03

Risk register — top risks by vendor

The verdict’s per-vendor bullets, consolidated and severity-ranked by area of risk. Two tiers: CRITICAL = dealbreaker-class for BMW’s requirements — must be resolved or mitigated before selection (each maps to a pass/fail gate in §02); HIGH = serious, needs a written answer or a mitigation plan. Every risk links to the official doc behind it.

Contentful
CRITICALCommercial / viability
Salesforce acquisition pending close (~Aug–Oct 2026) with no written roadmap, pricing or continuity commitment — renewal leverage shifts to Salesforce. press
HIGHWorkflows & governance
Approval enforcement at the API is undocumented — a Management-API token may publish a pre-Approved entry; admins bypass steps by default. docs
HIGHContent modeling
50 fields/content type forces decomposition; whether localized fields count toward the 50 is undocumented. docs
HIGHDelivery
GraphQL 11,000-entity complexity/request (rich text costs 1,000 each) + 8 KB query cap — a render-time wall on rich car pages. docs
HIGHCompliance / residency
EU residency is storage, not processing; org region-pinned permanently; Personalization edge profiles stored worldwide by default. docs
Contentstack
CRITICALLocalization / inheritance
Localizing an entry detaches it permanently — field-level HQ inheritance fails after the first partial override (the core BMW requirement). docs
HIGHMigration / throughput
CMA writes 10 req/s per ORGANISATION (shared across every stack) + bulk ops 1 req/s — the migration/rollout bottleneck. docs
HIGHContent modeling
5 Modular Blocks fields/type + 3-level nested-block cap; JSON RTE won’t deep-resolve references inside embedded entries. docs
HIGHCompliance
No TISAX on the trust page (automotive-relevant); standard SLA 99.50% (99.95% only on top tiers). legal
HIGHCommercial
Unpublished consumption + Lytics-event pricing (hard to model); docs-vs-legal contradictions (3/2 vs 5/5 envs/branches). legal
Sanity
CRITICALWorkflows & governance
No native approval workflow — enforced approval is a custom build (publish grant is API-real, the state machine is our code); official plugin advisory-only; Releases have no approval gates. docs
HIGHCompliance / security
No own ISO 27001 (GCP infra only); no published SLA %; no contractual EU region pinning; SSO Enterprise-only; no field-level permissions. docs
HIGHPersonalization / analytics
Nothing native — A/B, audiences and analytics are all external; Ninetailed (the historical path) is now Contentful-owned. docs
HIGHContent modeling
Locale-keyed i18n × 30 locales blows the attribute-path budget (≈ 3,100 paths → Enterprise) — avoidable via internationalized-array, but a design landmine. docs
MEDIUMExtensibility
GraphQL is second-class to GROQ (skills/portability); Agent Actions labelled “experimental”; Embeddings Index API deprecated. docs
READ ACROSS

The risks cluster differently per vendor: Contentful = one CRITICAL commercial/ownership risk (Salesforce) plus documented modeling & delivery ceilings; Contentstack = a CRITICAL inheritance risk that hits BMW’s core requirement, plus throughput; Sanity = a CRITICAL governance gap (approval is our build) plus a cluster of compliance and personalization/analytics gaps. None is disqualifying on its own — the decision is which risk profile BMW is best placed to carry.


04

Decisive requirement · HQ→market fallback & inheritance

The DE editor leaves price and image empty — delivery must return the HQ values; a DE override must survive every future HQ rollout. Below: each vendor's actual mechanics, and the trap an architect needs to know before the vendor demos around it.

ContentfulNATIVE

Markets as locales of one entry. Field-level localization with a configurable fallback locale per locale; multi-hop chains are explicitly supported (de-CH→de-AT→de-DE). Empty DE field ⇒ CDA serves the fallback value; a set DE value is a protected override by construction. Locale-scoped roles keep the DE editor inside de-DE. docs

The trap

An empty string kills the chain. “What you set is what you get”: a TMS round-trip or import writing "" silently blocks fallback across thousands of entries. And the whole model only works inside one space — cross-space references cap at 3 fields/type, 20 spaces/field, 50 links/field, and resolve links, not field values. docs

Outdated / impact

Nothing native. “HQ changed since approval” flags and “affects 27 entries in 12 markets” need a small App-Framework sidebar over the links-to-entry API — our build.

ContentstackNATIVE·DETACHES

Entry-level fallback through the locale tree. An unlocalized DE entry serves its fallback language automatically — per-locale versioning and publish queues are native, and publish rules scope per locale. docs

The trap

“The inheritance of the entry is broken from the fallback language and it, therefore, becomes independent.” The moment DE edits one field, the entire entry detaches — the still-empty price stops following HQ, permanently. Field-level inheritance post-edit requires decomposing cars into fragment entries or merging in the delivery layer. Fallback chains are also max one level for non-master languages. docs

Outdated / impact

References view exists; no outdated flag documented. Impact analysis = custom, like the others.

SanityDELIVERY-LAYER

Market document references the HQ master; fallback resolves at read time: coalesce(price, baseCar->price) — per field, any type, chainable market→region→HQ. Officially documented pattern; nothing configured in the CMS. docs

The proof

We already run this. Our deployed Phase-0 Studio: the DE car overrides only price + description, inherits the rest live from the HQ master. Edit HQ, re-query, DE follows — demoable today.

Outdated / impact

Custom, but cheap and exact: document badges comparing baseCar->_updatedAt vs. approval revision; count(*[references($hqId)]) for impact — one query, rendered in a Studio pane we control.

OUR ANGLE

No vendor ships outdated-detection or impact analysis natively. That is precisely the thin, vendor-portable layer our content backbone provides on top of the ContentStoreRepository port — BMW buys our proven layer, not a vendor roadmap, for their hardest requirement.


05

Decisive requirement · Enforced approval workflow

Draft → Reviewed → Approved → Published, where publishing from Draft is impossible and only a Market Lead moves Reviewed → Approved. The question that separates marketing from architecture: is the block enforced at the API, or only in the editor UI?

ContentstackFULLY NATIVE

Workflows with up to 20 stages; stage-transition rules assign users/roles per transition; Publish Rules require a named stage before publish, with approvers by user or role and an optional four-eyes block (last editor can't approve own work) — scoped per branch, environment and locale. docs

Caps & caveats

10 workflows/stack, 100 publish rules/stack, 100 content types per workflow docs — shared workflows at 30 markets, not per-market ones. Plan-gated (“available only if included in your plan”). Whether a Management-API token bypasses publish rules is undocumented — meeting question.

ContentfulNATIVE·CAVEATS

Workflows app (Enterprise, per pricing page): up to 20 steps, per-step rules allow/deny edit + publish per team, explicit deny wins, optional sequential enforcement, default “no one can change step.” docs app

Caps & caveats

Only 2 step-transition rules + 4 edit/publish rules per step — market teams must be aggregated. Admins bypass by default (“move from one step to another without restrictions”), and CMA-level enforcement is nowhere documented — three official pages checked are silent on whether an API token can publish mid-workflow. Get it in writing.

SanityCUSTOM BUILD

No native workflow engine. The official plugin self-describes as UI-only — “not enforced by the API,” “does not influence… whether a document is in draft or published” — and its standalone repo was archived 2025-12-08 (it lives on in the sanity-io/plugins monorepo, still published, explicitly feature-frozen). github

What is real

Publish is a separately grantable permission (Contributor writes but cannot publish) — that enforcement is API-level. On Enterprise, publish rights can be scoped by GROQ filter (e.g. approvalStatus == "approved"). The state machine itself — transitions, who-approved-what — is our application code. Releases have no approval gates. docs


06

Hard limits datasheet

The numbers as the vendors publish them — verified in two passes (2026-07-06 and 07-07; the second pass caught vendor docs changing under us, so re-click the links before quoting in a contract). RISK = dangerous at BMW/MINI scale (50+ models × 30+ markets, high asset volume). Current-plan limits; grandfathered/legacy contracts have separate limit pages — confirm which applies.

Limit Contentful Contentstack Sanity
Fields per content type 50RISK — forces model decomposition docs 500OK docs 1,000 attrs/doc (Growth), 8,000 Enterprise docs
Content types / entries 1,000 types/env · 5M records/space docs 1,000 types/org · 2M entries/orgORG-WIDE legal types = code (n/a) · 10M docs, 10 GB/dataset docs
Locales 500/env and /orgORG-WIDE docs 150/stack legal · chains max 1 levelRISK docs Unlimited (pattern-based) · attr-path budget 10k (Growth)×30 LOCALES docs
Isolation units (CF environments · CS branches · Sanity datasets) 151 envs/space + aliases docs 3 envs, 2 branches per stack (docs) docs vs 5/5 “default maximum” (legal — plan-dependent)CONTRADICTS legal Datasets: 2 on Growth, custom Ent. pricing
Write throughput (mgmt API) 10 req/s per spaceMIGRATION docs 10 req/s per org · bulk 1 req/sORG-WIDE docs 25 req/s mutations per IP · 4 MB body docs
Read throughput (delivery) 78 req/s/space (CDA incl. GraphQL) 100 req/s REST origin/org · GraphQL 80 req/s/org · CDN uncapped docs 500 concurrent uncached queries (concurrency cap, not a rate) · CDN uncapped docs
Workflow machinery 20 workflows/env docs · 20 steps · 4 publish rules/stepRISK docs 10 workflows/stack · 20 stages · 100 publish rules/stack docs n/a — custom buildGAP
Releases / scheduling 200 entities/release · 80 timeline releases/env · 500 scheduled actions/space docs 500 items/release · 25 items per API call · webhook fan-out behavior undocumented — trial probe docs 1,000 docs & 100 MB/release · Enterprise-only docs
Webhooks 20 (Free/Lite) · 100 (other paid) · 3 tries in ~1 min, timeouts never retriedRECONCILE docs 100/stack docs 4 on Growth, custom Ent. (GROQ-filtered) pricing
Assets 1,000 MB/asset (paid) docs 700 MB/asset · 500k assets/org legal Plan-metered storage; Media Library = Enterprise (per pricing page, 2026-07-07) pricing
Payload ceilings Request 1 MB · GraphQL query 8 KB · response 7 MB docs Entry-size cap unverified Doc 32 MB · nesting 20 levels docs
Custom roles 250/space (technical limit) · Enterprise-only per pricing page as of 2026-07-07 — note help-center pages still use a retired “Premium” tier vocabulary; confirm tier in RFP pricing Cap unverified · field+locale granularityBEST Enterprise-only · document-level filters onlyGAP

Migration math that matters: a 10k-entry × 30-locale AEM migration is ≈ 300k writes. At Contentful's 10 req/s/space that is ~8.3 h theoretical minimum per space; at Contentstack's 10 req/s per organization, every stack and integration shares that same budget. Sanity's 25 req/s is per IP — parallelizable, ask for it in writing.


07

Spaces · stacks · datasets — isolation & migration mechanics

Same concept, three vocabularies — Contentful: organization → space → environment (+ environment aliases) · Contentstack: organization → stack → branches, plus “environments” that are publishing targets, not sandboxes · Sanity: organization → project → dataset. The real risk hides in what silently doesn't carry when you clone, branch, export or promote — that decides the dev→stage→prod story, the AEM cutover plan, and whether a schema rollout at 30 markets is a pipeline or a weekend.

ContentfulSANDBOX CLONES

Environment = clone of master (or another sandbox) carrying environment-scoped resources (content types, entries, assets, locales, tags) — up to 151 per space. Promotion = scripted contentful-migration runs per environment, then repointing the master alias at the new environment. docs

What doesn't carry

Workflows aren't copied when you clone an environment — your approval configuration must be rebuilt or scripted per environment. Space export/import additionally drops scheduled releases, tasks and workflows, and webhooks export without credentials. Entry versioning + CMA snapshots exist only in master / the master-alias target. docs

Space / org moves

Cross-space content sharing = orchestration (Enterprise) with the reference caps from §04. And the org is pinned to its residency region forever — there is no US↔EU migration path. Content sync speed is bounded by CMA 10 req/s per space.

ContentstackTARGETS + BRANCHES

Environment = publishing destination, not a content copy: “a specific destination where your content is delivered when published.” Dev→stage→prod promotion is simply publishing the entry to the next environment — one content store, several delivery targets, environments global across branches. docs

Isolation lives in branches

A branch forks everything — content types, global fields, entries, assets, languages, webhooks, extensions. But: docs

Merge covers only content types + global fields, via CMA/CLI. Entries never merge back — content created on a branch is stranded unless exported by hand. With 2 branches per stack (docs limits; legal's "default maximum" says 5), this is schema-CI machinery, not content staging.

Throughput

Bulk operations run at 1 req/s and CMA writes at 10 req/s — per organization — so environment re-publishes and migrations queue globally.

SanityDATASETS + GIT

Schema promotion is a pull request — the schema is TypeScript in git, so dev→stage→prod is a normal code pipeline, the only vendor where this is true by construction. Content environments = datasets (2 on Growth, custom Enterprise).

Content promotion

NDJSON export/import per dataset — tarballs carry assets, references are auto-weakened on import then re-strengthened, --replace/--missing control idempotency. Proven in our repo: our PoC seeds with exactly this (sanity dataset import seed.ndjson production --replace). docs

What doesn't carry

Validation doesn't run on API imports — “Schema validation rules only run in Sanity Studio.” A bulk migration can write documents your own rules would reject; validation must be re-run in the pipeline (our backbone's dry-run already does this). Dataset copy/clone command + plan gating: unverified. Mutations at 25 req/s/IP, 4 MB bodies.

Migration concern Contentful Contentstack Sanity
“Environment” means Isolated full content clone (sandbox) Publishing destination only — one content store Dataset (independent content store)
Schema promotion dev→stage→prod contentful-migration scripts per env + master-alias repoint Branch → CLI/API merge of content types + global fields Git PR + CI deploy — schema-as-codeCLEANEST
Content promotion / sync Env clone at creation; then space export/import or CMA scripts Publish to next environment (native, cheap)SIMPLE NDJSON export/import with asset tarballs
What silently doesn't carry Workflows (on clone); scheduled releases, tasks, workflows (on export); webhook credentialsREBUILD Entries across branches — no content mergeSTRANDED Validation on import — API writes skip Studio rulesGOVERNANCE
Zero-downtime schema rollout Master alias swap (documented mechanism; deep-dive details on alias page) Merge into main branch; envs unaffected (they're targets) Deploy new Studio + additive schema; content unaffected
Versioning / snapshots scope Only master / master-alias envSANDBOX BLIND Per-locale entry versions (native) Full revision history per document (retention by plan)
Region / org-level moves Org pinned to one residency region foreverNO EXIT Separate NA/EU instances; cross-instance move unverified Single EU (Belgium) region; pinning planned, not contractual
Migration throughput ceiling 10 req/s per space (≈8.3 h per 300k writes — our derivation) docs 10 req/s + bulk 1 req/s per orgSHARED docs 25 req/s per IP — parallelizable (confirm in writing) docs
AEM-cutover fit Idempotent upserts via CMA; freeze window sized by rate limit Env-as-target model maps well to phased cutover; watch org budget during sync Re-runnable --replace imports; validation re-check needed in pipeline
OUR ANGLE

Our backbone's import contract — idempotent upserts on an external key, dry-run first, re-runnable without duplication — is exactly the property an AEM cutover needs: no big-bang freeze, just repeated converging syncs until parity, on any of the three platforms. Ask all three: (1) Contentful — does a master-alias swap preserve Workflows-app state on in-flight entries? (2) Contentstack — is entry-level branch merge on the roadmap, and what's the sanctioned pattern for content staging today? (3) Sanity — dataset copy/clone plan gating in writing, and how do we enforce validation on API imports?

Hub → spoke: a central hub feeding BMW / MINI / regional tenants

The org-topology question: shared masters (models, engines, legal, media) live in a central hub — a Contentful space, a Contentstack master stack, a Sanity dataset — and the BMW, MINI and LATAM tenants need to consume them: by live link (content stays in the hub, spokes reference it) or by copy pipeline (content is replicated out, then diverges). The three vendors give three very different answers. Verified 2026-07-07.

ContentfulLIVE LINKS·CAPPED

Cross-space references — the only first-party live-link mechanism here. A MINI-space entry references the hub entry; “multi-space orchestration” (Enterprise line item) adds content-type templates synced hub→spokes for schema distribution. docs pricing

The caps, at BMW numbers

3 cross-space reference fields per content type · 20 spaces per field · 50 linked entries per field over all locales — a “featured models” field linking the hub's 50+ car catalog hits the ceiling exactly at BMW's size. And the docs describe the feature in the web app and CMA only — delivery-side resolution (CDA/GraphQL) is not documented: assume the frontend makes a second query into the hub space until proven otherwise. Meeting question.

Also

Locale fallback does not cross spaces — the §04 inheritance model works within one space only. Each space has its own 10 req/s CMA budget (per-spoke isolation, a genuine plus). Copy pipeline fallback: space export/import per spoke, minus the workflow/task exclusions above.

ContentstackAPP OR COPY

Native reference fields do not work across stacks — “cross referencing between stack is not supported while using a reference field.” docs

The official answers

(1) Interstack Reference — a marketplace app providing a custom field that stores links to entries in other stacks; resolution lands on our delivery layer. docs (2) Copy pipelines: the “Keep Stacks in Sync” webhook guide, web-proxy sharing, an asset-sharing extension — hub publishes, automation replicates into spokes. docs (3) The “Master Stack strategy”: central stack owns the system of record, pushed to brand/regional child stacks — their own recommended enterprise pattern. blog

The tax

Copied content diverges by design (that's the §04 outdated-content problem, cross-stack edition), the CLI stamping out new brand stacks does carry workflows/roles config (good), and every sync pipeline competes for the org-wide 10 req/s write budget.

SanityLIVE LINKS·SAME PROJECT

Cross-dataset references — live links dereferenced at query time with GROQ ->, “behaves similarly to an internal reference.” Enterprise-only, and both datasets must be in the same project — a hub dataset feeding brand/market datasets works; a separate MINI project does not. docs

Caveats

No GraphQL dereferencing · references() (our impact-analysis tool) doesn't work cross-dataset · deletion protection is a warning, not a block (or none with weak: true) — hub deletions can strand spokes. Cross-project sharing exists only for assets, via Media Library (Enterprise).

The alternative we already run

One dataset, brands/markets as document dimensions — our PoC's baseCar+market pattern with document-level permissions scoping editors per brand/market. No inter-space migration problem, because there is no inter-space. Works until org boundaries (MINI's own vendor teams, separate billing) force real separation.

TOPOLOGY CALL

Decide the link-vs-copy question per content class, not per platform: shared immutable masters (engines, legal, WLTP) want live links; market-owned content wants copies with our outdated-detection on top. Our importer is the copy pipeline either way — idempotent external-key replay hub→spoke is exactly what it already does — and our content adapter is where cross-space links resolve when the vendor's delivery API won't. Ask all three: show one shared hub entry consumed by two brand spaces — who resolves the link at delivery, what happens when the hub entry changes, and what happens when it's deleted.

Exit strategy — taking the data with you

BMW is leaving a CMS right now; the next one must be provably leavable. What each vendor's full export actually contains — and the three things none of the three documents exporting (explicit exclusions at Contentful and Contentstack; documentation silence at Sanity): version history, personalization profiles, and user accounts. Verified 2026-07-07.

Exit concern Contentful Contentstack Sanity
Full export tooling contentful space export → one JSON bundle docs cm:stacks:export → JSON per module, entries by type+locale docs sanity dataset export → tarball: data.ndjson + files/ + images/ docs cli backups
Content & model included Content types, entries, assets, locales, webhooks, roles, editor interfaces; drafts/archived opt-in (--include-drafts) Content types, entries, assets, global fields, locales, environments, extensions, labels, taxonomyBROAD “All the non-deleted documents in a dataset, including drafts and asset documents” — the most complete content snapshotCOMPLETE
Governance config included Roles yes — but workflows, tasks, scheduled releases: NOT exported (“must be scripted afterwards”)REBUILD Workflow + Custom Role + Personalize + Webhooks all exportable — best config exit of the threeBEST Schema + Studio config are already our code in git; platform roles config not in dataset export
Explicitly NOT exported Version history, author history (re-import shows token owner as author), space memberships, custom apps, webhook credentials Users, Releases; 100 MB default content-length ceiling per item; secured assets need --secured-assets Revision history (point-in-time snapshot only); cursor-mode exports can be inconsistent under concurrent writes
Asset files Yes — --download-assets flag Yes — assets module (+ secured-assets flag) Yes — bundled in the tarball by default (--no-assets to skip)
Rich-text portability Proprietary Contentful rich-text JSON — conversion layer neededCONVERT Proprietary JSON RTE format — conversion layer neededCONVERT Portable Text — published as an open spec portabletext.org; least proprietary of the threeOPEN
Personalization data exit Experiences are entries (exported); Ninetailed profiles/analytics unverified — ask Personalize module exports config; Lytics event/profile data exit unverified — ask No vendor profile store — nothing to strandN/A
Export at BMW scale Bounded by CMA 10 req/s/space — schedule it docs Bounded by org-wide budgets; tunable concurrency in CLI Export API streams the dataset; point-in-time, run off-peak
Re-import / replay elsewhere IDs preserved in bundle; same-shape re-import supported Module structure is import-compatible stack-to-stack NDJSON is re-importable as-is; refs auto-restrengthened
EXIT ASKS

All three, contractually: post-termination data-retention window and export-assistance obligations in the MSA — before signature. Contentful: can Personalization profiles/experience analytics be exported, and what happens to them post-close under Salesforce? Contentstack: Lytics event/profile export path and format? Sanity: can revision history be exported on Enterprise (it's not in the standard export)? — And our structural insurance, stated precisely: content flows through our provider-neutral port with external-key idempotent upserts, so the day BMW leaves vendor N, the same import pipeline replays the exported content into vendor N+1 — proven across three adapters. That de-risks the content layer; it does not erase switching costs in workflow config, preview wiring, DAM integration and editor extensions (see §10).


08

The four PoC scenarios — what we demo live

Our hands-on evaluation plan. Items marked already built run in our repo today — the same import flows into all three vendors through one port, which is the CMS-independence demo.

S1Model a complex object — Car Technical Data

Nested specs with units, trims, market pricing, gallery, per-market WLTP disclaimers, engines shared across models. Success = maintainable at 50+ models.

ContentfulCEILING-BOUND

Clean references + rich-text embedding, but the 50-field cap forces decomposition into engineSpec/trim/pricing entries from day one. 1,000 links/entry — counted across ALL reference, media and rich-text fields. Migration CLI for schema CI/CD.

ContentstackNATIVE

Modular Blocks for spec groups, Global Fields for shared WLTP structures, save-time min/max cardinality enforcement. 500 fields of headroom.

SanitySTRONGEST

Schema-as-code, nested objects to 20 levels, JS cross-field validation, native hotspot. Already built: our ~100-field car type is the stress-test bed.

DEMO › Import the same 60-column spec CSV through our tool into each vendor live; let the 50-field refusal happen on camera in Contentful and walk the decomposition.

Modeling ceilingContentfulContentstackSanity
The binding wall Field count — 50 fields/type, hardFORCES DECOMP Composition shape — 5 Modular Blocks fields/type, 3-level nestingDEPTH CAP Attribute-path budget × localizationi18n TRAP
Composition model No modular blocks — references + rich-text embeds, all sharing 1,000 links/entry docs Modular Blocks (100 defs / 100 instances per field) + Global Fields (25/type) + refs (50 types/field) docs Objects/arrays in code, 20-level nesting, no field/type count cap docs
Resolution / query ceiling CDA resolves 10 levels; GraphQL 8 KB query + 11,000-entity complexity (rich text 1,000 each)RENDER WALL Reference query limit 100; JSON RTE won’t deep-resolve embedded refs No recursive GROQ joins — explicit -> per level
Localization trap Whether localized fields count toward the 50-cap is UNVERIFIED — verify Localizing forks the whole entry → every per-entry ceiling spent per locale Locale-keyed i18n × 30 ≈ 3,100 paths (→ Enterprise); internationalized-array keeps it flat docs
Escape hatch Decompose into referenced sub-types (forced, not optional) Model deep trees as references, not nested blocks Choose internationalized-array / document-level i18n up front

ONE-LINER › Contentful caps you on field count, Contentstack on composition depth (and taxes localization by forking the whole entry), Sanity on attribute-paths unless you pick the right i18n pattern. All three are workable at 50+ models — but the saving decision differs per vendor and must be made before authoring. Verified 2026-07-08.

S2Rollout / approval workflow

Draft → Reviewed → Approved → Published; direct publish impossible; Reviewed→Approved gated to Market Lead.

ContentstackFULLY NATIVE

Stages + transition rules + Publish Rules + four-eyes. Reference implementation — build it in the trial, demo the blocked publish and the four-eyes rejection.

ContentfulNATIVE·CAVEATS

Sequential workflow + per-step deny rules. Then, live: attempt a CMA publish against a Draft entry — whatever happens is the answer the docs won't give.

SanityCUSTOM

Custom document actions hide Publish until Approved; publish grants enforce who. Say plainly which half is platform, which half is our code — precision wins the room.

DEMO › Same four statuses on all three; an editor attempts publish from Draft on each — one native block, one blocked-with-caveats, one blocked by our layer.

S3HQ change + market change, with fallback

Empty DE fields inherit HQ at delivery; DE overrides survive HQ rollouts; outdated flags + impact counts in the editor.

ContentfulNATIVE (a)

Locale fallback chain demo + the empty-string trap shown deliberately. Impact sidebar = our App-Framework build. Already built: idempotent multi-locale import.

ContentstackDETACH TRAP

Show fallback working — then localize one field and show price stop following HQ. Run it ourselves first; ask the vendor to respond to it in the meeting.

SanityPROVEN (ours)

Already built: live coalesce inheritance in our deployed Studio. Sketch the outdated badge (_updatedAt vs approval rev) + impact pane as Phase 1.

DEMO › Change the HQ price live; watch DE inherit on two platforms via two different mechanisms — then show the DE override refusing to move.

S4Explore the limits

Find the real ceilings before contract signature — documented numbers are the hypothesis, the trial is the experiment.

Contentful

51st field refusal · sustained CMA at 10 req/s + 429/Retry-After behavior · 5th publish rule on a step · webhook drop test (timeouts never retried).

Contentstack

Org-wide write ceiling with two stacks writing at once · 21st workflow stage · release webhook fan-out (undocumented — does a 500-item release fire per-item webhooks?) · env #4 creation (docs say 3, legal says 5).

Sanity

Attribute-path budget: 100-field car × 30 locale keys · 25 req/s mutation ceiling from 1 vs N IPs · 1,001-attr document · release >1,000 docs.

DEMO › Bring the logs, not anecdotes: 429 curves, error payloads, and the exact refusal messages — that's what an architecture board trusts.

S5Editor experience & visual preview

The dimension AEM editors will judge first: in-context page preview (desktop/mobile, per market/language), visibility of inherited-HQ vs. market-overridden fields, workflow state at a glance, and draft-vs-approved-vs-published comparison. A headless CMS that loses the visual context reads as a regression regardless of architecture. Held out of the weighted matrix until verified at this page's sourcing standard — this scenario IS the verification.

ContentfulNATIVE·CONFIG

Live preview + Studio / Experiences (visual builder, scheduled publishing inside Experiences) docs. BMW-specific preview (inherited-vs-override visibility) is configuration + our frontend's preview route. Plan gating for Studio — verify.

ContentstackNATIVE·GATED

Live Preview + Visual Builder (edit-tag instrumentation of our frontend required; SDK v4 "available on select plans — contact support") docs and Timeline for previewing scheduled releases. Strong editorial flow — demand the preview demo, not just the workflow demo.

SanityCUSTOM·STRONG

Presentation tool / Visual Editing (config + plugins — our Phase-0 README already stages enabling it) docs. More of the experience is ours to build — which also means inherited-field badges and approval states render exactly as BMW wants, in the same Studio we already customize.

DEMO › The one ask, verbatim, to every vendor: “Show us a BMW-like page preview where an editor sees inherited HQ fields, local market overrides, draft vs approved vs published states, and asset/legal warnings — in one editorial flow.”


09

Operations, compliance & viability

Criterion Contentful Contentstack Sanity
Uptime SLA “Up to 99.99%” — Enterprise, negotiated pricing 99.50% std · 99.95% Scale/X5LOW STD legal No published % — Enterprise line itemUNPUBLISHED pricing
EU data residency Add-on: Dublin + Frankfurt backup. Storage only — “cannot guarantee data… is only processed in the EU”; org pinned (one-way migration into EU offered via sales during a limited transition window)CAVEATS faq Three separate instances (incl. EU) on AWS/Azure/GCP; “No data is shared between our North American and European instances” trust EU (Belgium) by default — single region, no contractual pinning; assets may sit EU/US/otherNO PIN security
Certifications ISO 27001:2022 · SOC 2 Type 2 · TISAX (TÜV Rheinland/ENX)AUTOMOTIVE security ISO 27001:2022 · SOC 2 Type II · no TISAXGAP SOC 2 Type 2 · no own ISO 27001 (GCP infra only)GAP
AI platform AI Actions (Ent.): 20/space, 20 invocations/min, whole-field only · consumption units · MCP server Agent OS (Agents + Automations + Polaris) · MCP across CMA/CDA/Launch/Personalize/Lytics + more docs Agent Actions (“experimental” in docs) · $0.05/credit (1/action) · MCP GA · Functions + App SDK docs
Personalization Contentful Personalization (Ninetailed) — edge profiles worldwide by default, EU endpoint optional Native Personalize + Lytics CDP (closed Dec 2024) — audiences, variants, A/B, banditBEST None native — frontend/CDP integration onlyGAP
Ownership & funding Acquisition by Salesforce pending (close ~Aug–Oct 2026)IN TRANSITION Private · ~$179M raised (Series C 2022) · AXP/CDP pivot Independent · $85M Series C (May 2025)FRESH
AEM-exit references Americar (chose CF over AEM in PoC) · Vodafone · BMW & TMWX already a customer story Official AEM→CS migration framework + ebook · zero named AEM-exit customersVERIFY None named · PUMA = best multi-market analogue (55k reusable pieces)
Enterprise pricing shape Free / Lite $300 / Enterprise custom · API calls + bandwidth metered · everything BMW needs = Enterprise Unpublished · Start→X5 tiers · consumption-based · ~$30k–200k+/yr (Vendr, 3rd-party) Free / $15-seat Growth / Enterprise custom · governance all Enterprise-gated

Delivery & CDN

The cached tier is what makes each vendor's rate limits livable at BMW traffic — and it's also where the EU-residency caveats live, since edges cache globally by design. Context: with Next.js/ISR in front (our FE evaluation), page-level caching sits in BMW's existing CDN estate anyway — the CMS CDN matters chiefly for API-driven surfaces (configurators, apps) and asset/image delivery.

Delivery concern Contentful Contentstack Sanity
Cached-read policy CDA behind a global CDN; the 78 req/s/space limit applies to CDA calls incl. GraphQL docs CDN-cached requests not rate-limited; REST origin 100 req/s/org, GraphQL 80 req/s/org docs API CDN cached requests unlimited; uncached capped at 500 concurrent queries docs
CDN provider & footprint Fastly + CloudFront — named on their own EU-residency FAQ, which also concedes edges “cache data globally” faq Provider unverified on official pages Provider unverified on official pages; Live Content API (all plans) for real-time delivery docs
Image delivery Images API with transforms; asset bandwidth is the metered dimension (40 TB/mo Enterprise) usage Image Delivery API — counts toward the origin rate limit when uncached Image CDN applies hotspot/crop automatically from the stored focal point docs
Publish→edge invalidation latency Undocumented for all three — no vendor publishes a propagation SLA for how fast a published change replaces cached content at the edge. Meeting question below.
Mainland-China delivery Unverified for all three — none documents China edge presence or an ICP-compliant delivery story on official pages. BMW-critical: China is the group's largest single market, and BMW China's dealer platforms already run on-prem Magnolia precisely for this class of reason. Meeting question below.
ASK ALL 3

(1) What is the measured publish-to-edge propagation time, and is any bound contractual? (2) What is your mainland-China delivery story — edge nodes behind the firewall, an ICP-compliant partner setup, or “customers proxy it themselves”? (3) When BMW fronts your API with its own CDN (Akamai), what are the sanctioned cache-invalidation webhooks/headers, given webhook retry guarantees are weak (Contentful: 3 tries in ~1 min, timeouts never retried)?


10

DAM strategy & total cost of ownership

In the AEM world, AEM Assets can be half the platform's value — model imagery, campaign assets, rights & expiry, legal approval, renditions, usage tracking. None of the three headless asset stores replaces that out of the box; the DAM question is an architecture decision that precedes the CMS decision. And on cost: all three are custom Enterprise pricing at BMW scale — no vendor publishes numbers, so TCO comes from the RFP, not this page.

DAM: three scenarios

ScenarioShapeAdvantageRisk
A — AEM Assets stays (Phase 1) New CMS holds the content model + references to assets served from AEM Assets / existing DAM Lowest Phase-1 risk; asset governance (rights, expiry, approvals) keeps working AEM isn't fully exited; dual-system ops until Phase 2
B — Dedicated enterprise DAM CMS integrates a specialist DAM (existing BMW estate or new) Clean content/media separation; strongest media governance long-term Extra integration + license cost; another migration
C — CMS asset store Assets move into the CMS (Contentful assets / Contentstack assets / Sanity Media Library) One system, simplest ops Likely insufficient at BMW scale: rights management, expiry and approval workflows are thin vs. a real DAM — plus the caps in §06 (700 MB / 500k assets per org on Contentstack; bandwidth metering on Contentful; Media Library Enterprise-gated on Sanity)

Recommendation: Phase 1 = CMS migration with asset references into the existing DAM (scenario A) — our import pipeline already handles URL-referenced assets. Phase 2 = DAM rationalization after the content model, workflow and editorial operating model have stabilized. Do not let a CMS vendor sell scenario C as the default.

TCO & reversibility — directional, not priced

Cost axis Contentful Contentstack Sanity
License risk High — everything BMW needs is Enterprise-gated, renewal mid-Salesforce-transition High — unpublished consumption pricing; Personalize adds event metering Medium/custom — transparent below Enterprise, but governance is all Enterprise
Build cost Medium — model decomposition (50-field cap), impact/outdated tooling, preview config Medium-high — fallback fragmentation or delivery merge layer, release orchestration High — approval state machine, permissions shaping, preview, compliance hardening
Exit cost Medium — good content export, governance config rebuilt (§07), proprietary rich text Medium-high — broad config export, but content patterns (modular blocks, JSON RTE) convert hardest Medium — most complete content export + open Portable Text; GROQ/schema code is ours but platform-shaped
HONEST SCOPE

Directional judgments, not verified prices — TCO inputs that must come from the RFP: seats/roles, markets × environments/branches/datasets, API + bandwidth + asset volume, DAM and preview integration, migration tooling and data cleansing, custom workflow/impact/editor extensions, support tier, training and the governance operating model. And the port, stated precisely: our provider-neutral layer is an accelerator and a risk reducer — it keeps optionality high through discovery and early implementation and makes content replay possible. It does not make vendors interchangeable after go-live: workflow configuration, preview wiring, DAM integration and editor extensions accrue as switching costs on top of the content layer.


11

AI & LLM integrations — deep dive

Two different questions, deliberately separated: what the vendor's AI does for editors (generation, translation, brand control — and what it costs, since every vendor meters it differently), versus what surface we get for our own agentic layer (MCP, event runtimes, app frameworks). The mindcore backbone already runs a governed LLM content-ops loop — propose → dry-run → confirmation token → idempotent apply — against all three adapters, so BMW's AI story does not depend on any vendor's roadmap.

ContentfulNATIVE·CAPPED

AI Actions (Enterprise): templated LLM operations on entry fields — generation, translation, rewriting — with “bring your own model” documented. Metered in AI Consumption Units: 750/yr Premium, 1,500/yr Enterprise; unit-conversion rates are not published. docs usage

Hard caps

20 AI Actions per space · 10 variables per action · 20 invocations/min · whole-field only (no text selections). At 30 markets doing AI-assisted translation, the 20/min ceiling is a queue.

Agentic surface

Official MCP server (entries, assets, content types, environments, AI Actions) docs + App Framework. Weak link for event-driven agents: webhooks retry only 3× in ~1 min and never retry timeouts. Post-close roadmap bends toward Agentforce — content as the agent layer of Salesforce Customer 360; a benefit if BMW is a Salesforce shop, lock-in if not. press

ContentstackBROADEST

Agent OS (docs current as of 2026-06): Agents (context-aware, tool-using), Automations (event-driven orchestration), and Polaris, a natural-language interface for tasks and answers across the platform — the "Agentic Experience Platform" positioning. docs

Brand governance

Brand Kit with Knowledge Vault: tone-of-voice and claims governance applied to AI output — the closest thing here to what BMW brand management will actually ask about. docs

Agentic surface

The only MCP server of the three that spans CMS + CDP + personalization in one surface: CMA, CDA, Launch, Brand Kit, Personalize and Lytics — plus Analytics, Automations and Developer Hub. docs Caveat: plan gating and LLM-choice details are unverified — Agent OS is young and priced opaquely; get the metering model in writing.

SanityDEEPEST·YOUNG

Agent Actions — Generate, Transform, Translate, Prompt, Patch — schema-aware LLM mutations, metered org-wide in AI credits at $0.05/credit (an Agent Action request = 1 credit; other AI operations meter differently) with configurable spending caps. Honest flag: the docs still label the feature “experimental” while the marketing headlines it. docs credits

Agentic surface

The best build-your-own story: remote MCP server GA (GROQ, releases, schema-aware patches) docs, Functions (event-driven runtime, full GROQ) and the App SDK — our agents can live inside the platform, not beside it.

Churn flag

Embeddings Index API deprecated ~a year after launch (migrate to native embeddings); AI Assist's reference support depended on it. Expect platform churn at the AI layer — version-pin and wrap. docs

AI capability Contentful Contentstack Sanity
Editor-facing AI AI Actions (Enterprise): field-level generate / rewrite / translate — whole-field only, no text selections docs AI Assistant in-entry + Polaris natural-language task/answer interface docs Agent Actions in Studio (Generate/Transform/Translate/Prompt/Patch) + Canvas AI writing docs
AI translation at 30 markets Via AI Actions — throttled by 20 invocations/min/spaceQUEUE docs Via AI Assistant; batch behavior unverified Translate action, schema-aware (localized field patterns); credits-metered
Metering & published cost AI Consumption Units: 750/yr Premium, 1,500/yr Enterprise; conversion rates unpublishedOPAQUE usage Unverified — no published metering for Agent OSOPAQUE $0.05/credit (1 credit per Agent Action; other AI ops differ), org-level spending caps — the only transparent price hereCLEAR credits
Hard caps 20 AI Actions/space (“20 to start” — may rise) · 10 variables/action · 20 invocations/min docs Unverified No documented caps; gated by credit spend
MCP server scope Entries, assets, content types, environments, AI Actions docs CMA + CDA + Launch + Brand Kit + Personalize + Lytics, plus Analytics, Automations, Developer Hub — broadest spanBEST docs Remote MCP GA: GROQ queries, releases, schema-aware patches docs
Event-driven runtime for agents Webhooks only — 3 tries in ~1 min, timeouts never retriedRECONCILE Automations (Automate lineage) — event-driven orchestration inside the platform Functions (full GROQ) + Media Library asset functions — code runs in-platformBEST
App / extension framework App Framework incl. org-level apps Marketplace apps + 21 published agent skills for AI coding tools github App SDK + fully custom React Studio
Bring your own LLM Documented optionYES Unverified Not documented — runs on Sanity-managed models
Brand governance of AI output None documented Brand Kit Knowledge Vault: tone-of-voice + claims rules applied to generationBEST docs None documented — prompt-level discipline only
Semantic search / embeddings Not documented Not documented (Lytics covers behavioral, not content embeddings) Native dataset embeddings — but the Embeddings Index API was deprecated ~1 yr after launchCHURN
Roadmap risk Reorientation toward Salesforce Agentforce post-close — asset or lock-in depending on BMW's stack Agent OS is young (docs current 2026-06) and priced opaquely “Experimental” label on Agent Actions while marketing headlines it; API churn at the AI layer
OUR ANGLE

The vendor-AI ladders above are editor conveniences; BMW's structural AI requirement — governed, auditable, idempotent content operations — is what our backbone already does on all three platforms. Demo: the same governed import (LLM mapping proposal → dry-run → human confirm → apply) executed against each vendor live, then an MCP session from an IDE against the same content — vendor AI as the icing, our layer as the governance.


12

Personalization, targeting & A/B testing — deep dive

Four things decide this for BMW, and none of them is the demo: where decisioning runs (edge API vs. frontend SDK), where profiles physically live (EU pinning), whether variants inherit the approval workflow of their baseline content, and what the meter is (events vs. tiers). The honest architectural question to settle internally first: does decisioning belong in the CMS at all, or in BMW's existing martech/CDP estate?

ContentstackNATIVE·FULL STACK

Personalize is native: audiences + attributes, variants edited inside the entry editor, A/B tests and multi-armed bandit traffic optimization, delivered via the Personalize Edge SDK/API alongside the CDA. docs

CDP

Own CDP since the Lytics acquisition closed Dec 2024 ("Data & Insights") — segments feed Personalize directly; the MCP server exposes both to agents. press

Watch

Pricing is event-based (Lytics events, not variant counts) — BMW-scale traffic makes this a metered bill to model before contract. Numeric caps on experiences/variants/audiences: unpublished. EU residency of the event/profile store: unverified — meeting question.

ContentfulNATIVE·ADD-ON

Contentful Personalization (the 2024 Ninetailed acquisition, now first-party): experiences modeled as entries — an Experience references an Audience plus baseline/variant components — so variants live in the same content model your workflow governs. Experimentation (A/B) and personalization in one product; Experience SDKs + API, edge/server-side rendering. docs

Pricing

Separate tier ladder (Start/Core/Scale, Enterprise add-on) on top of the CMS contract. pricing

Watch

Edge profiles reside “closest to the individual user (worldwide)” by default — an EU-only endpoint (experience.eu.ninetailed.co) exists but costs latency for non-EU visitors. For a German OEM, the default is the wrong way around; make the EU endpoint the contractual default.

SanityBYO STACK

Nothing native. Personalization and A/B are composed from the frontend/edge plus an external decisioning tool — Optimizely, GrowthBook, or a CDP; the Live Content API supports real-time variant swaps. Variant content = documents we model (which our HQ→market baseCar pattern already resembles).

Integration risk

Ninetailed — the historical go-to for Sanity personalization — is now owned by Contentful. Continuity of that integration is a strategic question, not a technical one; plan on Optimizely/GrowthBook or a CDP-first design instead.

Honest framing

For BMW this is either a dealbreaker (if personalization must be turnkey) or irrelevant (if decisioning belongs to the existing martech/CDP estate anyway — likely, given BMW's stack). Decide which before the meeting, because Sanity will ask.

Personalization capability Contentful Contentstack Sanity
Native product Contentful Personalization (ex-Ninetailed, first-party since 2024) docs Personalize — native module, delivered with the CDA docs None — BYO decisioningGAP
Variant modeling Experiences as entries: Experience → Audience ref + baseline/variant components — variants sit inside the governed content modelGOVERNABLE docs Variants edited inside the entry editor, attached to entries per audience Variants = documents we model ourselves (our baseCar HQ→market pattern is structurally the same move)
Targeting / audiences Audiences as content entries; profile attributes via SDK/API events Audiences + attributes native; segments fed by Lytics CDP External: CDP or experimentation tool defines audiences
A/B testing & optimization Experimentation (A/B) in the same product as personalization A/B tests + multi-armed bandit traffic optimizationBEST Via Optimizely / GrowthBook / VWO-class tools in the frontend
Decisioning & delivery Experience SDKs (JS frameworks), edge/server-side rendering, Experience API Personalize Edge SDK/API alongside CDA — decisioning at the edge Frontend/edge code + Live Content API for real-time variant swaps
Profile store & EU residency Edge profiles “reside closest to the individual user (worldwide)” by default; EU-only endpoint experience.eu.ninetailed.co costs latency for non-EU visitorsDEFAULT WRONG WAY Event/profile store residency unverified — meeting questionVERIFY No vendor profile store — residency is wherever BMW's CDP is (a feature, for once)BMW-CONTROLLED
CDP relationship Brings its own profile layer; integrates with external CDPs Owns one: Lytics ("Data & Insights", closed Dec 2024) press Integrates with whatever BMW already runs
Experimentation-tool paths Native product covers it; Optimizely-class tools integrable via API Native covers it; external tools via Automations/webhooks Optimizely / GrowthBook-class tools integrate at the frontend/API level (no official vendor listings verified); Ninetailed path now Contentful-ownedRISK
Pricing meter Separate tier ladder (Start/Core/Scale, Enterprise add-on) on top of CMS contract pricing Event-based (Lytics events) — traffic-driven bill at BMW scale; caps unpublishedMODEL IT No vendor meter — cost lives in the external tool
Variants under approval workflow? Unverified for all three — no vendor documents whether variant content passes the same approval gates as baseline content. This is universal question #1 below.
DEMO

One scenario, three implementations: a DE homepage hero that shows a loyalty-segment variant. Contentstack — variant + audience + bandit natively, watch the four-eyes rule interact with the variant. Contentful — an Experience entry referencing Audience + variants, delivered via the Experience API. Sanity — variant documents + a GrowthBook flag in the frontend. Same outcome, three ownership models: platform, platform+CDP, BMW's stack.

ASK ALL 3

(1) Do variants inherit the approval workflow of their baseline entry — can an unapproved variant go live through the personalization path? (2) Where do personalization profiles/events physically live, and what is contractually pinned to the EU? (3) What is the metered AI/personalization unit (consumption units / credits / events) and the modeled annual bill at 30-market traffic — before signature, not after.


13

Weighted scoring — the synthesis

Everything above, folded into one comparable view: eleven dimensions, scores 1–5 assigned strictly from the documented facts in sections 03–11 — not vendor marketing. Switch the presets to see how the weighting moves the result.

HOW TO READ THESE SCORES

Scores measure ability to satisfy BMW's requirement — not "native vs. custom" purity. A proven custom pattern (Sanity's coalesce inheritance, demonstrated in our PoC) can score higher than a native mechanism that fails the target (Contentstack fallback, which detaches on the first partial override). Unproven custom governance — especially on approval, security or compliance — stays penalized until it is demonstrated at the API level in the workshop. That is why Sanity scores 4 on inheritance (proven custom) yet 2 on workflow (planned custom, not yet API-proven): the two are not the same kind of custom.

DimensionWt Score 1–5 CFCSSA

All four profiles at a glance (weighted totals / 500)

The static view of the interactive matrix above — for print and quick reference. Two of the four profiles are close calls (highlighted); treat their ranking as a hypothesis for the PoC, not a verdict.

Balanced · close call
Contentful380 · leads by 1.4%
Contentful380
Contentstack373
Sanity319
Governance-first · clear
Contentstack398 · leads by 3.2%
Contentstack398
Contentful382
Sanity296
Multi-market-first · clear
Contentful394 · leads by 8.4%
Contentful394
Contentstack352
Sanity328
Engineering-first · close call
Contentstack362 · leads by 1.2%
Contentstack362
Sanity356
Contentful354
SENSITIVITY

Weighted totals are directional, not deterministic. Balanced (7 pts, 1.4%) and Engineering-first (6 pts, 1.2%) are decided by margins a single ±1 score on one well-weighted dimension can flip — so the ranking in those two profiles is a hypothesis to validate in the PoC, not a vendor selection. Governance-first and Multi-market-first are more robust. Note also: "engineering-native" (Sanity's character) is not the same as winning the Engineering-first profile — that profile also weights governance, ops and limits, where Sanity is weaker, which is exactly why Contentstack edges it. The final decision is made through the pass/fail gates (§02) and contractual signability, not the totals alone.

UNSCORED GATES

Three decision-critical dimensions are deliberately kept out of the weighted matrix until there is workshop evidence and RFP pricing — scoring them now would be guessing: Editor experience & visual preview (GATE 3 — quality depends on the preview implementation, not vendor docs; needs a live demo + editor feedback), DAM strategy (GATE 4 — an architecture decision: AEM Assets stays / separate DAM / CMS store — before it is a CMS score), and TCO (no real numbers without RFP pricing; the §10 High/Medium reads are directional only). They enter the final recommendation after the workshops, not before.

RECOMMENDED PATH

(1) Put the fork question to the business first: is field-level HQ→market inheritance after a partial market override non-negotiable? A yes points to Contentful (native fallback chains) or the Sanity pattern; a no makes Contentstack's native governance the front-runner. (2) Dual-track PoC: run S1–S5 on Contentful and Contentstack as the two leaders, with Sanity as the engineering benchmark lane that proves the inheritance pattern and keeps both vendors honest. Pass/fail gates: field-level inheritance after override (S3), API-enforced approval (S2), editor-grade preview (S5). (3) Pre-position the contracts now, in parallel with the PoC: Salesforce-transition protections (Contentful), rate-limit uplifts + TISAX commitment (Contentstack), SLA number + ISO + region pinning (Sanity) — so the PoC winner is signable the week it concludes, not a quarter later.


14

The five questions per vendor

Phrased for the room; the italic line is why we ask — what a bad answer sounds like. Context-specific asks live where their evidence lives: §07 (topology, migration & exit), §09 (CDN & China delivery), §11 (AI metering), §12 (personalization & profile residency), §08/S5 (preview).

ASK ALL 3

Phrased identically to every vendor so answers stay comparable: (1) “Can a market override one field and keep inheriting every untouched field from HQ — show it live.” (2) “What is your recommended architecture when AEM Assets remains the DAM in Phase 1?” (3) “What contractual rate limits apply for the migration window and steady-state — numbers, in the contract.” (4) “What does exit look like after two years — content, schema, assets, workflow config, and audit history? (We know no standard export includes audit history — what's your answer for a regulated automotive client?)”

Contentful

  1. “Can a Content-Management-API token publish an entry that sits in a workflow step before Approved — and can we get the enforcement model in writing?” Docs are silent; if the answer wanders to ‘the UI prevents…’, governance is cosmetic.
  2. “Which contractual commitments on roadmap, pricing and renewal terms survive the Salesforce close?” No customer FAQ exists; ‘nothing changes’ without paper = negotiating leverage sits with Salesforce.
  3. “At 30 markets, how do we express per-market approval rights within four edit/publish rules per workflow step?” Expected deflection: ‘use teams’ — which collapses per-market granularity; make them show it.
  4. “A TMS writes an empty string and silently kills the de-AT→de-DE fallback chain. What guardrails exist?” ‘What you set is what you get’ is documented behavior; we want tooling, not an apology.
  5. “EU residency covers storage, not processing — marketplace apps and Personalization edge profiles leave the region. What is the compliant reference architecture for a German OEM?” Bad answer: ‘most customers are fine with it.’

Contentstack

  1. “Management-API writes are 10 req/s per organization. What raised limits do OEM-scale customers run in production, contractually?” ‘Configurable depending on your plan’ is the docs' answer; we need a number with a signature.
  2. “Once a market localizes an entry, inheritance is gone permanently. Show us the recommended pattern for ‘DE overrides price only, keeps inheriting everything else.’” If the answer is ‘decompose into fragment entries’ — that's our modeling tax; price it.
  3. “Docs say 3 environments and 2 branches per stack; your services description says 5. Which is real, and what does 10 cost?” A vendor whose docs contradict their legal terms should resolve it in the room.
  4. “TISAX: not on your trust page. What's the assessment timeline — and which German automotive customers passed procurement without it?” For BMW, VDA ISA is not optional; ‘ISO covers it’ is a bad answer.
  5. “Your docs cap releases at 500 items and 25 per API call, and don't document webhook behavior on release deployment. What exactly fires when a 500-item release deploys — and what's the pattern for a coordinated 30-market go-live without hundreds of CI builds?” Expected: ‘use Automate’ — fine, but then Automate is load-bearing; ask for its guarantees.

Sanity

  1. “Show us the API-enforced path for ‘cannot publish until Approved’ — custom roles + content resources — and confirm in writing that Studio-level readOnly is not the security boundary.” If the demo leans on Studio UI, the enforcement story collapses under one curl command.
  2. “What uptime percentage goes in the contract, and when does Sanity hold its own ISO 27001 rather than pointing at GCP's?” No published SLA number exists today; procurement will not accept a line item without one.
  3. “Contractual EU region pinning for content and assets — when? Belgium-by-default is not a residency guarantee.” ‘Planned’ has no date in the docs; get one.
  4. “A 100-field car document with 30 locale-keyed field arrays — walk us through the attribute-path budget and what the Enterprise cap actually is, in writing.” 2,000/10,000/‘custom’ is the published ladder; ‘custom’ must become a number.
  5. “Content Releases have no approval gates and cap at 1,000 documents. What's the roadmap for gated releases, and the pattern for a market-launch train today?” Bad answer: ‘most customers script it’ — that's us building release governance too.