pre-send-qa — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited pre-send-qa (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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).
/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
| ESP (Prefix) | Load the draft | Audience size (2.8) | Recent sends (duplicate check) |
|---|---|---|---|
Klaviyo (Klaviyo) | get_campaign → get_campaign_message | get_segment / get_list | list_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_segment | tool_list_campaigns |
Rule (Rule) | tool_get_campaign | tool_list_segments | tool_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 names | tool_list_sent, tool_list_scheduled |
Brevo (Brevo) | get_email_campaign (subject, previewText, sender, replyTo, htmlContent, scheduledAt) | get_list for each id in recipients.listIds | list_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/segments | list_campaigns (filter_status: "sent") |
Salesforce Marketing Cloud (Salesforce_Marketing_Cloud) | list_assets (name_contains) → get_asset (include_content: true): subject, preheader, HTML, text | not exposed for unsent emails; ask for the sendable data extension count | list_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 it | braze_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.
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.
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 <integration> 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.
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 + tool_get_campaign_content (body; if the tool is missing, ask the user to paste the HTML and mark the body checks ⚠) | 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 |
| Brevo | get_email_campaign | Campaign id (integer) |
| MailerLite | get_campaign | Campaign id |
| Salesforce Marketing Cloud | get_asset (include_content: true) | Content Builder asset id (find it with list_assets) |
| Braze | braze_get_campaign_details | Campaign 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:
Walk through every category. For each item, mark [PASS], [WARN], or [FAIL] and include the specific offending content.
#### 2.1 Headers & routing
@gmail.com/@outlook.com from addresses (deliverability killer for bulk)[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].#### 2.2 Merge tags / personalization Scan body + subject + preheader for:
{{ ... }}, {% ... %}, *|...|*, [[...]], %%...%%, {...} — depending on ESP syntax{{ 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.{% if %} must have {% endif %}; same for loops#### 2.3 Links Extract all <a href="..."> from the HTML. For each:
href="/path" without domain; no href="#" unless intentional anchor*.staging.*, *.dev.*, stage., preprod.utm_source/utm_medium/utm_campaignutm_campaign; flag divergencetracking: 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.#### 2.4 Images
imgur, cloudinary demo, etc. instead of a brand CDN#### 2.5 Compliance
unsubscribe, preferences, opt outdisplay:none or font-size:1px#### 2.6 Spam triggers Scan subject + preheader + body for classic triggers and report offenders:
! in subject, !!!, ???$$$, ★, excessive emoji (>3)<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
<style> blocks. Many clients strip <head> styles.<meta name="viewport" content="width=device-width">font-family: "Custom Font"; without a web-safe fallback#### 2.8 Send context (MCP mode only)
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]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:
{
"title": "Campaign '<name>' has critical QA issues — blocked send",
"body": "<enumerated issues>",
"action_type": "pre_send_fix",
"priority": "high"
}Never modify the campaign automatically. Always present the diff and let the user approve each fix.
{{ first_name|default:"there" }}*|FNAME|* with *|IFNOT:FNAME|*there*|ELSE:*|FNAME|**|END:IF|*[Firstname] with platform-specific fallback syntax{{ contact.FIRSTNAME | default: "there" }}{$name|default('there')}%%[ IF Empty(@fn) THEN SET @fn = "there" ENDIF ]%% then %%=v(@fn)=%%{{${first_name} | default: 'there'}}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.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.