# AI Onboarding

> Everything you need to onboard your AI agent to MailBlastr — the MCP server, docs for agents, the Chat SDK, and production best practices.

If you are building with AI, MailBlastr offers several resources to improve your experience — an MCP server, agent-readable docs, an email Chat SDK, and battle-tested best practices for production sends. This page is the jumping-off point.

## Prerequisite: create an API key

We require a human to create a MailBlastr account. Once you have an account, [create an API key](https://www.mailblastr.com/docs/api-keys/overview) — it looks like `mb_xxxxxxxxx`. With a key, your agent can send email and manage most of your account over the API.

> **Note:** To send from MailBlastr you also need a [verified domain](https://www.mailblastr.com/docs/domains/managing). An agent can [create a domain](https://www.mailblastr.com/docs/api/domains-create) via the API, but the response returns DNS records you must add at your DNS provider before [verifying](https://www.mailblastr.com/docs/api/domains-verify). Verifying in the dashboard is usually easier.

## MailBlastr MCP server

MCP is an open protocol that standardizes how applications provide context to LLMs. Among other benefits, it gives LLMs tools to act on your behalf. The [MailBlastr MCP server](https://www.mailblastr.com/docs/ai/mcp-server) is published on NPM as `mailblastr-mcp` and covers the full API surface. Add it to any supported MCP client:

```json
{
  "mcpServers": {
    "mailblastr": {
      "command": "npx",
      "args": ["-y", "mailblastr-mcp"],
      "env": {
        "MAILBLASTR_API_KEY": "mb_xxxxxxxxx"
      }
    }
  }
}
```

See [MCP Server](https://www.mailblastr.com/docs/ai/mcp-server) for install instructions for Cursor, Codex, Claude Desktop, Windsurf, and more.

## MailBlastr docs for agents

You can give your agent current, context-aware docs in two ways:

1. **Markdown docs** — every doc page has a Markdown version. Append `.md` to any page URL, e.g. `https://mailblastr.com/docs/ai/onboarding.md`.
2. **MCP docs server** — for a structured approach using MCP tools, point your client at the docs MCP endpoint so your agent can search the docs as tools rather than fetching a single large file.

## Email skills for agents

Skills give AI agents specialized knowledge for specific tasks. These are reference *patterns* you capture as a `SKILL.md` (or equivalent context file) in your repo — they build on the [`mailblastr` SDK](https://www.mailblastr.com/docs/resources/sdks), the [`mailblastr-mcp` MCP server](https://www.mailblastr.com/docs/ai/mcp-server), webhooks, and native HTTP, so there is nothing extra to install:

| Skill | What it does |
| --- | --- |
| [MailBlastr](https://www.mailblastr.com/docs/ai/skill-mailblastr) | Send and receive emails, handle errors, prevent duplicate sends, and get code examples. |
| [React Email](https://www.mailblastr.com/docs/ai/skill-react-email) | Build emails in React, Tailwind, and TypeScript; audit existing React emails for style and cross-client rendering. |
| [Email Best Practices](https://www.mailblastr.com/docs/ai/skill-email-best-practices) | Audit SPF/DKIM/DMARC setup, compliance (CAN-SPAM, GDPR), and webhook handling. |
| [Agent Email Inbox](https://www.mailblastr.com/docs/ai/skill-agent-inbox) | Give an agent a secure inbox to receive and act on inbound email. |

## Conversational email (Chat SDK pattern)

You can turn email into a two-way communication channel for an agent: send replies with the [`mailblastr` SDK](https://www.mailblastr.com/docs/resources/sdks) or [POST /emails](https://www.mailblastr.com/docs/api/emails-send), receive inbound mail through [webhooks](https://www.mailblastr.com/docs/webhooks/overview) (`email.received`), and thread conversations with standard `In-Reply-To`/`References` headers. The [Chat SDK guide](https://www.mailblastr.com/docs/ai/chat-sdk) shows how to wire this up as a Vercel Chat SDK adapter, including card emails, attachments, and proactive outreach.

## Quick start: sending email from an agent

MailBlastr provides two endpoints for sending. Pick the right one for the job:

| Approach | Endpoint | Use case |
| --- | --- | --- |
| **Single** | `POST /emails` | Individual transactional emails, emails with attachments, scheduled sends. |
| **Batch** | `POST /emails/batch` | Multiple distinct emails in one request (max 100), bulk notifications. |

**Choose batch when** you are sending 2+ distinct emails at once and want to reduce API calls. **Choose single when** you are sending one email, the email needs attachments, the email needs to be scheduled, or different recipients need different timing.

### Best practices (critical for production)

Always implement these for production email sending.

**Idempotency keys** — prevent duplicate emails when retrying failed requests. Pass a unique `Idempotency-Key` header per logical send; a retry with the same key replays the original response instead of sending again. See [Idempotency keys](https://www.mailblastr.com/docs/emails/idempotency).

| Key facts |  |
| --- | --- |
| **Format (single)** | `<event-type>/<entity-id>` (e.g. `welcome-email/user-123`) |
| **Format (batch)** | `batch-<event-type>/<batch-id>` (e.g. `batch-orders/batch-456`) |
| **Expiration** | 24 hours |
| **Max length** | 256 characters |
| **Duplicate payload** | Returns the original response without resending |
| **Different payload** | Returns a `409` error |

**Error handling** — map status codes to actions:

| Code | Action |
| --- | --- |
| `400` / `422` | Fix request parameters — do not retry. |
| `401` / `403` | Check your API key / verify your domain — do not retry. |
| `409` | Idempotency conflict — an identical key is still in flight. Wait and retry, or use a fresh key. |
| `429` | Rate limited — retry with exponential backoff. |
| `500` | Server error — retry with exponential backoff. |

**Retry strategy** — use exponential backoff (1s, 2s, 4s…), cap at 3–5 retries, only retry `429` and `500`, and always send the same idempotency key on a retry.

### Single send parameters

**Required**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `from` | string | Yes | Sender address. Format: `"Name <email@domain.com>"` (must be on a verified domain). |
| `to` | string[] | Yes | Recipient addresses (max 50). |
| `subject` | string | Yes | Email subject line. |
| `html / text` | string | Yes | Email body content (provide at least one). |

**Optional**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `cc` | string[] | No | CC recipients. |
| `bcc` | string[] | No | BCC recipients. |
| `reply_to` | string[] | No | Reply-to addresses. |
| `scheduled_at` | string | No | Schedule send time (ISO 8601 or natural language). |
| `attachments` | array | No | File attachments (max 40MB total). |
| `tags` | array | No | Key/value pairs for tracking. |
| `headers` | object | No | Custom headers. |

### Minimal single send

```bash
curl -X POST 'https://api.mailblastr.com/emails' \
  -H 'Authorization: Bearer mb_xxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: welcome-email/user-123' \
  -d '{
    "from": "Acme <onboarding@yourdomain.com>",
    "to": ["delivered@example.com"],
    "subject": "Hello World",
    "html": "<p>Email body here</p>"
  }'
```

### Minimal batch send

Because a batch is validated atomically — one invalid item rejects the whole request — pre-validate every email (required fields, address format, and a size of 1–100) before sending.

- **No attachments** — use single sends for emails with attachments.
- **No scheduling** — use single sends for scheduled emails.
- **Atomic** — if one email fails validation, the entire batch fails.
- **Max 100 emails** per request.
- **Max 50 recipients** per individual email in the batch.

**Node.js**

```js
import { MailBlastr } from 'mailblastr';

const mb = new MailBlastr('mb_xxxxxxxxx');

const { data, error } = await mb.batch.send([
  { "from": "Acme <hello@yourdomain.com>", "to": ["a@example.com"], "subject": "Order Shipped",   "html": "<p>Your order has shipped!</p>" },
  { "from": "Acme <hello@yourdomain.com>", "to": ["b@example.com"], "subject": "Order Confirmed", "html": "<p>Your order is confirmed!</p>" }
]);
console.log({ data, error });
```

**Ruby**

```ruby
require "mailblastr"

Mailblastr.api_key = "mb_xxxxxxxxx"

Mailblastr::Batch.send([
  {
    "from": "Acme <hello@yourdomain.com>",
    "to": [
      "a@example.com"
    ],
    "subject": "Order Shipped",
    "html": "<p>Your order has shipped!</p>"
  },
  {
    "from": "Acme <hello@yourdomain.com>",
    "to": [
      "b@example.com"
    ],
    "subject": "Order Confirmed",
    "html": "<p>Your order is confirmed!</p>"
  }
])
```

**PHP**

```php
$mailblastr = Mailblastr::client('mb_xxxxxxxxx');

$mailblastr->batch->send([
  [
    'from' => "Acme <hello@yourdomain.com>",
    'to' => [
      "a@example.com"
    ],
    'subject' => "Order Shipped",
    'html' => "<p>Your order has shipped!</p>"
  ],
  [
    'from' => "Acme <hello@yourdomain.com>",
    'to' => [
      "b@example.com"
    ],
    'subject' => "Order Confirmed",
    'html' => "<p>Your order is confirmed!</p>"
  ]
]);
```

**Python**

```python
import mailblastr

mailblastr.api_key = "mb_xxxxxxxxx"

mailblastr.Batch.send([
  {
    "from": "Acme <hello@yourdomain.com>",
    "to": [
      "a@example.com"
    ],
    "subject": "Order Shipped",
    "html": "<p>Your order has shipped!</p>"
  },
  {
    "from": "Acme <hello@yourdomain.com>",
    "to": [
      "b@example.com"
    ],
    "subject": "Order Confirmed",
    "html": "<p>Your order is confirmed!</p>"
  }
])
```

**Go**

```go
package main

import (
    "fmt"
    "io"
    "net/http"
    "strings"
)

func main() {
    req, _ := http.NewRequest("POST", "https://api.mailblastr.com/emails/batch", strings.NewReader(`[
  { "from": "Acme <hello@yourdomain.com>", "to": ["a@example.com"], "subject": "Order Shipped",   "html": "<p>Your order has shipped!</p>" },
  { "from": "Acme <hello@yourdomain.com>", "to": ["b@example.com"], "subject": "Order Confirmed", "html": "<p>Your order is confirmed!</p>" }
]`))
    req.Header.Set("Authorization", "Bearer mb_xxxxxxxxx")
    req.Header.Set("Content-Type", "application/json")
    res, _ := http.DefaultClient.Do(req)
    defer res.Body.Close()
    out, _ := io.ReadAll(res.Body)
    fmt.Println(string(out))
}
```

**Rust**

```rust
use reqwest::Client;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let res = Client::new()
        .post("https://api.mailblastr.com/emails/batch")
        .header("Authorization", "Bearer mb_xxxxxxxxx")
        .header("Content-Type", "application/json")
        .body(r#"[
  { "from": "Acme <hello@yourdomain.com>", "to": ["a@example.com"], "subject": "Order Shipped",   "html": "<p>Your order has shipped!</p>" },
  { "from": "Acme <hello@yourdomain.com>", "to": ["b@example.com"], "subject": "Order Confirmed", "html": "<p>Your order is confirmed!</p>" }
]"#)
        .send()
        .await?;
    println!("{}", res.text().await?);
    Ok(())
}
```

**Java**

```java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.mailblastr.com/emails/batch"))
    .header("Authorization", "Bearer mb_xxxxxxxxx")
    .header("Content-Type", "application/json")
    .method("POST", HttpRequest.BodyPublishers.ofString("""
[
  { "from": "Acme <hello@yourdomain.com>", "to": ["a@example.com"], "subject": "Order Shipped",   "html": "<p>Your order has shipped!</p>" },
  { "from": "Acme <hello@yourdomain.com>", "to": ["b@example.com"], "subject": "Order Confirmed", "html": "<p>Your order is confirmed!</p>" }
]
"""))
    .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```

**.NET**

```csharp
using System.Net.Http;
using System.Text;

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.mailblastr.com/emails/batch");
request.Headers.Add("Authorization", "Bearer mb_xxxxxxxxx");
request.Content = new StringContent(@"[
  { ""from"": ""Acme <hello@yourdomain.com>"", ""to"": [""a@example.com""], ""subject"": ""Order Shipped"",   ""html"": ""<p>Your order has shipped!</p>"" },
  { ""from"": ""Acme <hello@yourdomain.com>"", ""to"": [""b@example.com""], ""subject"": ""Order Confirmed"", ""html"": ""<p>Your order is confirmed!</p>"" }
]", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());
```

**cURL**

```bash
curl -X POST 'https://api.mailblastr.com/emails/batch' \
  -H 'Authorization: Bearer mb_xxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '[
  { "from": "Acme <hello@yourdomain.com>", "to": ["a@example.com"], "subject": "Order Shipped",   "html": "<p>Your order has shipped!</p>" },
  { "from": "Acme <hello@yourdomain.com>", "to": ["b@example.com"], "subject": "Order Confirmed", "html": "<p>Your order is confirmed!</p>" }
]'
```

**CLI**

```bash
mailblastr emails batch \
  --data '[{"from":"Acme <hello@yourdomain.com>","to":["a@example.com"],"subject":"Order Shipped","html":"<p>Your order has shipped!</p>"},{"from":"Acme <hello@yourdomain.com>","to":["b@example.com"],"subject":"Order Confirmed","html":"<p>Your order is confirmed!</p>"}]'
```

> **Note:** See [Send an email](https://www.mailblastr.com/docs/api/emails-send) and [Send a batch](https://www.mailblastr.com/docs/api/emails-batch) for the full parameter reference.
