name: pre-send-qa description: Pre-flight QA for an email campaign — merge tags, broken links, spam triggers, compliance, mobile rendering — before you hit send version: "1.0.0" author: Cogny AI requires: cogny-mcp platforms: [klaviyo, mailchimp, rule, get-a-newsletter] user-invocable: true argument-hint: "[campaign ID | paste HTML]" allowed-tools:
Cogny Cloud (aggregated) namespace
- mcp__cogny__klaviyo__*
- mcp__cogny__mailchimp__*
- mcp__cogny__rule__*
- mcp__cogny__get_a_newsletter__*
- mcp__cogny__create_finding
Cogny Solo / Lite (per-ESP direct) namespace
- mcp__klaviyo__*
- mcp__mailchimp__*
- mcp__rule__*
- mcp__get_a_newsletter__*
- WebFetch
- Bash
- Read
- Write
Pre-Send QA
Catch the embarrassing stuff before it ships to your list. Runs a full pre-flight check on an email: merge tag fallbacks, broken links, spam triggers, CAN-SPAM / GDPR footers, mobile rendering, size limits, accessibility.
Requires: Cogny MCP for campaign-ID mode. Paste-HTML mode works without MCP (but without send-context checks).
Usage
/pre-send-qa <campaign-or-draft-id> — QA a specific campaign (or draft, for Get a Newsletter) in your ESP
/pre-send-qa — then paste the email HTML/MJML/text when prompted
Steps
1. Load the email
Mode A — Campaign ID (MCP). Detect which ESP is connected (check both namespaces: mcp__cogny__<svc>__* and mcp__<svc>__*) and use the right tool:
| ESP | Fetch tool | What the ID refers to |
|---|---|---|
| Klaviyo | get_campaign | Campaign object |
| Mailchimp | tool_get_campaign | Campaign object |
| Rule | tool_get_campaign | Campaign object |
| Get a Newsletter — pre-send | tool_get_draft | A draft that hasn't been sent yet |
| Get a Newsletter — post-send forensics | tool_get_sent + tool_get_report | A sent item |
For every ESP extract: subject, preheader, from name, from email, reply-to, HTML body, text body, target segment/list, scheduled send time. Also fetch the associated list/audience size so Section 2.8 has recipient count.
Get a Newsletter has no single "campaign" object. If the user passes an ID, try tool_get_draft first (pre-send QA is the common use), fall back to tool_get_sent if that 404s. If they want to QA a scheduled send, use tool_list_scheduled to find it.
Mode B — Paste:
- Ask for subject line, preheader, from name/email, and body HTML. Accept multi-line paste.
- Section 2.8 (send context) is skipped in paste mode — no recipient count, no segment, no schedule.
2. Run the checklist
Walk through every category. For each item, mark [PASS], [WARN], or [FAIL] and include the specific offending content.
2.1 Headers & routing
- Subject line present — not empty
- Subject length ≤ 55 chars (mobile truncation threshold)
- Preheader present — not empty, not a duplicate of subject
- Preheader length 85–100 chars (too short = wasted real estate; too long = truncated)
- From name is branded — not "noreply", not "info"
- From email uses sending subdomain — flag
@gmail.com/@outlook.comfrom addresses (deliverability killer for bulk) - Reply-to is monitored — warn if it's a noreply@
2.2 Merge tags / personalization
Scan body + subject + preheader for:
{{ ... }},{% ... %},*|...|*,[[...]],%%...%%,{...}— depending on ESP syntax- All merge tags well-formed — balanced brackets, closed blocks
- All merge tags have fallbacks — e.g.,
{{ first_name|default:"there" }}(Klaviyo),*|IFNOT:FNAME|*there*|ELSE:*|FNAME|**|END:IF|*(Mailchimp). Flag any token without a fallback as FAIL. - "Hi ," or "Hello ," anti-pattern — substring search for bare punctuation after a greeting. This is the #1 embarrassing merge-tag failure.
- Dynamic content blocks closed — open
{% if %}must have{% endif %}; same for loops - No raw tokens in subject line — e.g., subject reading literally "Hi {{ first_name }}"
2.3 Links
Extract all <a href="..."> from the HTML. For each:
- URL is absolute — no
href="/path"without domain; nohref="#"unless intentional anchor - URL resolves — HEAD-check each unique URL (cap at 30 requests, dedupe). Flag non-200s.
- No
localhost/127.0.0.1/.local— dev leak - No staging/dev domains — flag
*.staging.*,*.dev.*,stage.,preprod. - UTM parameters present on all outbound links — warn on links missing
utm_source/utm_medium/utm_campaign - UTM consistency — all links should share
utm_campaign; flag divergence - Link count — report total. Warn if >20 (clutter). Warn if <1 (no CTA).
- CTA destination consistency — multiple buttons labeled "Shop now" should generally point to the same URL
2.4 Images
- All
<img>havealttext — non-empty, descriptive - No pure-image email — flag if text-to-image ratio is <40% text. Hero-image-only emails trigger spam filters.
- Image URLs are absolute + HTTPS
- Image URLs aren't hotlinked from sketchy hosts — flag if images are on
imgur,cloudinary demo, etc. instead of a brand CDN
2.5 Compliance
- Unsubscribe link present — regex for
unsubscribe,preferences,opt out - Unsubscribe link is visible — not
display:noneor font-size:1px - Physical postal address in footer — CAN-SPAM requirement. Regex for a street/city pattern or flag if missing.
- List-Unsubscribe header — flag if ESP allows setting it but it's not configured (one-click unsubscribe is required by Gmail/Yahoo for bulk senders)
- Preference center link — nice-to-have; warn if only unsubscribe exists
2.6 Spam triggers
Scan subject + preheader + body for classic triggers and report offenders:
- Word-level: free, winner, congratulations, guaranteed, risk-free, act now, click here, limited time, no obligation, viagra (lol), make money, earn extra, $$$
- Punctuation: more than one
!in subject,!!!,??? - Casing: ALL CAPS WORDS longer than 3 chars in subject
- Symbols:
$$$,★, excessive emoji (>3) - HTML: hidden text (color matches background), tiny font, embedded JavaScript, background images on
<body>
Output each offender with context:
🔴 Subject contains "!!!"
🟡 Body has 4 instances of ALL CAPS words: "LIMITED", "TODAY", "ACT NOW", "DON'T"
2.7 Rendering & size
- HTML size < 102 KB — Gmail clips at 102 KB. Report actual size.
- Single root
<table>layout (desktop email standard) OR uses MJML — flag flex/grid as a deliverability risk - Inline CSS — style attributes vs
<style>blocks. Many clients strip<head>styles. - Mobile viewport meta —
<meta name="viewport" content="width=device-width"> - Font stack has fallbacks — no
font-family: "Custom Font";without a web-safe fallback - No
<script>, no<form>, no external fonts loaded via JS — all stripped by most clients
2.8 Send context (MCP mode only)
- Target segment is populated — recipient count > 0
- Audience isn't the entire list for a promotional send — flag "All Subscribers" when there's a segmented alternative
- Scheduled time isn't 3am recipient-local — flag weird send hours
- Not a duplicate send — check last 30 days for a campaign with the same subject going to the same segment
3. Report
Pre-send QA: <campaign name | "pasted email">
Recipient count: <N> Send ETA: <time or "not scheduled">
Overall: <READY TO SEND | FIX BEFORE SEND | CRITICAL ISSUES>
🔴 Critical (will break or embarrass)
— <specific issue with offending text / URL / line>
— ...
🟡 Warnings (fix if you have time)
— ...
🟢 Passed
— <count> checks passed
──
Recommended fix order:
1. ...
2. ...
Merge-tag fallback cheatsheet for <ESP>:
[ESP-specific syntax examples]
4. Finding (optional)
If critical issues are found and the campaign is scheduled to send soon, create a finding:
{
"title": "Campaign '<name>' has critical QA issues — blocked send",
"body": "<enumerated issues>",
"action_type": "pre_send_fix",
"priority": "high"
}
5. Do not auto-fix
Never modify the campaign automatically. Always present the diff and let the user approve each fix.
Notes
- ESP-specific merge tag syntax:
- Klaviyo:
{{ first_name|default:"there" }} - Mailchimp:
*|FNAME|*with*|IFNOT:FNAME|*there*|ELSE:*|FNAME|**|END:IF|* - Rule / Get a Newsletter:
[Firstname]with platform-specific fallback syntax
- Klaviyo:
- Spam-trigger lists are heuristics; modern spam filters use ML + reputation. Treat warnings as "raise eyebrow, check reputation" not "instant spam".