# AI Onboarding

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

If you are building with AI, SendPing 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 SendPing account, and to create the key itself: key creation, permission changes, and revocation happen in the dashboard, never over the API. Once you have an account, [create an API key](https://www.sendping.co/docs/create-an-api-key) — it looks like `mb_xxxxxxxxx`. With a key, your agent can send email and manage the rest of your account over the API, but it can never mint itself a second key or widen the one you gave it.

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

## SendPing 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 [SendPing MCP server](https://www.sendping.co/docs/ai/mcp-server) is published on NPM as `sendping-mcp` and covers the full API surface. Add it to any supported MCP client:

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

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

## SendPing 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://sendping.co/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 [`sendping` SDK](https://www.sendping.co/docs/resources/sdks), the [`sendping-mcp` MCP server](https://www.sendping.co/docs/ai/mcp-server), webhooks, and native HTTP, so there is nothing extra to install:

| Skill | What it does |
| --- | --- |
| [SendPing](https://www.sendping.co/docs/ai/skill-sendping) | Send and receive emails, handle errors, prevent duplicate sends, and get code examples. |
| [React Email](https://www.sendping.co/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.sendping.co/docs/ai/skill-email-best-practices) | Audit SPF/DKIM/DMARC setup, compliance (CAN-SPAM, GDPR), and webhook handling. |
| [Agent Email Inbox](https://www.sendping.co/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 [`sendping` SDK](https://www.sendping.co/docs/resources/sdks) or [POST /emails](https://www.sendping.co/docs/api/emails-send), receive inbound mail through [webhooks](https://www.sendping.co/docs/webhooks/overview) (`email.received`), and thread conversations with standard `In-Reply-To`/`References` headers. The [Chat SDK guide](https://www.sendping.co/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

SendPing 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.sendping.co/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** | 255 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). |
| `headers` | object | No | Custom headers. |

### Minimal single send

```bash
curl -X POST 'https://www.sendping.co/api/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@test.sendping.co"],
    "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 { SendPing } from 'sendping';

const mb = new SendPing('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 "sendping"

SendPing.api_key = "mb_xxxxxxxxx"

SendPing::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
<?php
require 'vendor/autoload.php';

use SendPing\SendPing;

$sendping = SendPing::client('mb_xxxxxxxxx');

$sendping->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 sendping

sendping.api_key = "mb_xxxxxxxxx"

sendping.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
import "github.com/shekhu10/sendping-sdks/sendping-go"

client := sendping.NewClient("mb_xxxxxxxxx")

batch, err := client.Batch.SendEmails([]*sendping.BatchEmailRequest{
    {
        From:    "Acme <hello@yourdomain.com>",
        To:      []string{"a@example.com"},
        Subject: "Order Shipped",
        Html:    "<p>Your order has shipped!</p>",
    },
    {
        From:    "Acme <hello@yourdomain.com>",
        To:      []string{"b@example.com"},
        Subject: "Order Confirmed",
        Html:    "<p>Your order is confirmed!</p>",
    },
})
```

**Rust**

```rust
use sendping::{BatchEmailOptions, SendPing};

let mb = SendPing::new("mb_xxxxxxxxx");

let items = vec![
    BatchEmailOptions::new(
        "Acme <hello@yourdomain.com>",
        ["a@example.com"],
        "Order Shipped",
    )
    .with_html("<p>Your order has shipped!</p>"),
    BatchEmailOptions::new(
        "Acme <hello@yourdomain.com>",
        ["b@example.com"],
        "Order Confirmed",
    )
    .with_html("<p>Your order is confirmed!</p>"),
];
let _batch = mb.batch.send_emails(items).await?;
```

**Java**

```java
import co.sendping.SendPing;
import co.sendping.SendPingResponse;
import co.sendping.requests.BatchEmailRequest;
import java.util.List;

SendPing sendping = new SendPing("mb_xxxxxxxxx");

List<BatchEmailRequest> requests = List.of(
        BatchEmailRequest.builder()
                .from("Acme <hello@yourdomain.com>")
                .to("a@example.com")
                .subject("Order Shipped")
                .html("<p>Your order has shipped!</p>")
                .build(),
        BatchEmailRequest.builder()
                .from("Acme <hello@yourdomain.com>")
                .to("b@example.com")
                .subject("Order Confirmed")
                .html("<p>Your order is confirmed!</p>")
                .build());

SendPingResponse response = sendping.batch().sendEmails(requests);
```

**.NET**

```csharp
using SendPing;

ISendPing sendping = SendPingClient.Create("mb_xxxxxxxxx");

var resp = await sendping.EmailBatchSendAsync(new List<BatchEmailMessage>
{
    new()
    {
        From = "Acme <hello@yourdomain.com>",
        To = "a@example.com",
        Subject = "Order Shipped",
        HtmlBody = "<p>Your order has shipped!</p>",
    },
    new()
    {
        From = "Acme <hello@yourdomain.com>",
        To = "b@example.com",
        Subject = "Order Confirmed",
        HtmlBody = "<p>Your order is confirmed!</p>",
    },
});
```

**cURL**

```bash
curl -X POST 'https://www.sendping.co/api/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
sendping 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.sendping.co/docs/api/emails-send) and [Send a batch](https://www.sendping.co/docs/api/emails-batch) for the full parameter reference.
