8xSend exposes its project management as an MCP server, so Claude Code — running in the play's repo, with the play's code in front of it — can install flows, write the email copy in the product's own voice, set the design, and check the integration is really firing.
The reason to want this isn't clicks saved. It's that the emails get better. Copy written by someone looking at the product's actual features, in its actual vocabulary, beats copy written from memory into a dashboard textarea — and until now those were two different sittings.
Connect it
1. Issue a manage key
Settings → API keys → Issue manage key. Shown once. Put it in your shell profile:
export EIGHTXSEND_MANAGE_KEY=8xs_mgmt_…A manage key is scoped to exactly one project. It can change that project's settings, design, flows and copy, and it can send real email — so treat it like the Resend key. It can never reach another play's project, and every change it makes is attributed to it in the audit log.
2. Add .mcp.json to the play's repo
{
"mcpServers": {
"8xsend": {
"type": "http",
"url": "https://<your-8xsend-host>/api/mcp",
"headers": { "Authorization": "Bearer ${EIGHTXSEND_MANAGE_KEY}" }
}
}
}The key is read from your environment, so this file is safe to commit — every engineer on the play uses the same config with their own key. Or run:
claude mcp add --transport http --scope project 8xsend https://<your-8xsend-host>/api/mcp \
--header "Authorization: Bearer $EIGHTXSEND_MANAGE_KEY"3. Work
Start Claude Code in the play's repo. /mcp should show 8xsend connected. Then just ask — or use one of the three built-in workflows:
| Command | Does |
|---|---|
/mcp__8xsend__setup_play | Reads the project, fills in what it can, installs the right flows, writes the copy, and reports what only a human can do |
/mcp__8xsend__write_copy <flow> | Rewrites one flow's emails from this codebase's real vocabulary |
/mcp__8xsend__go_live <flow> | Validates, previews, checks the events actually arrive — then asks you before flipping the switch |
What it can and can't do
| Can | Project settings, email design and masthead logo, install flows from the library, rewrite copy, add translations, preview a render, per-flow sending rules, issue the ingest key its backend needs, send a real test email, validate, set live/paused, catch up missed events, read stats, diagnose one contact |
| Won't | Take a Resend key or webhook secret — those would have to travel through a transcript, and pasting them in Settings → Sending costs thirty seconds. Issue another manage key: a credential that can mint its own replacement outlives its revocation, so rotation stays where you can see the whole list |
| Can't | Create the project, add DNS records, verify a domain, create a tracking subdomain. A send-scoped Resend key can't reach any of it, and nothing here touches a registrar. Restructure a flow — its delays, branches and steps are a shape, and they stay in the builder |
get_setup_checklist marks every step with who owns it — 8xsend, resend, dns or your_app — so an agent asks you for exactly the handful only you can do, instead of guessing or stalling on all of them.
Creating the project stays in the dashboard. It's the same visit where you do the Resend work anyway, and the API-keys tab hands you the .mcp.json snippet at the end of it.
The tools
Orientation — get_project, get_setup_checklist
Configuration — update_project_settings (From address, App URL, postal address, timezone, languages, consent policy, project-wide frequency cap, consent webhook, conversion events and window), set_product_facts (value proposition, audience, features, real prices, real social proof, guarantee — the sheet every email is written from), set_email_design (colours, typeface, radius, button style), set_email_logo (the masthead, from a file in the repo or a URL), issue_ingest_key
Flows — list_flow_library (with update_available per template), install_flow, reinstall_flow (archive the installed flow, install the current template as a draft, optionally carrying edited copy), get_flow (each email with its brief), update_flow_settings (quiet hours, smart sending, frequency cap, re-entry)
Copy — set_email_copy (writes to each email's brief; variant: "B" addresses a subject-test arm), set_email_html, add_translation, add_subject_variant (start a subject-line test), preview_email (renders, and lints for generic phrases and unmet briefs)
Shipping — validate_flow, set_flow_status, backfill_flow, send_test_email
Verifying — validate_event (run a payload through the API's validation without storing it), recent_events (what actually arrived, with parsed properties, plus recent rejections and why), get_event_brief, check_events (what's missing, and property_gaps: required properties absent or zero in the events that do arrive, each with a fix), get_stats (opens, clicks, and conversions credited to the last email clicked or opened before the purchase, within the project's window), diagnose_contact
Wiring — get_integration_code (the SDK wrapper, signup/deletion hooks, a Supabase auth trigger and a cron template for the play's stack, ready to paste), issue_ingest_key (also mints sandbox keys for staging and CI)
It also exposes the setup guide, the events contract and the flow catalog as resources, so the agent can read them without spending a tool call.
Wiring the app's own code
issue_ingest_key closes the last gap: an agent can now install the flows and write the track() calls that feed them, in one sitting, without you copying a key between two windows. The key is returned exactly once — 8xSend stores a hash and cannot show it again — so it goes straight into the app's server environment, never into a committed file. If one already exists, the tool refuses rather than silently starting a 24-hour clock on the key production is using.
Writing copy
set_email_copy is the tool this exists for. It writes into the project's design rather than replacing it, which is what keeps a play's twenty-eight emails looking like one product:
set_email_copy({
updates: [{
flow: "payment-failed",
step: "Notice 1 — immediate",
subject: "Your payment didn't go through",
heading: "We couldn't take your payment",
body: "Hi {{contact.first_name|there}}, the card on your Plus subscription was declined...",
cta_label: "Update payment method"
}]
})Emails are addressed by flow and step name, never by id — both survive deleting and re-installing a flow, so copy written today still lands after the template is improved tomorrow. Pass up to 100 updates in one call to rewrite a whole play at once, and dry_run: true to see the diff first.
Three rules worth knowing, each of which exists because of a real failure:
- Props merge. Omit
cta_urland the email keeps its own. That matters: the dunning emails deliberately point at whichever store bills the customer, and overwriting that with a project URL is what once made them unactionable. - Translations are never touched implicitly. English copy can't overwrite somebody's
pt-BRversion — name the locale to edit one. - A hand-written-HTML email is skipped, loudly. Writing copy props to it would do nothing at all, so it's reported instead of silently ignored.
preview_email renders through the real send path and reports the subject, the plain text, and the things that break silently — an empty body, a token with no fallback, a button whose link resolves to nothing. It deliberately returns text, not HTML: an agent can't see markup, and MCP results are capped.
When to use custom HTML instead
set_email_html gives one email its own markup. It permanently opts that email out of the project's design, so a later change in Settings → Design will not reach it. Worth it for a launch announcement with its own layout; not worth it for the other twenty-seven.
Going live
Nothing sends until you say so. set_flow_status live and backfill_flow are both marked destructive, so Claude Code asks before either runs, and backfill_flow additionally defaults to a dry run — it reports how many people would be emailed and requires confirm: true on a second call.
Validation runs before a flow can go live and blocks anything incomplete: a missing subject, no default language version, no postal address in the footer, a CTA whose link can't resolve.
When a flow isn't firing
8xSend only ever sees what the play's backend pushes to its ingest API, so almost every "it's not sending" turns out to be an event that never arrived.
get_event_brief <flow>— what this flow needs, when to fire each event, with what properties. Derived from the flow's own definition, so it can't drift.- Wire it in the play's code.
check_events— what has actually arrived in the last N hours, and which of the events the installed flows depend on are still missing.diagnose_contact— one person end to end: consent, suppression, the events received, the flows entered, every send, and the specific reason nothing went out.