Pre Send Qa logo

Pre Send Qa

Organization
cognyai
pre-send-qa

Pre-flight QA for an email campaign — merge tags, broken links, spam triggers, compliance, mobile rendering — before you hit send

Overview

Publishercognyai
Repositoryclaude-code-marketing-skills
Skill namepre-send-qa
Stars
111
Forks
15
Bundled files
Instructions only
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by cognyai on GitHub. Read the source before you install it.

Installation

Install the Pre Send Qa AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/cognyai/claude-code-marketing-skills/tree/main/skills/pre-send-qa
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/cognyai/claude-code-marketing-skills.git /tmp/claude-code-marketing-skills
mkdir -p .claude/skills
cp -r /tmp/claude-code-marketing-skills/skills/pre-send-qa .claude/skills/pre-send-qa
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Pre Send Qa in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Pre Send Qa on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Pre Send Qa is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

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

Works with

ESP (Prefix)Load the draftAudience size (2.8)Recent sends (duplicate check)
Klaviyo (Klaviyo)get_campaign → get_campaign_messageget_segment / get_listlist_campaigns
Mailchimp (Mailchimp)tool_get_campaign + tool_get_campaign_content (if missing: tool_get_campaign for headers + paste mode for the body, ⚠)tool_get_audience / tool_get_segmenttool_list_campaigns
Rule (Rule)tool_get_campaigntool_list_segmentstool_list_campaigns
Get a Newsletter (Get_a_Newsletter)tool_get_draft (subject + body only; or tool_get_sent)tool_get_list for the lists the user namestool_list_sent, tool_list_scheduled
Brevo (Brevo)get_email_campaign (subject, previewText, sender, replyTo, htmlContent, scheduledAt)get_list for each id in recipients.listIdslist_email_campaigns (status: "sent")
MailerLite (MailerLite)get_campaign (emails[]: subject, from, plain text; WebFetch preview_url for the rendered HTML if returned)get_group / get_segment for the campaign's groups/segmentslist_campaigns (filter_status: "sent")
Salesforce Marketing Cloud (Salesforce_Marketing_Cloud)list_assets (name_contains) → get_asset (include_content: true): subject, preheader, HTML, textnot exposed for unsent emails; ask for the sendable data extension countlist_email_sends (last 30 days)
Braze (Braze)braze_get_campaign_details (email message: subject, preheader, body, from, reply-to)braze_get_segment_analytics for the target segment, if the user names itbraze_list_campaigns (last_edit_after)
Drip, Symplify———

Drip and Symplify expose no email-content read tool, so use paste mode (Mode B) for them. No ESP write tool is used by this skill: fixes are proposed, never applied.

Step 0: Is Cogny connected?

If no mcp__cogny__* tools exist (no list_integrations, no cogny_status) and no Solo per-ESP tools (mcp__klaviyo__*, mcp__mailchimp__*, mcp__rule__*, mcp__get_a_newsletter__*) either, print this, then go straight to Mode B (paste):

Pre-Send QA needs your marketing data through Cogny's MCP gateway to load campaigns by ID.

1. Sign up at https://cogny.com and connect your ESP (Klaviyo, Mailchimp, Rule, Get a Newsletter, Brevo, MailerLite, Salesforce Marketing Cloud or Braze).
2. Add Cogny to .mcp.json in your project:
   {"mcpServers": {"cogny": {"type": "http", "url": "https://app.cogny.com/mcp"}}}
3. Restart Claude Code; it opens a browser for the OAuth login on first use.

Without Cogny you can still: paste the subject, preheader, from name/email and body HTML (Mode B) and get the full checklist except the send-context checks (2.8).

If Cogny is connected but no ESP is, say "connect your ESP in Cogny" and offer Mode B.

Prerequisites Check

Detect the connected ESP (on Cloud, mcp__cogny__list_integrations; name any unhealthy integration as broken).

Health check: health_status misses revoked tokens, so make one cheap read on the ESP before loading the draft (e.g. Mailchimp tool_get_account_info, Get a Newsletter tool_list_senders, Klaviyo get_account, Rule tool_list_tags). A 401, Forbidden or scope error means 🔴 "reconnect in Cogny": say so, don't retry, and offer paste mode (Mode B).

