---
name: gentic-email-marketing
description: "Give your AI agent your Klaviyo data. Campaign performance that matches your Klaviyo dashboard, plus semantic search over what your emails actually said — subject lines, body copy, image alt text and AI descriptions of the creative. Reporting is free; content indexing is 5¢ per email campaign."
license: MIT
metadata:
  author: gentic
  version: "1.0.0"
---

# Gentic Email Marketing

Connect any AI agent to your Klaviyo account. Pull campaign performance — opens, clicks, conversions and revenue — in numbers that match your Klaviyo dashboard exactly. Sync the shape of your account into your organization's Brain, then search what your emails actually SAID, in natural language, including the words printed inside the images. Read-only throughout.

## When to apply

- User wants campaign performance from Klaviyo — opens, clicks, conversions, revenue — for a timeframe.
- User wants to know which campaigns drove the most revenue, or how open and click rates are trending.
- User wants to find emails by what they SAID — a product, an offer, a promotion — rather than by name or date.
- User wants to know what an email's creative actually showed, including text inside images.
- User wants their Klaviyo campaigns, flows, lists, segments or metrics synced into their Brain for analysis.
- User wants to join email engagement against orders, subscriptions, loyalty or SMS data.
- User asks about SMS campaign performance — Klaviyo campaigns carry a send_channel, so SMS sends report here too.

## Tools

| Tool | Description | Cost |
|------|-------------|------|
| `klaviyo_connection_status` | Check whether the calling organization has connected Klaviyo (the email and SMS marketing platform). Returns `{ connected, account_id, updated_at }` when connected, or `{ connected: false }` with a hint otherwise. Free. Call before klaviyo_sync or klaviyo_report so a missing integration produces actionable guidance rather than a failed run. Never returns the API key.  Set check_live to true to also VERIFY the stored key against Klaviyo with a single one-row read. Worth doing once after connecting a new Klaviyo account: a key that is present is not the same as a key that works, and the cheapest time to learn the difference is before a sync. | Free |
| `klaviyo_report` | Get campaign performance from Klaviyo — opens, clicks, conversions and revenue per campaign, for a chosen timeframe. Uses the org's connected Klaviyo integration (dashboard → Integrations; no API key is passed here). Returns one row per campaign message with its `send_channel`, `campaign_id` and the requested statistics. READ-ONLY: this reads reporting figures and changes nothing in Klaviyo.  NUMBERS MATCH THE KLAVIYO DASHBOARD. This uses Klaviyo's Reporting API, which groups by attributed send time — the same basis the Klaviyo UI uses. Klaviyo's Metric Aggregates endpoint groups by event time instead and produces DIFFERENT numbers for the same window; we deliberately expose only the one surface, so a figure here can be compared to the customer's Klaviyo dashboard without reconciling two definitions.  Requires a conversion metric — the Klaviyo metric that counts as a conversion, usually 'Placed Order'. PASS `conversion_metric_name: 'Placed Order'` and it is resolved against this organization's own synced `klaviyo_metrics` table; you do not need to look up an id. That resolution needs a prior klaviyo_sync (the `metrics` resource) — if none has run, the error says so and tells you what to run. There is deliberately NO default metric: which metric counts as a conversion changes every number in the report, so it is always an explicit choice. FREE. | Free |
| `klaviyo_search_emails` | Search the CONTENT of this organization's Klaviyo campaigns by natural language — 'which emails talked about the iGel mask?', 'find the sends that mentioned free shipping', 'what did we say about the Black Friday sale?'. Searches subject lines, preview text, body copy, image alt text and (where they were generated) AI descriptions of what the images show, ranked semantically. Returns the campaign, its subject line, send time and a snippet.  Requires `klaviyo_sync_email_content` to have been run first — that is the tool that fetches and indexes the content; this one only reads it. For campaign PERFORMANCE numbers (opens, clicks, revenue) use `klaviyo_report`; for the list of campaigns and their configuration use `query_data` over `klaviyo_campaigns`.  FREE. | Free |
| `klaviyo_sync` | Sync email-marketing configuration from Klaviyo into this organization's data backend — READ-ONLY, verbatim, one BOUNDED chunk per call. Uses the org's connected Klaviyo integration (dashboard → Integrations; no API key is passed here). Warehouses five resources into per-org tables: `klaviyo_campaigns` (both email and SMS), `klaviyo_flows`, `klaviyo_lists`, `klaviyo_segments`, `klaviyo_metrics`, upserted on their id so re-syncs refresh rows in place. This syncs the SHAPE of the account — campaigns, flows, lists, segments and metric definitions — not per-recipient events or profiles. For campaign performance numbers use klaviyo_report. BOUNDED + RESUMABLE: each call pages only until `max_pages_per_run` pages OR `max_seconds_per_run` seconds are reached, then returns `has_more` + `next_cursor: { step, cursor }`. Call again passing that back as `cursor` until `has_more` is false. Returns `{ synced: { <resource>: count }, has_more, next_cursor, persisted, pages_fetched }`. FREE. | Free |
| `klaviyo_sync_email_content` | Pull the ACTUAL CONTENT of this organization's Klaviyo campaigns — subject lines, preview text, the visible body copy of the email template, the alt text of every image, and (optionally) an AI description of what each image shows — then embed it so `klaviyo_search_emails` can search it semantically. Uses the org's connected Klaviyo integration; no API key is passed here.  Run `klaviyo_sync` with `resources: ["campaigns"]` FIRST — this tool reads the campaign list from the warehouse and fetches content for campaigns that don't have it yet.  BOUNDED + RESUMABLE: each call processes at most `max_campaigns_per_run` campaigns (or until `max_seconds_per_run`), then returns `has_more`. Call again until `has_more` is false — no cursor to pass back, because each run only picks up campaigns that still have no content.  COST: 5¢ per email campaign, 1¢ per SMS campaign. A campaign whose content is already stored and unchanged costs NOTHING, so re-running is safe and a nightly schedule only pays for new sends.  Most marketing emails carry their message inside images. Set `describe_images: true` to have the images read by a vision model so the text printed on the creative becomes searchable too; the descriptions are included in the 5¢, capped at 10 images per campaign. The response reports `alt_coverage` and warns when an account's emails are too image-heavy for text-only search to work well. | Variable |

