← Blog·Aug 13, 2026productai
Teaching Claude how eusend actually works
We shipped an MCP server so assistants could use eusend. That gave them tools, not judgement. The new Claude Code plugin adds the missing half.

When we shipped MCP a few weeks ago, the pitch was time-to-first-email: point your assistant at our server and it can send mail, add a domain, read your delivery log. That part works.
But watching people actually build against eusend with an agent, the failures weren't tool failures. The tools did exactly what they were told. The problem was upstream — the assistant didn't know things about eusend that nobody could reasonably expect it to know, and confidently wrote code as if it did.
Today we're shipping a Claude Code plugin that fixes the other half.
/plugin marketplace add eusend-dev/eusend-skill
/plugin install eusend@eusend
Tools are not knowledge
Here's the distinction, concretely.
An MCP server tells the model "there is a function called send_email and these are its parameters." That is genuinely useful and it is also the entire extent of what it communicates. It says nothing about whether the code the model is writing around that call is correct.
So you get integrations that look right and are subtly broken:
The retry that double-sends. POST /emails is not idempotent on its own. Pass an Idempotency-Key and a retry returns the original email ID and queues nothing. The failure mode isn't forgetting the header — it's generating one with crypto.randomUUID() at call time, which makes every retry a brand-new key and therefore a brand-new send. It looks like correct idempotency code. It is the opposite of idempotency code.
The webhook handler that never verifies anything. Our signature covers the raw request body. If your framework has already parsed the JSON and you re-serialize it to check the HMAC, you get a different byte string and the signature never matches. The two ways that ends are a handler that rejects every legitimate delivery, or — worse, and more common — one where somebody "fixed" it by removing the check.
The unchecked await. The Node SDK returns { data, error } and does not throw on an API error. That's a deliberate choice and it's in the docs, but a model pattern-matching on every other JavaScript SDK it has ever seen writes const { data } = await client.emails.send(...) and moves on. Every failed send disappears silently.
The SPF record that breaks your company's email. This is the one that actually costs people something. Adding a sending domain to an email provider means adding an SPF record — that's true nearly everywhere, so it's a reasonable thing to assume. With eusend it's wrong twice over. We authenticate with DKIM and our mail carries its own bounce domain, so an SPF record on your root domain is never consulted and authorises nothing. And a domain may publish only one SPF record: adding a second makes SPF fail for every other service sending from that domain, including your own Google Workspace or Microsoft 365 mail. A helpful assistant appends include:_spf.eusend.dev to your existing record and breaks your company's email to fix a problem you didn't have.
None of these are the model being stupid. They're all the model being reasonable about a system it has no information on.
What a skill is
A skill is a markdown file with instructions that the assistant loads when it's relevant. Not a prompt you paste, not a config file — a document that sits in the toolkit and comes out when the task calls for it.
The useful property is that it costs almost nothing until it's used. Ours is about 260 tokens always-on: enough for Claude to know the skill exists and roughly what it covers. The actual content loads when you're doing something with eusend, and the detailed reference material — six files covering sending, DNS, webhooks, audiences, error codes, and testing — loads a level below that, only when a specific question needs it.
So the model isn't carrying our entire API surface around in its context on the off chance. It picks up what it needs, when it needs it.
What we put in it
We deliberately did not write a second copy of the docs. The docs are already good and they're one fetch away.
The skill is written around the things that bite:
- The
fromaddress must be on a verified domain,DOMAIN_NOT_VERIFIEDis terminal, and no retry loop will ever fix it - Which error codes are transient, which need a horizon of hours, and which mean stop and change something — plus the fact that
ALL_SUPPRESSEDis the system working correctly and not an incident - Verifying webhook signatures against the raw body, in constant time, with working handlers for Express, Next.js route handlers, Flask, and Go's
net/http - That tags allow
A–Z a–z 0–9 _ -and nothing else, so an email address or a timestamp as a tag value gets rejected - That batch sends fail per item and a
201on the batch tells you nothing about the messages inside it - That unsubscribing from an audience does not stop transactional mail, which is usually what you want and occasionally very much not
- That over SMTP the username is the literal string
eusendand your key goes in the password field — the single most common cause of a535 - Giving your application server a
sending_accesskey instead offull_access, becauseGET /emails/:idreturns the rendered body and the full recipient list, and that's the half of a leaked key worth stealing
Roughly: everything we'd tell you in a support thread, written down once.
It installs the MCP server too
The plugin bundles the MCP server, so one install gets you both halves — the knowledge and the tools. Set EUSEND_API_KEY in your environment and it authenticates; set EUSEND_FROM and your assistant stops having to guess a sender address.
The skill works with no key at all. Only the server needs one. And the same restraint we put on the tools still applies — no deleting domains, audiences, contacts or keys, no un-suppressing, and broadcasts stay two separate calls so a bulk send is never one accidental tool invocation.
Use an eu_test_ key while you're wiring things up. Sends are accepted, recorded, and fire the full webhook lifecycle without reaching anyone's inbox — and sending to bounced@… or complained@… simulates the failure paths, which is how you actually test a bounce handler.
The honest caveat
A skill that teaches an assistant the wrong thing is worse than no skill, because it replaces uncertainty with confidence. That's a real risk and it's why the whole thing lives in a public repository under MIT rather than somewhere we can quietly change it: you can read every line of what we're telling your assistant before you install it.
If you find something it gets wrong, that's a bug and the issue tracker is open. We'd rather hear it from you than have it quietly generate bad integrations.
It isn't Claude-only
Worth being specific about this, because "skill" sounds proprietary and isn't. Agent Skills started at Anthropic and was released as an open standard, and it has been picked up broadly — Cursor, GitHub Copilot, VS Code, Codex, Gemini CLI, OpenCode, Goose, Amp, Roo Code, Factory, Kiro, and a long tail past that.
We wrote ours to the spec rather than to Claude Code: the frontmatter uses only the six fields the standard defines, and the body has none of Claude Code's own template syntax. So the same directory works in any of those tools.
What differs is the install. /plugin is Claude Code's marketplace format; everywhere else you drop the folder somewhere the agent scans. Cursor, Codex and Copilot happen to share .agents/skills/, so one copy covers all three:
git clone https://github.com/eusend-dev/eusend-skill
mkdir -p .agents/skills
cp -r eusend-skill/plugins/eusend/skills/eusend .agents/skills/
The MCP server is portable for the same reason — it speaks plain MCP, so Cursor, Codex and Claude Desktop all connect to it. What Claude Code gets is the convenience of both arriving in one command, not exclusive access.
Get started
/plugin marketplace add eusend-dev/eusend-skill
/plugin install eusend@eusend
Then ask for something real — "add password reset emails to this app using eusend", or "why is this send returning DOMAIN_NOT_VERIFIED" — and see whether it does better than it would have.
Full details on the plugin page. Signups are open at eusend.dev/signup, and your email still never leaves the EU — an assistant writing the integration doesn't change where the mail goes.