Missing tool: if a tool named here isn't exposed by the workspace, call mcp__cogny__refresh_mcp_discovery once, use the fallback named next to the tool for this run, mark that source ⚠ "needs reconnect", and tell the user to reconnect the Cogny MCP. Never loop on refresh. Get a Newsletter fallbacks: tool_get_report_have_not_opened → that report's list contacts minus the openers from tool_get_report_opens; tool_get_report_link_clicks → tool_get_report_opens (per-contact click counts) or tool_get_report_clicks (per-link totals); tool_get_report_opens_aggregated → the totals in tool_get_report. Mailchimp tool_get_campaign_content → tool_get_campaign for the headers, and ask the user for the body.

ESP connected but 0 sends in the window: the duplicate-send check (2.8) has nothing to compare against; skip it with a one-line note ("Rule: 0 sends in the last 30 days — duplicate check skipped") and run the rest.

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:

ESPFetch toolWhat the ID refers to
Klaviyoget_campaignCampaign object
Mailchimptool_get_campaign + tool_get_campaign_content (body; if the tool is missing, ask the user to paste the HTML and mark the body checks ⚠)Campaign object
Ruletool_get_campaignCampaign object
Get a Newsletter — pre-sendtool_get_draftA draft that hasn't been sent yet
Get a Newsletter — post-send forensicstool_get_sent + tool_get_reportA sent item
Brevoget_email_campaignCampaign id (integer)
MailerLiteget_campaignCampaign id
Salesforce Marketing Cloudget_asset (include_content: true)Content Builder asset id (find it with list_assets)
Brazebraze_get_campaign_detailsCampaign API identifier

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.

Get a Newsletter drafts carry subject and body only. tool_get_draft returns no sender, lists, reply-to or preheader: sender and lists are chosen when the draft is sent (tool_send_draft), and reply-to and preheader are set in the Get a Newsletter editor. Ask the user for the sender, the target lists, the reply-to and the preheader. Validate the sender against tool_list_senders and size the lists with tool_get_list. Whatever the user doesn't supply, mark as [UNVERIFIED] in 2.1 and 2.8 rather than PASS or FAIL, because it can't be checked from the draft.

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.com from addresses (deliverability killer for bulk)
  • Sender passes DMARC — [FAIL] 🔴 if the sender record has dmarc_passed: false (Get a Newsletter tool_get_sender / tool_list_senders; the matching sender/domain-authentication fields on other ESPs) or the ESP rewrites the From address to its own domain (e.g. you=yourdomain.com@<esp-domain>): recipients see the ESP's domain and alignment fails. Fix: authenticate the sending domain (SPF/DKIM aligned with DMARC) in the ESP before sending. Unknown in paste mode → [UNVERIFIED].
  • 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. Get a Newsletter: its merge-tag syntax isn't confirmed by the MCP source, so mark its tokens "verify in the editor" instead of PASS/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; no href="#" 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
  • Get a Newsletter: UTMs and click tracking can be added at send time with tracking: true on tool_send_draft (a write the user runs or approves). If the links carry no UTMs, recommend it rather than failing the check.
  • 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> have alt text — 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:none or 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, record it: on Cloud call create_ticket first (fields mapped per section 2 of the routing guide); on Solo call create_finding:

json
{
  "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: [Firstname] with platform-specific fallback syntax
    • Get a Newsletter: the MCP source doesn't confirm a merge-tag syntax. Report any tokens you find as "verify in the editor" (send a test from Get a Newsletter) instead of PASS or FAIL.
    • Brevo: {{ contact.FIRSTNAME | default: "there" }}
    • MailerLite: {$name|default('there')}
    • Salesforce Marketing Cloud: AMPscript, e.g. %%[ IF Empty(@fn) THEN SET @fn = "there" ENDIF ]%% then %%=v(@fn)=%%
    • Braze: Liquid, {{${first_name} | default: 'there'}}
  • Spam-trigger lists are heuristics; modern spam filters use ML + reputation. Treat warnings as "raise eyebrow, check reputation" not "instant spam".

Recording findings

Use create_ticket when the workspace has it (Cloud) and create_finding otherwise (Solo). The fields differ; see section 2 of ../marketing-engineer/references/mcp-routing.md.

Frequently asked questions

What does the Pre Send Qa AI skill do?

Pre-flight QA for an email campaign — merge tags, broken links, spam triggers, compliance, mobile rendering — before you hit send

Why use Pre Send Qa on TypingMind?

Because you install it once and use it with any model. Pre Send Qa is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Pre Send Qa in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/cognyai/claude-code-marketing-skills/tree/main/skills/pre-send-qa. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Pre Send Qa?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Pre Send Qa?

As many as you like. As long as a model supports skills, you can use Pre Send Qa with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Pre Send Qa AI skill free?

It is published on GitHub by cognyai. Check the repository for licensing terms. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