🗓️ Everything *[NYC] is back. A free gathering for AI builders. Sept 9 →

AI email campaigns built from your structured content

  • Give an AI email generator structured access to your campaign brief, brand voice, and audience segments so it writes per-segment drafts that sound on-brand instead of generic LLM copy.
  • Keep tone traits, prohibited words, and segment enrichment in Sanity documents the marketing team can update without a deploy or a prompt engineer.
  • A starter you can adapt: per-segment generation, multi-turn refinement in Studio, and provider-agnostic ESP dispatch ready to extend.
  • Ken Jones

    Community Experience Engineer at Sanity

  • Jarod Reyes

    Head of Developer Experience & Community at Sanity

  • Josh Hartman

    Senior Solution Architect at Sanity

Published:

Flowchart: AI-driven email generation process. Structured content and AI produce unique drafts, refined with user-AI chat, then automatically shipped to email marketing services like Klayvio and MailChimp.

Email service providers (ESP) are great at sending email. But they quietly become a content management system (CMS) through the side door while nobody is looking.

Here’s a scenario to illustrate our point. Say, someone needs to send a product-promotion email. Someone in Marketing opens the email service provider (ESP), pastes product specs from Notion, grabs prose from a Google Doc, and then pastes disclaimer language from a brand guide nobody has opened in six months. They then hit send. This one product email now lives in four different places, drifting independently.

Then somebody on the team adds AI to the mix. The team buys an AI generation tool, points it at a campaign goal, and gets back an email draft that sounds like slop. The VIP segment and the lapsed-customer segment get the same copy. Marketing rewrites both by hand. AI may have sped some things up, but it didn’t solve the complicated part.

You can do better than that. The four problems below all go away when AI generation lives inside your content, with a promotion created for each audience and every prompt assembled from documents with a single source of truth.

Some of the most cutting-edge companies and dev teams are using Sanity for this exact use case. In this guide we’ll show you exactly how it’s done and give you the code to get up and running with a prototype you can show the team today.

Disorganized content creates four problems that compound

The duplication tax

The brief lives in Notion. The copy lives in Google Docs. The product details live in Sanity. The tone lives in a brand guide. The actual campaign lives in the ESP. Five sources of truth for one email.

Every approval cycle bounces between three tools and a Slack thread, and at the end of it somebody still types {firstname} into a subject line.

The fix: one structured campaign brief, referenced by everything downstream, including the AI.

The tone-deaf AI tax

Default LLM generation writes the same email for everybody, because the prompt knows nothing about your business or your customers. The LLM fills the gap by making things up. Coupon codes that do not exist. Product names that are not yours. Personalization tokens like {{vip_tier_2026}} that no customer record has ever carried.

But that context already exists, in your CMS. Your brand voice is a document with tone traits and prohibited words. Your campaign brief is a landing page with a primary message. Pulling those into a prompt at generation time is the difference between a bland draft and a draft you would actually send.

The fix: assemble the prompt from the documents that already describe your audience, your brand, and your campaign. Generate once per segment.

The audit-trail black hole

Now we’re getting to the power of building marketing workflows on top of structured content. Once you’ve sent the email the metrics start flowing in but they all end up in the ESP. Maybe you download a CSV and then build a chart in sheets?

The data exists. It is just on the wrong side of the wall, separated from the brief that produced it, the segment it targeted, and the tone traits that might explain why one variant outperformed another. Worse, none of it loops back to the AI. Next quarter's generation makes the same mistakes last quarter's generation made, because nothing taught it otherwise.

When engagement data writes back into the same document model that produced the email, the loop closes. The next time you generate copy for the lapsed segment, the agent can read "the last three subject lines that hit 30%+ open rates for this audience" as prompt context. Past performance becomes better prompts in the future which becomes better material.

The fix: engagement webhooks that write performance data back onto the structured promotion document, where the next generation can read it.

How AI changes the math here

There is a reason "AI for email" usually disappoints. The model is only as smart as the context you feed it, and most SAAS setups feed it almost nothing.

Think about what a good editor knows when they sit down to write a campaign for the VIP segment:

  • The campaign's primary message and the underlying value proposition.
  • The brand's tone traits, prohibited words, subject-line patterns, legal constraints.
  • Who the VIP segment actually is. Affinity, typical copy tone, engagement tier.
  • What worked last time for similar audiences.

A generic AI prompt gets none of that. A structured prompt, assembled at call time from context the editorial team owns, gets all of it. That is the difference between a draft you throw away and a draft worth refining.

This is also why AI inside a CMS beats AI sitting next to one. The structured inputs are right there. The structured outputs land where the rest of the pipeline expects them. The model sees the same content the humans see. Your structured data does the heavy lifting and AI makes it conversational.

What actually works

Pull every problem above apart and the same architecture falls out.

The brief is a typed document, not a Notion page. The campaign carries goal, primary message, tone traits, target segments, and launch window as fields, not paragraphs. AI reads them. Downstream artifacts reference them. When the brief changes, every variant inherits the change.

Copy generation is per-segment and reads from real content. Brand voice is a document. Each segment is a document with editorial enrichment fields. The prompt is assembled at call time from those documents, and the AI runs once per target segment, not once per campaign.

Emails reference content, but don’t copy it. This is the quiet superpower. Your email links to the product document, not to a snapshot of the product. If a price changes Thursday morning, after the draft is approved the email still gets sent out with the new price. Nobody has to remember to update the email. Same goes for product images, promotional copy, disclaimers, and brand assets. Anything you would otherwise be tempted to inline. Reference it once, render it at send time. Your "stale email" failure mode quietly disappears.

