Send the first email
You cannot send from an unverified domain: it is a server-side gate, not a warning. Domain first, then the send.
1. Look at the existing domains
list_domains
Returns the workspace domains with their status. For the ones still pending it
includes the DNS records to set (SPF, DKIM, MAIL FROM). If the domain you need
is already there and verified, skip to step 4.
2. Add the domain
There is no MCP tool that creates domains: it happens in the dashboard under Communication › Domains, or over REST.
POST https://sending.dev/api/v1/domains
Authorization: Bearer sk_...
Content-Type: application/json
{ "name": "yourdomain.dev" }The response carries the DNS records to publish.
3. Have the records published, then verify
The user publishes the records at their DNS provider. When they say they have:
verify_domain { domainId }
DNS propagation is not instant: if it still comes back pending, wait a few minutes
before trying again rather than calling it in a loop.
4. Send the email
send_email {
from: "Acme <[email protected]>",
to: "[email protected]",
subject: "Welcome",
html: "<h1>Ready</h1>",
idempotencyKey: "welcome-sara-001"
}
idempotencyKey is required, at least 8 characters, and it goes in the body:
there is no Idempotency-Key header. It is what makes a retry safe: the same key
never sends twice. Use a key that describes the send, not a random value regenerated
on every attempt, or it protects you from nothing.
Every address field takes both [email protected] and Acme <[email protected]>.
to, cc and bcc also take several recipients at once, as an array or comma
separated inside one string. Each recipient counts as one unit of the monthly quota.
The name the recipient sees
With from: "[email protected]" the mail client shows only "hello". Two ways to
make a name appear:
- per send:
from: "Acme <[email protected]>"; - per domain, once:
set_domain_sender_name { domainId, defaultFromName: "Acme" }. From then on every email of that domain (transactional, campaigns, automations) goes out asAcme <[email protected]>even whenfromis the bare address.
An explicit name in the send always wins over the domain default. list_domains
shows the current value in defaultFromName; defaultFromName: null removes it.
Attachments
Upload first, reference after:
upload_attachment { filename, content } → { id }
send_email { ..., attachments: [{ id }] }
Limits: 20 attachments, 10 MB each, 25 MB in total. Executable extensions are
blocked, for the sake of the domain reputation. An uploaded attachment stays
reusable in later sends with the same { id }.
When a send does not go out
402: the month's quota is exceeded. Callget_usageand tell the user.- Domain not verified: back to step 3.
- Recipient on the suppression list (earlier bounce or complaint): the send is blocked on purpose. Do not work around it.
Other channels
send_message sends on the same shape over WhatsApp or Telegram, if the channel is
configured and the contact has consented for that channel.

