# CLI for AI agents

> Use SendPing from AI agent workflows and CI/CD pipelines — non-interactive sends, stdin piping, batch, idempotent retries, scheduling, and webhook feedback loops, all over plain HTTP.

SendPing is well suited to AI agent workflows and CI/CD pipelines: it is a plain JSON REST API with no interactive login, so an agent can drive it with `curl` (and `jq`) the moment it has an `mb_` key in its environment. This page covers the agent-specific patterns. See the [CLI](https://www.sendping.co/docs/resources/cli) reference for the full command surface and the [CLI Quickstart](https://www.sendping.co/docs/resources/cli-quickstart) to send your first email.

If your agent prefers a typed client, the official `sendping` Node SDK wraps the same endpoints — see [SDKs](https://www.sendping.co/docs/resources/sdks).

## Non-interactive by default

There is nothing to switch on. Every SendPing request is a single HTTP call that returns machine-readable JSON to stdout, with no progress indicators and no prompts. Capture the HTTP status to drive exit codes so a failed call fails the agent’s step:

```bash
status=$(curl -s -o /tmp/out.json -w '%{http_code}' -X POST 'https://www.sendping.co/api/emails' \
  -H "Authorization: Bearer $SENDPING_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{ "from": "Acme <onboarding@yourdomain.com>", "to": ["delivered@test.sendping.co"], "subject": "Welcome", "text": "Welcome aboard." }')

if [ "$status" -ge 400 ]; then
  jq -r '.message' /tmp/out.json >&2   # structured error: { "name", "message" }
  exit 1
fi
jq -r '.id' /tmp/out.json
```

Errors always come back as the same structured envelope — `statusCode`, `name`, and `message` — alongside a non-2xx status. Branch on `name`, never on `message`. See the [error reference](https://www.sendping.co/docs/api/errors). An example auth failure:

```json
{
  "statusCode": 401,
  "name": "missing_api_key",
  "message": "Missing API key. Pass it as `Authorization: Bearer mb_...`."
}
```

## Piping content from stdin

Agents generate content on the fly. Build the request body with `jq` and pipe it straight into `curl` with `-d @-` instead of writing temp files — `--rawfile`/`--arg` keep the generated text safely JSON-escaped:

```bash
echo "Your order has shipped." | jq -Rs '{
    from: "Acme <onboarding@yourdomain.com>",
    to: ["delivered@test.sendping.co"],
    subject: "Order update",
    text: .
  }' | curl -s -X POST 'https://www.sendping.co/api/emails' \
  -H "Authorization: Bearer $SENDPING_API_KEY" \
  -H 'Content-Type: application/json' \
  -d @-
```

The same `jq -n --rawfile html ./body.html` pattern injects an HTML body from a file — see the [CLI](https://www.sendping.co/docs/resources/cli) reference.

## Batch sending

Send up to 100 emails in a single request by POSTing a JSON array to [POST /emails/batch](https://www.sendping.co/docs/api/emails-batch). Read the array straight from a file the agent wrote with `-d @-`:

```bash
cat emails.json | curl -s -X POST 'https://www.sendping.co/api/emails/batch' \
  -H "Authorization: Bearer $SENDPING_API_KEY" \
  -H 'Content-Type: application/json' \
  -d @- \
  | jq '.data'
```

## Safe retries

Add an `Idempotency-Key` header to prevent duplicates when an agent retries a failed request. Reusing the same key returns the original result instead of sending twice. The header works on both `/emails` and `/emails/batch`.

```bash
curl -X POST 'https://www.sendping.co/api/emails' \
  -H "Authorization: Bearer $SENDPING_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: welcome-user-123' \
  -d '{ "from": "Acme <onboarding@yourdomain.com>", "to": ["delivered@test.sendping.co"], "subject": "Welcome", "text": "Hello!" }'
```

## Scheduling

The `scheduled_at` field accepts an ISO 8601 timestamp or natural language like `"tomorrow at 9am"` or `"in 2 hours"` (relative to UTC) — see [Schedule emails](https://www.sendping.co/docs/emails/schedule).

```bash
curl -X POST 'https://www.sendping.co/api/emails' \
  -H "Authorization: Bearer $SENDPING_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "from": "Acme <onboarding@yourdomain.com>",
    "to": ["you@yourdomain.com"],
    "subject": "Your trial ends soon",
    "text": "Your free trial expires in 3 days.",
    "scheduled_at": "tomorrow at 9am"
  }'
```

Cancel or reschedule a queued email with [POST /emails/:id/cancel](https://www.sendping.co/docs/api/emails-cancel) and [PATCH /emails/:id](https://www.sendping.co/docs/api/emails-update):

```bash
# Reschedule
curl -X PATCH 'https://www.sendping.co/api/emails/49a3999c-0ce1-4ea6-ab68-afcd6dc2e794' \
  -H "Authorization: Bearer $SENDPING_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{ "scheduled_at": "in 2 hours" }'

# Cancel
curl -X POST 'https://www.sendping.co/api/emails/49a3999c-0ce1-4ea6-ab68-afcd6dc2e794/cancel' \
  -H "Authorization: Bearer $SENDPING_API_KEY"
```

## Closing the loop with webhooks

When an agent sends an email, it usually needs to know what happened next — delivered, bounced, opened, or replied to. Register a [webhook](https://www.sendping.co/docs/webhooks/overview) pointing at a public URL (use a tunnel such as Tailscale Funnel, ngrok, or localtunnel during development) so SendPing streams [signed events](https://www.sendping.co/docs/webhooks/verify) to your handler as they occur.

```bash
curl -X POST 'https://www.sendping.co/api/webhooks' \
  -H "Authorization: Bearer $SENDPING_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "endpoint": "https://hostname.tailnet-name.ts.net/api/webhook",
    "events": ["email.delivered", "email.bounced", "email.received"]
  }'
```

> **Note:** Always verify the webhook signature before trusting a payload — the signing secret is returned when you create the webhook. See [Verify webhooks](https://www.sendping.co/docs/webhooks/verify).