## Workflow

### 1. Connect Klaviyo once, then work without credentials

The Email Marketing server sources the Klaviyo connection (the Private API key) from the org's connected integration (Gentic dashboard → Integrations → Klaviyo). You never pass credentials to the tools. Call `klaviyo_connection_status` first — it's free and returns `{ connected, account_id, updated_at }`, and never returns the API key. Pass `check_live: true` to actually verify the stored key against Klaviyo with a single one-row read. Do that once after a new connection: a key that is PRESENT is not the same as a key that WORKS, and the cheapest time to learn the difference is before a sync.

### 2. Reporting needs no sync — but it does need a conversion metric

`klaviyo_report` is **free** and READ-ONLY, and you can call it before syncing anything. It returns one row per campaign message with its `send_channel`, `campaign_id` and the requested statistics. It requires a conversion metric, and there is deliberately **no default** — which metric counts as a conversion changes every number in the report, so it is always an explicit choice. Pass `conversion_metric_name: 'Placed Order'` (usually the right one) and it resolves against the org's own synced `klaviyo_metrics` table; you don't need to look up an id. That resolution needs a prior `klaviyo_sync` of the `metrics` resource — if none has run, the error tells you exactly that. An ambiguous or unknown name comes back with the real candidate list, so read the error rather than guessing again.

### 3. ⚠️ Why these numbers match the customer's Klaviyo dashboard

`klaviyo_report` uses Klaviyo's **Reporting API**, which groups by **attributed send time** — the same basis the Klaviyo UI uses. Klaviyo also has a Metric Aggregates endpoint that groups by **event time** and produces DIFFERENT figures for the same window. We deliberately expose only the one surface, so a number you quote can be compared to the customer's own dashboard without reconciling two definitions. If a figure doesn't match what someone sees in Klaviyo, the cause is the timeframe or the conversion metric — not two competing definitions of the same statistic.

### 4. Sync the SHAPE of the account with `klaviyo_sync`

`klaviyo_sync` is **free** and READ-ONLY. It warehouses five resources verbatim into per-org tables: `klaviyo_campaigns` (both email AND SMS), `klaviyo_flows`, `klaviyo_lists`, `klaviyo_segments` and `klaviyo_metrics`, upserted on their id so re-syncs refresh rows in place. Be precise about what this is: it syncs the **shape** of the account — campaigns, flows, lists, segments and metric definitions — **not** per-recipient events or profiles. There is no per-person event stream here. For performance figures use `klaviyo_report`; for the campaign list and its configuration use `query_data` over `klaviyo_campaigns`.

### 5. Walk it with bounded, resumable pagination

