Email design skill for Claude Code. Anti-slop emails — six committed archetypes, brand-aware briefs, real imagery, real footers. Works with any ESP via MCP (Nitrosend, Klaviyo, Mailchimp, ActiveCampaign).
SaferSkills independently audited email-design (Agent Skill) and scored it 96/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 1 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
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.
Distinctive, on-brand emails. Produces a structured brief, then hands it to the connected sending tool. Self-contained.
Triggered for:
Skip for:
Pull what is already known. Do not ask the user yet.
If Nitrosend MCP tools are available (nitro_*):
nitro://account resource for brand fields (company name, brand colour, logo, fonts, physical address, voice document if present).nitro_get_status for onboarding state and any obvious gaps.nitro_query({ entity: "templates", limit: 3 }) to see prior design language. Look at the actual sections, colours, typography, and tone.If the connected ESP is something else, look for the equivalent — most ESPs expose a brand or settings endpoint. If the ESP is unknown, fall back to project files: BRAND.md, email-brand.md, PRODUCT.md, or a top-level README.md for tone clues.
Build a brand profile in your head with these fields, marking each as known, inferable, or missing:
If three or more fields are missing, you can ask. Otherwise default to inferred values and note them in the brief.
Hard cap: three questions, in one message, as a numbered list. If more fields are missing, fill with sensible defaults and note them in the brief — do not interrogate.
Priority order — pick the top three that are missing and matter for this specific email:
If the user has already answered these earlier in the session, do not re-ask.
Anti-slop wins. Commit to an archetype before drafting. The most common failure mode is defaulting to "minimal-lux" because it feels safe — it is the right answer maybe 30% of the time. For the rest, pick deliberately.
| Archetype | When | Hallmarks | Reference brands |
|---|---|---|---|
| Editorial | Newsletters, brand stories, long-form | Serif heads, full-bleed photo, generous leading, single column, voice-rich | Patagonia, Tracksmith, Lucy Folk |
| Bold mono | Promo, launches, statements | One saturated colour drenches the surface, big punchy type, minimal copy | Absolut, Liquid Death, Fly By Jing |
| Minimal lux | Premium, transactional, restraint as positioning | Tinted neutrals, narrow width 472–520px, one accent, lots of breath, never discount-led | Stripe, Aesop, Apple, MoMA |
| Founder letter | Welcome, win-back, milestones, reply-driver | Plain-text feel, first-person, short paragraphs, signed off, no template chrome | Ugmonk, Superhuman, Tracksmith CEO |
| Punk / character | Brand voice carriers, retention | Heavy personality, mascot or character, irreverent copy, custom imagery | Frank Body, Duolingo, Liquor Loot, Chubbies |
| Lookbook | Product, fashion, food, paint, anything where the product is the design | Editorial photo > copy, gallery layout, full-bleed hero | Dior, Clare Paint, Starbucks seasonal |
Apply to every archetype. Implementation should match the aesthetic — bold needs committed; minimal needs precision.
#### Colour
#000 or #fff. Tint every neutral toward the brand hue.#### Typography
background-clip: text. Emphasis through weight or size, not effects.#### Layout
#### Whitespace (this is where most AI-drafted emails fail)
The default LLM behaviour is to over-pad — every section gets generous top and bottom padding, plus standalone spacer rows. The result is a moonscape. Counteract:
spacer sections in a row, ever, is wrong. Collapse.padding_top: 0, padding_bottom: 32. Let the next section's top padding (or its lack) own the rhythm.padding_bottom: 32–48px, never more. Footer has its own top padding.<br> between blocks, margin AND padding on the same side, an empty text section used as a spacer.This applies regardless of platform whitespace-trimming passes — belt and braces.
#### Imagery
#### Copy
--.Write the brief as JSON before composing sections. This is what feeds the sending tool. Treat it as the contract.
{
"name": "Campaign or template name",
"subject": "subject line ≤45 chars",
"preheader": "preheader ≤90 chars adding new info",
"archetype": "editorial | bold-mono | minimal-lux | founder-letter | punk-character | lookbook",
"color_strategy": "restrained | committed | drenched",
"primary_color": "#hex",
"secondary_color": "#hex or null",
"voice_descriptors": ["e.g. founder-personal", "wry", "concrete"],
"structure": [
{ "kind": "header", "logo": true, "padding": "compact" },
{ "kind": "hero", "image_slot": "<slot-id>" },
{ "kind": "headline", "content": "..." },
{ "kind": "body", "content": "..." },
{ "kind": "cta", "label": "...", "url": "..." },
{ "kind": "secondary_body", "content": "..." },
{ "kind": "footer" }
],
"image_slots": [
{
"slot_id": "hero",
"purpose": "what it must convey",
"candidate_url": "<url-from-brand-site or null>",
"user_prompt": "<text to show user if no candidate>"
}
],
"footer": {
"mission_line": "exact verified copy from brand fields or brand site, or null — never invent",
"socials": [{ "name": "instagram", "url": "..." }],
"physical_address": "one line",
"unsubscribe": "{{unsubscribe_url}}"
},
"anti_slop_checks": ["no category-reflex palette", "real imagery", "distinctive voice"]
}Each image_slot must end with a real candidate URL or an explicit prompt to the user. Sources, in priority order:
nitro_query({ entity: "media" }). Use what's there before fetching anything new.https://<brand-domain>/ with WebFetch and look for:<meta property="og:image"><section class="hero"> img, <header> img, [data-hero] img, banner-y filenames)/press, /about, or /blog page imagery for portraits and lifestyleNever fabricate or AI-generate a stock-style image. A clearly-marked placeholder is better than visual slop.
If you uploaded a new image and the brand uses Nitrosend: nitro_ingest_image({ url: "...", purpose: "hero" }).
Map the brief's structure array to the sending tool's section types. For Nitrosend nitro_compose_campaign:
| Brief kind | Nitrosend section | Notes |
|---|---|---|
| header | omit if logo lives in theme.logo_url; otherwise image | Logo height 32–48px |
| hero | image | Full-width, no side padding, set link_url only if it's clickable |
| headline | text with <h1> or <h2> markup | Don't add a separate spacer above; let the next section's top padding handle rhythm |
| body | text | Short paragraphs |
| cta | button | Single CTA, full-width on mobile |
| secondary_body | text | Optional |
| divider | divider | Once per email max |
| footer | footer | Auto-rendered if account footer is configured; otherwise build explicitly |
Override the platform's default padding when composing. Do this even if the platform has a whitespace-trimming pass.
{
"type": "image",
"props": {
"src": "...",
"alt": "...",
"padding_top": 0,
"padding_bottom": 24
}
}Default rule per section: padding_top: 0, padding_bottom: 32. Override only with reason.
A great footer, in this order:
company_description on the brand, also visible in the Brand settings), or the brand's homepage / about page (hero subhead, footer tagline, "we believe…" copy). If neither yields an exact, verified line, omit the mission line entirely — a missing mission is better than a fabricated one.Visual: smaller type (12–13px), tinted neutral text at 60–70% of body contrast, generous internal padding, no extra spacer above.
Always run a dry-run first and always show the preview URL before the user approves a real send. Ask if they want a test email.
nitro_compose_campaign({
name: brief.name,
subject: brief.subject,
preheader: brief.preheader,
sections: <translated from brief.structure with explicit padding>,
theme: {
brand_color: brief.primary_color,
bg_color: ...,
text_color: ...,
font_body: ...,
font_heading: ...,
logo_url: ...
},
audience: { audience_type: "lists", contact_list_ids: [...] },
dry_run: true
})Then surface the preview URL from the response. Only proceed to a real send after the user confirms.
After the dry-run preview, before any real send, ask:
"Want me to send you a test email first? I can send to [from_email from account] or another address — let me know."
Default the recipient to the account's from_email or the saved test recipients, but accept any address the user names. If they say yes, send via the platform's test endpoint and surface the result.
For Nitrosend:
nitro_send_test_message({
template_id: <id from compose response>,
email: "<recipient>"
})For other ESPs, use the equivalent (POST /v1/my/templates/{id}/send_test on the Nitrosend REST API; analogous endpoints elsewhere). After the test lands, ask if any final tweaks are needed before the real send.
Run this mentally before composing. If three or more fail, redraft.
<br><br>, no margin+padding combosWhen the user wants to iterate:
{
"name": "Welcome 1 — Founder Hello",
"subject": "you're in",
"preheader": "a quick note from the founder, plus what to expect this week",
"archetype": "founder-letter",
"color_strategy": "restrained",
"primary_color": "#1f2933",
"voice_descriptors": ["first-person", "concrete", "warm", "no marketing fluff"],
"structure": [
{ "kind": "header", "logo": true, "padding": "compact" },
{ "kind": "headline", "content": "Hey, I'm [name]." },
{ "kind": "body", "content": "Three short paragraphs. What we built, why we built it, what to do this week." },
{ "kind": "cta", "label": "Reply with one thing you're trying to do", "url": "mailto:..." },
{ "kind": "footer" }
],
"image_slots": [],
"footer": {
"mission_line": "Building [thing] so [who] can [outcome].",
"socials": [{ "name": "linkedin", "url": "..." }],
"physical_address": "...",
"unsubscribe": "{{unsubscribe_url}}"
},
"anti_slop_checks": ["plain-text feel keeps it personal", "no stock imagery", "single CTA is a reply, not a click"]
}{
"name": "Launch — Nitro Blue Drop",
"subject": "blue.",
"preheader": "the new colorway. limited run. tomorrow 9am.",
"archetype": "bold-mono",
"color_strategy": "drenched",
"primary_color": "#1d4ed8",
"voice_descriptors": ["punchy", "confident", "few words"],
"structure": [
{ "kind": "hero", "image_slot": "hero" },
{ "kind": "headline", "content": "Blue." },
{ "kind": "body", "content": "Limited to 200. 9am tomorrow." },
{ "kind": "cta", "label": "Set a reminder", "url": "..." },
{ "kind": "footer" }
],
"image_slots": [
{
"slot_id": "hero",
"purpose": "Single product shot on solid background. Same blue as primary_color. No text overlay.",
"candidate_url": "https://brand.com/og-image.jpg",
"user_prompt": "Got the product photo of the blue colorway? Drop the URL or upload via the media uploader."
}
],
"footer": {
"mission_line": "Made for runners. Made in [city].",
"socials": [
{ "name": "instagram", "url": "..." },
{ "name": "tiktok", "url": "..." }
],
"physical_address": "...",
"unsubscribe": "{{unsubscribe_url}}"
},
"anti_slop_checks": ["committed to one colour", "real product photo, not stock", "no double-padding"]
}{
"name": "Newsletter — Running gear for winter's worst",
"subject": "running gear for winter's worst",
"preheader": "what we wear when it's brutal out there",
"archetype": "editorial",
"color_strategy": "committed",
"primary_color": "#0a3d2e",
"voice_descriptors": ["earnest", "specific", "outdoorsy", "sells the weather, not the product"],
"structure": [
{ "kind": "header", "logo": true, "padding": "compact" },
{ "kind": "hero", "image_slot": "winter-trail" },
{ "kind": "headline", "content": "When the weather wins, you don't." },
{ "kind": "body", "content": "Short essay — 120 words on running in horizontal sleet. No product names yet." },
{ "kind": "cta", "label": "See the kit", "url": "..." },
{ "kind": "secondary_body", "content": "Three pieces, briefly. Photo above each, not in a grid card." },
{ "kind": "footer" }
],
"image_slots": [
{
"slot_id": "winter-trail",
"purpose": "Editorial landscape — runner small in frame, weather doing the talking. No product close-ups in the hero.",
"candidate_url": null,
"user_prompt": "Have a winter trail / weather shot? Or shall I leave a placeholder?"
}
],
"footer": {
"mission_line": "We're in business to save our home planet.",
"socials": [{ "name": "instagram", "url": "..." }],
"physical_address": "...",
"unsubscribe": "{{unsubscribe_url}}"
},
"anti_slop_checks": ["no category-reflex green-and-brown palette", "hero sells the moment, not the product", "voice carries the email, not the design chrome"]
}~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.