# Send emails with HTTP

> Queue a message with curl and a Bearer token.

## What you'll build

A single `curl` request that queues an email through `POST /api/v1/emails`. Any language with an HTTP client can make the same call.

## Before you start

- An API key with **Send only** access. **Full access** also works. See [Create an API key](/docs/create-an-api-key). Support keys cannot send.
- A verified sending domain. See [Add and verify a domain](/docs/add-a-domain). `from` must be an address on it.
- A cluster that is ready to send (`sending_ready` is `true`). See [Clusters](/docs/dashboard/clusters/introduction).

## Install

Nothing to install. The examples use `curl`.

## Send your first email

POST JSON with your API key as a Bearer token.

```bash send-emails.sh
curl -sS -X POST \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "from": "hello@mail.example.com",
    "to": ["you@example.com"],
    "subject": "Your receipt",
    "text": "Thanks for your order.",
    "html": "<p>Thanks for your order.</p>",
    "unique_args": {"order_id": "ord_123"}
  }' \
  https://app.postshiba.com/api/v1/emails
```

A queued send returns `201 Created`. The message then shows under Events in the dashboard.

```json response.json
{
  "queued": true,
  "message_id": "abc@capsule.test",
  "from": "hello@mail.example.com",
  "to": ["you@example.com"],
  "subject": "Your receipt",
  "unique_args": {"order_id": "ord_123"}
}
```

`to`, `cc`, and `bcc` take an array or a comma-separated string. Every field is on [Sending](/docs/api-reference/sending).

### Pin a cluster

Without a pin, PostShiba uses your first cluster that is ready to send. Add `X-Capsule-Cluster-Id` to choose one. The cluster id is on the **API keys** page.

```bash pin-cluster.sh
curl -sS -X POST \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -H 'X-Capsule-Cluster-Id: NmQpXr' \
  -d '{"from": "hello@mail.example.com", "to": ["you@example.com"], "subject": "Hello", "text": "Hello"}' \
  https://app.postshiba.com/api/v1/emails
```

For high-volume sending, use SMTP or HTTP inject. See [Send emails with SMTP](/docs/send-with-smtp) and [Sending](/docs/api-reference/sending).

## Attachments and templates

### Attach a file

Add an `attachments` array. Each item has `filename`, `content_type`, and base64 `content`. The API accepts up to 10 files, 18 MB in total. Allowed types are images (including SVG and HEIC), PDF, EPUB, Office and OpenDocument files, RTF, plain text, CSV, zip archives, contact cards, and calendar invites. Files are scanned for viruses.

```bash send-attachment.sh
curl -sS -X POST \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "from": "hello@mail.example.com",
    "to": ["you@example.com"],
    "subject": "Your photo",
    "text": "See attached.",
    "attachments": [
      {
        "filename": "photo.png",
        "content_type": "image/png",
        "content": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="
      }
    ]
  }' \
  https://app.postshiba.com/api/v1/emails
```

### Send a published template

Pass `template` with the template's alias or public id and its `variables`. Leave out `html` and `text`. The template must be published.

```bash send-template.sh
curl -sS -X POST \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "from": "hello@mail.example.com",
    "to": ["you@example.com"],
    "template": {"id": "welcome", "variables": {"name": "Ada"}}
  }' \
  https://app.postshiba.com/api/v1/emails
```

## Keep the key safe

Use a **Send only** key in your app. It can send email and call `GET /api/v1/users/me`. Every other request returns `403` `forbidden`.

| If this key leaks | Send only | Full access |
| --- | --- | --- |
| Send mail from your verified domains | Yes | Yes |
| Read events, recipients, templates, and sent messages | No | Yes |
| See SMTP passwords and webhook secrets | No | Yes |
| Change clusters, domains, credentials, webhooks, and suppressions | No | Yes |

- Keep Full access keys for setup scripts, the [CLI](/docs/send-with-cli), and [MCP](/docs/connect-mcp). Don't deploy one with your app.
- Read the key from an environment variable such as `POSTSHIBA_API_KEY` and send it as `Authorization: Bearer $POSTSHIBA_API_KEY`. Don't commit it, log it, or put it in a URL.
- Send from your server. Never put a key, even a Send only key, in browser or mobile code.
- Create one key per app and environment. If a key leaks, delete it on **API keys** and create a new one. Deleting stops it straight away.

## Troubleshooting

Errors are JSON with an `error` string. Send errors also include `field` and `message`.

| Status | `error` | What to do |
| --- | --- | --- |
| `401` | `Invalid token or no user signed in` | The API key is missing, mistyped, or deleted. Copy it again from **API keys**. |
| `403` | `forbidden` | Support keys cannot send. Use a Send only or Full access key. A Send only key gets this on any call other than sending. |
| `403` | `domain_unverified` | `from` is not on a verified domain. Finish [Add and verify a domain](/docs/add-a-domain). |
| `403` | `domain_suspended` | The From domain is paused. Resume it from [Managing domains](/docs/dashboard/domains/manage-domains). |
| `403` | `kyc_required` | Your team is pending production approval. Until then, send only to team members, live inbox addresses, and inbox forward-to addresses. See [Does PostShiba require production approval?](/docs/knowledge-base/does-capsule-require-production-approval) |
| `422` | `invalid` | `field` names the problem, such as a missing `to`, an unknown `tenant`, an unpublished template, or an attachment that is too large or not an allowed type. |
| `429` | `throttled` | The cluster hit its hourly throttle or 24-hour cap. Retrying right away hits the same cap. See [What happens at your cap](/docs/dashboard/clusters/what-happens-at-your-cap). |
| `429` | `rate_limited` | More than 300 send requests in one minute on one API key. Slow down and try again. |

Every other status is on [Errors](/docs/api-reference/errors).

## Next steps

- [Unique args](/docs/dashboard/emails/unique-args) to get your own ids back on delivery webhooks.
- [Managing webhooks](/docs/webhooks/introduction) to receive delivery events.
- [Send with a template](/docs/dashboard/templates/send-with-a-template)
- [SDKs](/docs/sdks) for language clients that wrap this request.
