subject-line-lab — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited subject-line-lab (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.
Stop A/B testing blind. This skill reads your actual last 100+ sends, finds the subject-line patterns that correlate with open rate on your list, and generates 20 tuned candidates for your next campaign.
Requires: Cogny MCP + a connected ESP (Klaviyo, Mailchimp, Rule, Get a Newsletter, Brevo, MailerLite, Salesforce Marketing Cloud, Symplify or Braze). Sign up
/subject-line-lab — mine patterns and generate generic candidates /subject-line-lab "Black Friday doors open" — mine patterns and generate candidates for a specific campaign topic
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 and stop:
Subject Line Lab needs your marketing data through Cogny's MCP gateway.
1. Sign up at https://cogny.com and connect your ESP (Klaviyo, Mailchimp, Rule, Get a Newsletter, Brevo, MailerLite, Salesforce Marketing Cloud, Symplify 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.
Nothing to run without Cogny.Detect which ESP is connected. Check both namespaces (Cloud-aggregated via mcp__cogny__<svc>__* and Solo/Lite per-ESP via mcp__<svc>__*):
mcp__cogny__mcp__Klaviyo__* or mcp__klaviyo__*mcp__cogny__mcp__Mailchimp__* or mcp__mailchimp__*mcp__cogny__mcp__Rule__* or mcp__rule__*mcp__cogny__mcp__Get_a_Newsletter__* or mcp__get_a_newsletter__*mcp__cogny__mcp__<Prefix>__*)On Cloud, mcp__cogny__list_integrations lists what is connected; treat any integration whose health_status isn't healthy as broken and name it.
Health check: health_status misses revoked tokens, so make one cheap read per ESP before relying on it (Klaviyo get_account, Mailchimp tool_get_account_info, Rule tool_list_tags, Get a Newsletter tool_list_senders, Brevo/MailerLite list_*campaigns with a limit of 1, SFMC list_email_sends for one day, Symplify get_campaign_overview for one day, Braze braze_list_campaigns). A 401, Forbidden or scope error means 🔴 "reconnect <integration> in Cogny": name it in the output, don't retry, and continue with the other ESPs.
Missing tool: if a tool named here isn't exposed by the workspace (older connections cache their tool list), call mcp__cogny__refresh_mcp_discovery once, then 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: skip it with a one-line note ("Mailchimp: connected, 0 broadcast sends in the last <N> days — skipped") and run the rest.
If Cogny is connected but no ESP is, say: "Connect your ESP (Klaviyo, Mailchimp, Rule, Get a Newsletter, Brevo, MailerLite, Salesforce Marketing Cloud, Symplify or Braze) in Cogny", and stop.
If multiple are connected, prompt the user which one to analyze (or analyze all, labeled). Never pool sends from two ESPs into one baseline — open-rate definitions differ (see below). Run steps 2–5 per ESP and show the results side by side.
| ESP (Prefix) | Send history | Per-send metrics | Subject + preheader | Definition notes |
|---|---|---|---|---|
Klaviyo (Klaviyo) | list_campaigns | get_campaign_values_report (statistics: ["open_rate","click_rate","recipients"], campaign_id) | get_campaign_message | Open rate includes Apple MPP machine opens |
Mailchimp (Mailchimp) | tool_list_reports | tool_get_report | in the report / tool_get_campaign | Opens include MPP |
Rule (Rule) | tool_list_campaigns | tool_get_campaign_statistics | tool_get_campaign | |
Get a Newsletter (Get_a_Newsletter) | tool_list_sent | tool_get_report (report_id from the tool_list_sent row) | tool_list_sent (subject is on each row; no preheader) | One report per sent mail, no campaign object. delivered equals sent_to even when mails bounced, so open rate = unique opens / (sent_to − bounced) |
Brevo (Brevo) | list_email_campaigns (status: "sent") | get_email_campaign (the statistics block) | get_email_campaign (subject, previewText) | Stats live on the campaign, not a report tool. Use MPP-excluded opens if the payload separates them (appleMppOpens) |
MailerLite (MailerLite) | list_campaigns (filter_status: "sent") | get_campaign (stats) | get_campaign (emails[].subject) | Use the rates in stats as returned; don't recompute from other ESPs' formulas |
Salesforce Marketing Cloud (Salesforce_Marketing_Cloud) | list_email_sends (start_date, end_date, max_results) | same call: unique opens/clicks and rates per send | same call (subject) | Covers user-initiated and scheduled sends only. Journey emails are triggered sends; skip them like flows |
Symplify (Symplify) | get_campaign_overview (channel: "EMAIL", start, end) | same call, or get_campaign_report (delivery_id) | delivery name in the overview | If no subject field comes back, analyse the delivery name only for structure and say so |
Braze (Braze) | braze_list_campaigns | braze_get_campaign_analytics (campaign_id, length ≤ 100 days, ending_at) — daily rows, sum within the campaign | braze_get_campaign_details (email message subject) | Max 100 days per call; page back with ending_at. Multichannel campaigns: use only the email channel's numbers |
Drip (Drip) | — | — | — | Not supported. Drip's MCP exposes Email Series campaigns and workflows, not one-off broadcasts or per-email open rates |
Each ESP exposes subject-line history differently. Use the right tool per connected service:
| ESP | Send history tool | Notes |
|---|---|---|
| Klaviyo | list_campaigns (channel=email) then get_campaign + get_campaign_message | Tools are bare-named. get_campaign_values_report gives open/click rate per campaign in one call; list_events for deeper metrics. |
| Mailchimp | tool_list_reports then tool_get_report | Reports are the source of truth for opens/clicks per send. |
| Rule | tool_list_campaigns then tool_get_campaign_statistics | Statistics tool returns opens/clicks/bounces. |
| Get a Newsletter | tool_list_sent then tool_get_report | "Campaigns" don't exist as an object. tool_list_sent rows already carry subject, send time and report_id, so tool_get_sent isn't needed; call tool_get_report per send for metrics and use sent_to − bounced as the denominator (its delivered doesn't subtract bounces). |
| Brevo | list_email_campaigns (status: "sent") then get_email_campaign | Subject, preview text and statistics come from the same call. |
| MailerLite | list_campaigns (filter_status: "sent") then get_campaign | stats holds opens/clicks; get_campaign_activity is per-subscriber and not needed here. |
| Salesforce Marketing Cloud | list_email_sends | One call returns subject + metrics per send for the window. |
| Symplify | get_campaign_overview then get_campaign_report for detail | Pre-aggregated KPIs per delivery. |
| Braze | braze_list_campaigns → braze_get_campaign_details → braze_get_campaign_analytics | Only campaigns with an email message; the analytics are daily, so sum them per campaign. |
For the connected ESP, pull the last 100 campaign sends (or as many as available). For each, capture:
Skip transactional sends and automated flow emails — they distort the signal. Focus on broadcast campaigns. Exclude test sends too: recipient count under 10, a subject/name containing "test", "[TEST]", "testutskick" or "proof", or a send to a list/audience whose name contains "test", "intern", "internal" or "seed" (case-insensitive; internal seed lists can be dozens of people, so recipient count alone misses them). Report how many were excluded. (Brevo: type: "classic" only, never trigger; SFMC: never triggered sends; Braze: skip API-triggered and Canvas sends.)
For each subject line, tag these features (use keyword/regex heuristics — no ML needed):
Structural:
? / ! / . / no punctuationContent:
% or $ or currency (promo signal){{ first_name }}, [Firstname])Temporal:
For each feature, compute:
Rank features by absolute lift.
Output the top 5 positive patterns and top 3 negative patterns with specific numbers:
Patterns that worked for your list (last 100 sends):
🟢 WINNERS
1. Subject lines ending in "?" → 34% open rate vs 21% baseline (+62%, n=14 strong)
2. Subject lines with numbers → 29% open rate vs 21% (+38%, n=22 strong)
3. Length 30-50 chars → 27% open rate vs 19% (+42%, n=31 strong)
4. First-person voice ("I", "we") → 26% open rate vs 22% (+18%, n=18 strong)
5. Sent Tue 09:00-11:00 → 28% open rate vs 22% (+27%, n=12 weak)
🔴 DRAGS
1. ALL CAPS words → 14% open rate vs 23% (-39%, n=8 weak)
2. Emoji at start → 18% open rate vs 24% (-25%, n=11 strong)
3. Urgency words ("last chance", "today only") → 16% open rate vs 22% (-27%, n=9 weak)
Your best historical subject: "<actual subject>" at <open rate>% (<date>)
Your worst: "<actual subject>" at <open rate>% (<date>)If a campaign topic was provided, generate 20 subject line candidates that follow the winning patterns and avoid the drags. If no topic was provided, generate 20 generic promo/newsletter candidates using your patterns.
Format:
Candidates for: "<topic>"
(tuned to: ends-in-?, has-number, 30-50 chars, first-person voice)
Preheader recommendation: 85-100 chars, don't duplicate subject, extend the hook.
1. "What <specific number> of our customers asked last week?" (52 chars)
Preheader: "We pulled the top 5 questions and answered them in one place — you'll recognize #2."
2. ...
20. ...
──
A/B test plan:
- Primary variant: candidate #<N> (leans hardest into top pattern)
- Challenger: candidate #<M> (different angle to avoid pattern overfitting)
- Hold-out: your house-style baseline subject
Segment: X% / Y% / Z%Save the winning-pattern summary to Cogny's context tree for future sessions:
{
"path": "insights/email/subject-line-patterns",
"body": "<the top 5 winners + top 3 drags, with numbers and date range>"
}If the analysis surfaces a clear actionable insight (e.g., "50% of recent sends use emoji-at-start which is actively hurting opens"), record it: on Cloud call create_ticket first (fields mapped per section 2 of the routing guide); on Solo call create_finding:
{
"title": "Emoji-at-start subjects are tanking opens (-25% vs baseline)",
"body": "11 of last 100 sends started with emoji. Avg open 18% vs 24% non-emoji baseline. Recommend: move emoji to mid-subject or drop entirely on next 10 campaigns and re-measure.",
"action_type": "subject_line_optimization",
"expected_outcome": "Lift average open rate by 2-4 pp",
"estimated_impact_usd": 0,
"priority": "medium"
}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.