# Sending email

There are two ways to send a single email. Both take the same body.

| | `POST /v1/send` | `POST /v1/enqueue` |
| --- | --- | --- |
| Returns | after your SMTP server accepts the email | as soon as the email is queued |
| Response includes | `messageId` and `outboxId` | `outboxId` and `queuedAt` |
| On a temporary failure | you get the error and decide | retried in the background |
| Plan | Free and Pro | Pro |

Use `/send` when the caller needs to know the email went out — a password reset, a login code.
Use `/enqueue` when it should not wait — a welcome email after sign-up, a notification from a
request handler. For one message to many people, use a [batch](https://docs.marleyfetch.com/guides/batches.md).

## The request body

```json
{
  "from": "you@yourcompany.com",
  "to": ["alice@example.com", "bob@example.com"],
  "cc": "manager@example.com",
  "bcc": "archive@yourcompany.com",
  "subject": "Weekly report",
  "text_body": "Numbers are up.",
  "html_body": "<h1>Weekly report</h1><p>Numbers are up.</p>"
}
```

| Field | Required | |
| --- | --- | --- |
| `to` | yes | Recipients. A single address, a comma-separated string, or an array. |
| `subject` | yes | Non-empty. |
| `text_body` | one of the two | Plain-text body. |
| `html_body` | one of the two | HTML body. Send `text_body` too — some clients and spam filters prefer it. |
| `from` | no | Address of one of your [connections](https://docs.marleyfetch.com/guides/connections.md#choosing-the-sender-from). Omit to use your default connection. |
| `cc`, `bcc` | no | Same formats as `to`. |

`/send` accepts at most **10 recipients** in total across `to`, `cc` and `bcc`; more returns
`400 TOO_MANY_RECIPIENTS`. To reach more people, send a [batch](https://docs.marleyfetch.com/guides/batches.md) — it also sends
each recipient their own copy instead of exposing the whole list in `to`.

## `POST /v1/send`

::: code-group

```bash [cURL]
curl https://api.marleyfetch.com/v1/send \
  -H "Authorization: Bearer $MARLEYFETCH_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: reset-password-8f3a2c" \
  -d '{
    "from": "security@yourcompany.com",
    "to": "user@example.com",
    "subject": "Reset your password",
    "text_body": "Use this link to reset your password: https://...",
    "html_body": "<p>Use <a href=\"https://...\">this link</a> to reset your password.</p>"
  }'
```

```ts [SDK]
await mf.from('security@yourcompany.com')
  .to('user@example.com')
  .subject('Reset your password')
  .text_body('Use this link to reset your password: https://...')
  .html_body('<p>Use <a href="https://...">this link</a> to reset your password.</p>')
  .send()
```

:::

Response:

```json
{
  "success": true,
  "messageId": "<send-4821@mid.marleyfetch.com>",
  "outboxId": 4821,
  "from": "security@yourcompany.com",
  "message": "Email sent successfully"
}
```

If your SMTP server rejects the message, you get a `500` whose `message` is the server's reason,
for example `535 5.7.8 Username and Password not accepted`. The failed attempt does not count
against your quota.

### Safe retries with `Idempotency-Key`

A request can time out after the email was sent. Retrying blindly would send it twice. Add an
`Idempotency-Key` header — any string unique to this email, such as a UUID or
`order-1042-shipped` — and reuse it on retries:

- If the first request **succeeded**, the retry returns its result with `"replayed": true`.
  Nothing is sent again.
- If the first request **failed**, the retry returns that failure with `"success": false` and
  `"replayed": true`. Use a new key to try again.
- If the first request is **still running**, the retry gets `400 IDEMPOTENCY_KEY_IN_FLIGHT`.
  Wait a moment and retry with the same key.

Keys are scoped to your account. `Idempotency-Key` is supported on `/send`.

## `POST /v1/enqueue`

::: code-group

```bash [cURL]
curl https://api.marleyfetch.com/v1/enqueue \
  -H "Authorization: Bearer $MARLEYFETCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "new-user@example.com",
    "subject": "Welcome aboard",
    "html_body": "<p>Thanks for signing up.</p>"
  }'
```

```ts [SDK]
await mf.to('new-user@example.com')
  .subject('Welcome aboard')
  .html_body('<p>Thanks for signing up.</p>')
  .enqueue()
```

:::

Response:

```json
{
  "success": true,
  "message": "Email queued successfully",
  "outboxId": 4822,
  "from": "you@yourcompany.com",
  "queuedAt": "2026-09-28T10:15:00.000Z"
}
```

A background worker sends the email, usually within seconds, and retries it if your provider is
briefly unavailable or rate-limiting. The API has no status endpoint for single emails yet;
follow them in the [Email Outbox](https://app.marleyfetch.com/outbox) by `outboxId`.

`/enqueue` is a Pro feature. On the Free plan it returns `403 FEATURE_NOT_AVAILABLE`.

## Limits

- `/send`: at most 10 recipients per request, and the Free plan's 100 emails a day.
- Both endpoints: the connection's own [quota](https://docs.marleyfetch.com/guides/connections.md#quotas-and-pacing). `/send`
  answers `429` when it is used up; queued emails wait for the next window instead.

See [Errors](https://docs.marleyfetch.com/reference/errors.md) for every error code and how to handle it.