Each `klaviyo_sync` call is BOUNDED: it pages until `max_pages_per_run` pages OR `max_seconds_per_run` seconds, then returns `has_more` and `next_cursor: { step, cursor }`. Call again passing that back as `cursor` until `has_more` is false. It returns `{ synced: { <resource>: count }, has_more, next_cursor, persisted, pages_fetched }` — report `persisted` per resource and whether `has_more` is still true, because a partial walk that looks complete is the failure mode worth catching. Use `resources` to sync only what you need; syncing just `metrics` is enough to unblock `klaviyo_report`.

### 6. Indexing email content is the one tool that costs money

`klaviyo_sync_email_content` pulls the ACTUAL CONTENT of campaigns — subject lines, preview text, the visible body copy, and the alt text of every image — then embeds it so it can be searched. Run `klaviyo_sync` with `resources: ["campaigns"]` FIRST: this tool reads the campaign list from the warehouse and fetches content only for campaigns that don't have it yet. **Cost: 5¢ per email campaign, 1¢ per SMS campaign.** A campaign whose content is already stored and unchanged costs **nothing**, so re-running is safe and a nightly schedule only pays for new sends. It's BOUNDED and RESUMABLE via `max_campaigns_per_run` / `max_seconds_per_run` and returns `has_more` — but there is **no cursor to pass back**, because each run simply picks up campaigns that still have no content. On a large back catalogue, say what a full backfill will cost before starting one.

### 7. Most marketing emails hide their message inside images

Set `describe_images: true` and the images are read by a vision model, so the text printed on the creative becomes searchable too — which for a lot of email programs is where the actual offer lives. The descriptions are **included in the 5¢**, capped at 10 images per campaign. The response reports `alt_coverage` and warns when an account's emails are too image-heavy for text-only search to work well. Treat that warning as the signal to turn `describe_images` on rather than concluding the search is broken.

### 8. Search the content with `klaviyo_search_emails`

`klaviyo_search_emails` is **free** and searches the indexed content semantically — 'which emails talked about the iGel mask?', 'find the sends that mentioned free shipping'. It returns the campaign, its subject line, send time and a snippet, with `filters`, `include_body` and `limit` to narrow the result. It only READS the index; if it comes back empty, the likely cause is that `klaviyo_sync_email_content` hasn't been run for those campaigns — check that before telling the user the emails don't mention the thing.

### 9. Route the three question types to the right tool

These are easy to confuse and the wrong choice produces a confidently wrong answer. Performance numbers (opens, clicks, revenue) → `klaviyo_report`. What an email SAID → `klaviyo_search_emails`. The list of campaigns and their configuration → `query_data` over `klaviyo_campaigns`. And note that `klaviyo_campaigns` and `klaviyo_report` both cover **SMS as well as email** — each row carries a `send_channel` — so SMS campaign performance is answered here, not on `/sms-marketing`, whose Postscript data has no message-performance in it at all.

### 10. Present results clearly

Don't dump raw JSON. Summarize the numbers, name the timeframe and say which conversion metric was used — a report is not interpretable without it. After a sync, report `persisted` per resource and whether `has_more` is true. After a content index run, say how many campaigns were processed and what it cost. If you're about to backfill a large catalogue, give the estimate before you spend it, not after.

## Notes

- Organization-scoped: the Klaviyo connection comes from the org's connected integration (dashboard → Integrations → Klaviyo). No credential is ever passed to the tools, and `klaviyo_connection_status` never returns the API key.
- Cost: `klaviyo_connection_status`, `klaviyo_report`, `klaviyo_sync` and `klaviyo_search_emails` are all **free**. Only `klaviyo_sync_email_content` costs money — **5¢ per email campaign, 1¢ per SMS campaign** — and a campaign already stored unchanged costs nothing, so re-running is safe. Pricing is pulled live from the Gentic MCP manifest.
- Everything here is READ-ONLY: nothing sends, schedules, edits or archives anything in Klaviyo.
- `klaviyo_report` matches the customer's Klaviyo dashboard because it uses the Reporting API, which groups by attributed send time — the same basis as the Klaviyo UI. Klaviyo's Metric Aggregates endpoint groups by event time and gives different numbers for the same window; only the one surface is exposed, deliberately.
- `klaviyo_report` has **no default conversion metric**. Pass `conversion_metric_name` (usually 'Placed Order'); it resolves against the org's synced `klaviyo_metrics` table, which requires a prior `klaviyo_sync` of the `metrics` resource.
- `klaviyo_sync` warehouses the **shape** of the account — `klaviyo_campaigns`, `klaviyo_flows`, `klaviyo_lists`, `klaviyo_segments`, `klaviyo_metrics` — not per-recipient events or profiles.
- `klaviyo_campaigns` and `klaviyo_report` cover **both email and SMS**; each row carries a `send_channel`. SMS campaign performance is answered here.
- Content search is two-stage: `klaviyo_sync_email_content` fetches and indexes, `klaviyo_search_emails` only reads the index. An empty search usually means the content wasn't indexed, not that it isn't there.
- `describe_images: true` has images read by a vision model so text printed on the creative is searchable; included in the 5¢, capped at 10 images per campaign. The response reports `alt_coverage` and warns when an account is too image-heavy for text-only search.
- Syncs are bounded and resumable. `klaviyo_sync` returns `has_more` + `next_cursor: { step, cursor }` to pass back as `cursor`; `klaviyo_sync_email_content` returns `has_more` with **no cursor**, because each run picks up whatever still has no content.

