sd-create-email
Draft an email campaign in the portal. The campaign is created in draft status, the approval link is minted, and the URL is handed back so the author can share it for review.
Important: "Approve" on the link records a "ready to ship" stamp. It does NOT send the email. Sending is a separate deliberate action (email_campaigns_send) — the author triggers it after approval.
Pre-flight
- Read
.sd/config.json— confirmclient,defaultSiteId,brand. Runsd-initfirst if missing. - Read brand messaging — emails lean heavily on
toneOfVoice,valueProposition,keyDifferentiators,boilerplate. - Read
SD_DESIGN_PRINCIPLES.md— section 9 has the email-specific tweaks (<table>layouts, inline styles, max-width 600px, 16px body, max 40px logo height). - Read
.sd/learnings.mdif present — apply## Active rules. Pay extra attention to email-specific rules since deliverability gotchas accumulate fast (e.g. "client X doesn't want emoji in subject lines," "campaigns to list Y always go from hello@, not the personal address"). - Identify the target list. Ask the user which list the campaign goes to. List candidates with
mcp__simplerdevelopment__email_lists(oremail_lists_createfor a brand-new list). RecordlistId— required foremail_campaigns_create. - From-address sanity. Resolve
fromNameandfromEmail. Prefer ones the tenant has already used in past campaigns (checkemail_campaigns_list). For a new tenant, ask explicitly — getting this wrong can land the campaign in spam.
Sourcing — ASK if unclear
Same options as sd-create-page:
prompt-only— write from prompt + brand voice (most common for nurture / short announcement emails).url— fetch one or more URLs (e.g. announcement blog post, case study) and structure the email around it.brief— read a local markdown/txt brief.mixed— combine.
Do not silently add private/internal sources. Client-facing email should be grounded only in the user's prompt, files they provide, URLs they provide, and the tenant's own SimplerDevelopment MCP data.
Authoring
Reuse before invent. Check
.sd/config.json:inventory.emailTemplatesfor templates that match the campaign type (welcome, announcement, newsletter, transactional). If a template matches, use it as the starting point — pull the template'shtmlContentorblockContent, customize, and pass through.Email block discipline. Email rendering is constrained vs web rendering. Stick to:
text,heading,image,button,divider,spacer— universal.columns(2-col max for mobile compatibility).email-header,email-footer— reuse if the brand has these templates.- Avoid complex visual-editor blocks (tabs, accordion, marquee, video, embedded HTML beyond what
renderBlocksToEmailHtmlsupports).
Subject + preview text.
- Subject: 30–50 chars. Lead with the value/news, not a brand prefix.
- Preview text: 80–110 chars. Should complement the subject, not repeat it. Many inboxes show this preview alongside the subject.
Body shape. Most well-performing campaign types follow a common spine:
- Announcement: header → hero (image + 1-line headline) → body (2-3 short paragraphs) → CTA button → footer.
- Newsletter: header → 2-4 sections (each with eyebrow + heading + 2-3 lines + read-more link) → footer.
- Welcome: header → personalized greeting → 2-3 onboarding steps → CTA → footer.
- Nurture: header → contextual hook → 1 short value section → soft CTA → footer.
Brand voice. As with pages —
toneOfVoice,brandPersonality,writingStyleset register;valuePropositionandkeyDifferentiatorsanchor the content. Skip the corporate-voice phrasing unless the brand explicitly calls for it.Logo in the header. From
.sd/config.json:brand.logos.logoUrl, place the wide logo as the first row of the email. Constraints:- Image element, NOT base64 (Gmail/Outlook block data: URIs in email).
max-height: 40px— bigger reads as amateur.- Center-align on dark headers, left-align on light headers.
alttext fromlogos.logoAltor<companyName> logo.
If
logoUrlis null, use the styled wordmark variant oflogoTextin the brand accent color.Footer. Always include unsubscribe (the renderer injects
{{UNSUBSCRIBE_URL}}). Include the company wordmark + a tenant domain, website URL, or physical address from the brand profile. Light-on-dark or dark-on-light — match the header's pattern.Run the email-specific contrast check. All body text and button labels must pass 4.5:1. Buttons commonly fail when the background uses the brand
accentColor— callbranding_check_contrastforbutton.style.colorvsbutton.style.backgroundColorand adjust if it fails.Link related artifacts. Common patterns:
- CTA to a booking page —
buttonblock whoseurlis the absolute booking URL:https://<site-domain>/book/<slug>. (Email links MUST be absolute.) Pair with a secondary "or reply to this email" inline link. - Link to a survey — same pattern,
https://<site-domain>/s/<survey-slug>. - Link to a CMS page — same pattern. Always absolute.
Don't try to embed the survey or booking widget directly in an email — email clients don't run React. Always link out.
- CTA to a booking page —
MCP call
Call mcp__simplerdevelopment__email_campaigns_create with:
{
"name": "<internal name, e.g. 'Q2-2026 Product Announcement'>",
"subject": "<email subject line>",
"previewText": "<preview text>",
"fromName": "<from name>",
"fromEmail": "<verified from-address>",
"replyTo": "<optional reply-to>",
"listId": <list id>,
"blocks": [...]
}
Pass blocks (preferred — server renders to HTML using renderBlocksToEmailHtml) OR htmlContent (pre-rendered). Not both.
status is forced to draft at create time — you cannot start a campaign in any other state.
MCP response handling — read errors first
SimplerDevelopment's MCP wraps every response — successes AND errors — in a JSON-RPC success envelope shaped like:
{"result":{"content":[{"type":"text","text":"{...JSON...}"}]}}
Before reporting success to the user, parse result.content[0].text as JSON. If the parsed object contains an error key (e.g. {"error":"Site not found"} or {"error":"Unauthorized"}), the call FAILED — even though the JSON-RPC envelope said result. STOP immediately. Surface the error verbatim to the user. Do NOT invent a successful response with a made-up post id, approval URL, slug, or site name. Hallucinated success is worse than a visible failure — the user will publish content that doesn't exist or copy approval URLs to stakeholders that 404.
Only treat the call as successful when the parsed text contains the expected entity shape (e.g. {"id":..., "approval":{...}} for posts_create).
Output
The MCP response includes an approval envelope. Return to the user:
- Campaign id + portal URL:
/portal/email/campaigns/<id> - Recipient count (read via
email_listsforsubscriberCounton the listId) - Approval URL — this is what the user shares for review
- A one-line summary of the campaign (subject + body shape + CTA)
- A reminder that approval does NOT auto-send — the user runs
email_campaigns_sendor schedules via the portal after the link is approved.
Iteration
- Edit copy → call
email_campaigns_updatewith the same id. Each update mints a fresh approval URL so the reviewer sees the content as-of-mint-time. Old URL stays in its current state; new URL supersedes. Return the new one to the user. - A/B subject test → set
abEnabled: true,abSubjectB: "<variant>"on update. The approval previewer renders the primary subject — note the A/B variant in the review or share it as a comment. - Major rework → call
email_campaigns_forkfor a clean variant with its own approval link. Fork resetsstatus='draft'and zeroes send counts — never touches the parent.
Failure modes
- No
.sd/config.json→ runsd-initfirst. fromEmailnot verified → Resend will reject the send when the user later callsemail_campaigns_send. The skill can't detect this; flag in the output ("verify the from-address in the portal before send").- Subscription not active →
email_campaigns_createwill return "This feature requires an active email subscription". Surface to user. statusis not draft when calling update → the update tool refuses. If the campaign already sent, the user needs to fork it.- Pending approval gate →
email_campaigns_createreturnspending: trueif the API key hasrequire_cms_approval. The campaign isn't created until the staged change is approved through the same approval URL.
Install
This skill ships as part of the SimplerDevelopment client skills bundle. Install the full skill bundle in one step from the portal:
https://simplerdevelopment.com/install
macOS, Windows, and Linux installers download the bundle to ~/.claude/skills/. Both Claude Desktop and Claude Code auto-discover skills from that path on next restart.
See CLIENT_QUICKSTART.md (installed alongside this file) for the full setup walkthrough, including the MCP-server config Claude Desktop needs and the one-time sd-init bootstrap.