The ESP is a delivery target, dumb on purpose. Approval workflow lives in your content system. So does the rendered email payload. The function that ships it to the ESP is the smallest possible adapter.

That is the principle. The starter described below is one implementation of it.

A practical example you can play with

The email-marketing starter is one shape of the answer. Klaviyo is the reference ESP. The same shape works for SendGrid, Mailchimp, Customer.io, or Resend.

The content model

Two editable document types do the work:

  • campaign: The campaign holds the brief: primary message, value proposition, emotional goal, tone traits, target segments, and urgency stage.
  • promotion: The promotion holds the segment-specific artifact, one per target segment. Inside each promotion is a field called emailSlots, an ordered array of composable blocks (emailHeader, emailSection, emailCTA, emailDivider, emailFooter) that gets rendered into HTML at send time.

Three reference documents feed the AI:

  • brandVoice: Brand voice is a singleton with tone traits, prohibited words, and writing style rules.
  • urgencyStage: Urgency stage tunes the campaign's pressure level.
  • segment: Segments are two-layered. The top half is read-only and synced from Klaviyo (externalId, name, type, description, memberCount, isActive). The bottom half is editable (affinityDescription, typicalCopyTone, engagementTier).
    Re-running the sync only updates the top half. Editors own the bottom half, and the AI reads both. That is how the segment-specific tone profile survives every membership refresh.

A separate klaviyoImport document tracks sync status (idle, requested, importing, imported, error), the last-imported timestamp, and counts. Useful for "did the sync run" diagnostics. Other ESPs slot into the same shape. Replace klaviyoImport with the provider equivalent, swap the synced fields on the segment document for whatever metadata that provider exposes, and the editorial enrichment layer stays put. Nothing downstream notices.

A workflow.state document tracks each promotion's lifecycle, with timestamped history entries for every transition.

Per-segment AI generation

A document action on the campaign loops over the campaign's referenced segments. For each one, it fetches the campaign brief, the brand voice, and the segment's enrichment, assembles a prompt, and calls Sanity Agent Actions (specifically, the generate action). The agent writes a typed promotion document. One run, one promotion per segment.

The target array lists the fields the agent is allowed to write. Output validates against your deployed schema, so generated content lands as typed Portable Text blocks, not a string blob. And instruction is assembled from real documents, so the prompt is never stale.

Here is what that produces in practice. A campaign targeting a VIP segment (typicalCopyTone: ["luxury", "exclusive", "invitation-only"], engagementTier: "vip") and a lapsed segment (typicalCopyTone: ["re-engagement", "incentive-driven"], engagementTier: "low") yields two genuinely different drafts. The VIP version opens with private-access language. The lapsed version opens with a discount and a softer "we miss you." Both follow the same brand voice rules. Both reference the same campaign brief. Neither needed a second prompt to get right.

Multi-turn refinement

Editors rarely accept a first draft. The refinement panel is where the actual work happens.

Open the "Refine with AI" inspector on a promotion and start talking to it. "Make the subject line more urgent." "Move the discount to the headline." "Now soften it for the lapsed audience." Each accepted suggestion patches the document live, so the email changes under the editor as they go.

The thread is keyed to the promotion ID, so the conversation survives reloads, context switches, and coming back to it the next morning. Hand the promotion off to a teammate and they see what was asked, what was tried, and what got accepted.

The agent writes the same way a human would. If the editor is working inside a content release, edits land in the release. There is no special "AI staging area." The agent uses the editor's permissions and the editor's perspective. If they cannot change the document, neither can the agent.

Code lives at studio/plugins/promotion/inspector/RefinementInspector.tsx and components/VariantRefinementPanel.tsx if you want to see how the inspector wires up to the content-agent client.

Approval and dispatch

Promotions move through draft to ready-for-review to approved. A Sanity Function fires on the workflow transition, reads the document, builds HTML, and hands it to the ESP. That function is the boundary between the editorial team and the email scheduler:

The function takes a promotion ID. It produces "an email got sent." The middle is interchangeable.

A few things worth knowing about how dispatch behaves:

  • Promotions can be re-sent. Approving a promotion that has already been sent fires the function again. Useful for re-launching to a refreshed segment, occasionally useful for "send it again, this time without the typo."
  • Every send is logged. The workflow.state document captures status (sent or error), timestamps for each transition, and the error message on failure. The function also writes the full ESP response body to the function logs when a send fails, so you do not have to guess which validation rule you tripped.
  • The HTML is built from references. Product images, prices, and copy blocks are all dereferenced at build time. The email going out reflects the document state at the moment of send, not the state when the draft was approved.

Try it yourself

pnpm bootstrap deploys the blueprint, deploys the schema, generates types, imports seed data, and prompts for an ESP API key. Studio runs at localhost:3333, frontend at localhost:3000. Hit "Generate Promotions" on the seeded campaign and watch the per-segment AI flow round-trip in under a minute.

To swap ESPs, point your AI coding agent at the repo and ask it to find every file that touches Klaviyo, then replace those calls with your ESP's SDK. The integration footprint is small (one dispatch function, one optional segment importer, one env var name), and most of the work is mechanical translation of the API shape. We are working on a Sanity agent skill that will assist with this directly. Until then, a well-prompted agent with the right context handles it cleanly.

The content model, the AI generation flow, the refinement panel, the preview, the approval workflow, the engagement data. None of that has to change.

More in this series

This guide is part of AI Content Operations, a series on building AI-powered content workflows with Sanity.

More on this topic