## Tool details

- `klaviyo_connection_status` — Check whether the calling organization has connected Klaviyo (the email and SMS marketing platform). Returns `{ connected, account_id, updated_at }` when connected, or `{ connected: false }` with a hint otherwise. Free. Call before klaviyo_sync or klaviyo_report so a missing integration produces actionable guidance rather than a failed run. Never returns the API key.  Set check_live to true to also VERIFY the stored key against Klaviyo with a single one-row read. Worth doing once after connecting a new Klaviyo account: a key that is present is not the same as a key that works, and the cheapest time to learn the difference is before a sync.
  - `check_live` (boolean) — When true, make one minimal read against Klaviyo to verify the stored key actually works. Adds a round-trip; still free.
- `klaviyo_report` — Get campaign performance from Klaviyo — opens, clicks, conversions and revenue per campaign, for a chosen timeframe. Uses the org's connected Klaviyo integration (dashboard → Integrations; no API key is passed here). Returns one row per campaign message with its `send_channel`, `campaign_id` and the requested statistics. READ-ONLY: this reads reporting figures and changes nothing in Klaviyo.  NUMBERS MATCH THE KLAVIYO DASHBOARD. This uses Klaviyo's Reporting API, which groups by attributed send time — the same basis the Klaviyo UI uses. Klaviyo's Metric Aggregates endpoint groups by event time instead and produces DIFFERENT numbers for the same window; we deliberately expose only the one surface, so a figure here can be compared to the customer's Klaviyo dashboard without reconciling two definitions.  Requires a conversion metric — the Klaviyo metric that counts as a conversion, usually 'Placed Order'. PASS `conversion_metric_name: 'Placed Order'` and it is resolved against this organization's own synced `klaviyo_metrics` table; you do not need to look up an id. That resolution needs a prior klaviyo_sync (the `metrics` resource) — if none has run, the error says so and tells you what to run. There is deliberately NO default metric: which metric counts as a conversion changes every number in the report, so it is always an explicit choice. FREE.
  - `conversion_metric_name` (string) — Name of the Klaviyo metric that counts as a conversion — usually "Placed Order". PREFER THIS over conversion_metric_id: the name is resolved against the org's own synced `klaviyo_metrics` table, so you never need to look an id up by hand. Requires a prior klaviyo_sync (metrics resource). Matching is case-insensitive, exact first, then substring; an ambiguous or unknown name comes back with the real candidate list.
  - `conversion_metric_id` (string) — Klaviyo metric id, if you already have it. Usually unnecessary — pass conversion_metric_name instead and it will be resolved for you.
  - `timeframe` (string, enum: `last_7_days` | `last_30_days` | `last_90_days` | `last_12_months` | `this_month` | `last_month` | `this_year` | `last_year`) — Reporting window. Defaults to last_30_days.
  - `statistics` (array of string) — Which statistics to return. Omit for a standard set (delivered, open/click/conversion rates, conversion value).
- `klaviyo_search_emails` — Search the CONTENT of this organization's Klaviyo campaigns by natural language — 'which emails talked about the iGel mask?', 'find the sends that mentioned free shipping', 'what did we say about the Black Friday sale?'. Searches subject lines, preview text, body copy, image alt text and (where they were generated) AI descriptions of what the images show, ranked semantically. Returns the campaign, its subject line, send time and a snippet.  Requires `klaviyo_sync_email_content` to have been run first — that is the tool that fetches and indexes the content; this one only reads it. For campaign PERFORMANCE numbers (opens, clicks, revenue) use `klaviyo_report`; for the list of campaigns and their configuration use `query_data` over `klaviyo_campaigns`.  FREE.
  - `query` (string, required) — Natural-language search query, e.g. 'emails about the winter sale' or 'anything mentioning a clinical study'.
  - `filters` (string) — Optional SQL filter over the structured columns: channel, campaign_name, subject, send_time (TIMESTAMP), template_name. Example: "channel = 'email' AND send_time > '2026-01-01'". Read-only expressions only.
  - `include_body` (boolean) — Return the FULL content of each match — body text, every image URL, every alt text and every image description (default false). By default each result carries a ~400-character `content_excerpt` of the matched content instead, which is enough to say what an email was about; the full arrays run to thousands of tokens per result and are mostly image URLs. Turn this on when you need to quote the copy exactly or walk the images.
  - `limit` (integer) — Maximum matches to return (default 10, max 50).
- `klaviyo_sync` — Sync email-marketing configuration from Klaviyo into this organization's data backend — READ-ONLY, verbatim, one BOUNDED chunk per call. Uses the org's connected Klaviyo integration (dashboard → Integrations; no API key is passed here). Warehouses five resources into per-org tables: `klaviyo_campaigns` (both email and SMS), `klaviyo_flows`, `klaviyo_lists`, `klaviyo_segments`, `klaviyo_metrics`, upserted on their id so re-syncs refresh rows in place. This syncs the SHAPE of the account — campaigns, flows, lists, segments and metric definitions — not per-recipient events or profiles. For campaign performance numbers use klaviyo_report. BOUNDED + RESUMABLE: each call pages only until `max_pages_per_run` pages OR `max_seconds_per_run` seconds are reached, then returns `has_more` + `next_cursor: { step, cursor }`. Call again passing that back as `cursor` until `has_more` is false. Returns `{ synced: { <resource>: count }, has_more, next_cursor, persisted, pages_fetched }`. FREE.
  - `resources` (array of string) — Subset of resources to sync this run, in order. Any of: campaigns, flows, lists, segments, metrics. Omit to sync all five. `campaigns` always walks both the email and SMS channels.
  - `max_pages_per_run` (integer) — Cap on pages fetched this call (default 20, hard max 60). Page more via next_cursor, never via a bigger run.
  - `max_seconds_per_run` (integer) — Wall-clock budget in seconds (default 60, hard max 120). The walk stops after the current page once exceeded; resume with next_cursor.
  - `cursor` — Resume point from a prior call's `next_cursor` — { step, cursor }. `step` is a resource name, or `campaigns:email` / `campaigns:sms`. Omit or null to start from the beginning.
- `klaviyo_sync_email_content` — Pull the ACTUAL CONTENT of this organization's Klaviyo campaigns — subject lines, preview text, the visible body copy of the email template, the alt text of every image, and (optionally) an AI description of what each image shows — then embed it so `klaviyo_search_emails` can search it semantically. Uses the org's connected Klaviyo integration; no API key is passed here.  Run `klaviyo_sync` with `resources: ["campaigns"]` FIRST — this tool reads the campaign list from the warehouse and fetches content for campaigns that don't have it yet.  BOUNDED + RESUMABLE: each call processes at most `max_campaigns_per_run` campaigns (or until `max_seconds_per_run`), then returns `has_more`. Call again until `has_more` is false — no cursor to pass back, because each run only picks up campaigns that still have no content.  COST: 5¢ per email campaign, 1¢ per SMS campaign. A campaign whose content is already stored and unchanged costs NOTHING, so re-running is safe and a nightly schedule only pays for new sends.  Most marketing emails carry their message inside images. Set `describe_images: true` to have the images read by a vision model so the text printed on the creative becomes searchable too; the descriptions are included in the 5¢, capped at 10 images per campaign. The response reports `alt_coverage` and warns when an account's emails are too image-heavy for text-only search to work well.
  - `campaign_ids` (array of string) — Specific campaign ids to (re-)fetch, e.g. to pick up an edited send. Omit to process campaigns that have no content stored yet. Content that is byte-identical to what is already stored is not charged.
  - `describe_images` (boolean) — Describe each image with a vision model so on-image copy becomes searchable (default false). Included in the per-campaign price, capped at 10 images per campaign. Adds noticeable time per campaign.
  - `max_campaigns_per_run` (integer) — Cap on campaigns processed this call (default 25, hard max 100). Process more by calling again, never by raising this past the ceiling.
  - `max_seconds_per_run` (integer) — Wall-clock budget for this call (default 60s, hard max 240s).

---

_This SKILL.md is generated from the live Gentic MCP manifest. Tool names, descriptions, and pricing are always current. Connect Gentic Email Marketing at https://gentic.co/email-marketing._
