# Send Campaign

`POST https://api.inboxjot.com/v1/campaigns/{campaign_id}/send`

Sends a draft campaign now, or schedules it. A `scheduled_at` in the future schedules the campaign. Without it, or with a time in the past, sending starts now. Call it again on a scheduled campaign to change the time or send it now. The response is `202`, because the emails go out from a queue after it returns. Needs a Full key. A key limited to one domain answers 403. Send an optional `Idempotency-Key` header to retry safely. See Idempotency.

Before it sends, Inboxjot checks that the from address is on a verified domain, the campaign has a subject and content, your postal address is set in Settings, and at least one subscribed contact matches the audience. The daily sending limit and domain warmup still apply, so a large campaign can pause and go on the next day. `paused_reason` then says why.

## Path Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `campaign_id` | string | Yes | The campaign id. |

## Body Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |

## Response

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | No | The campaign id. |
| `name` | string | No | The campaign name. |
| `status` | string | No | `draft`, `scheduled`, `sending`, `paused`, `failed`, `sent` or `canceled`. |
| `from` | string | No | The from address. |
| `reply_to` | string \| null | No | The reply-to address. |
| `subject` | string | No | The subject. |
| `preheader` | string | No | The preview text. |
| `html` | string \| null | No | The rendered HTML, or `null` before there is content. |
| `text` | string \| null | No | The plain text version. |
| `template_id` | string \| null | No | The id of the linked template. |
| `template` | object \| null | No | The linked template. |
| `template.id` | string | No | The id. |
| `template.name` | string | No | The name. |
| `include_segment_ids` | string[] | No | The ids of the segments it sends to. |
| `include_segments` | object[] | No | The segments it sends to. |
| `include_segments[].id` | string | No | The id. |
| `include_segments[].name` | string | No | The name. |
| `exclude_segment_ids` | string[] | No | The ids of the segments it leaves out. |
| `exclude_segments` | object[] | No | The segments it leaves out. |
| `exclude_segments[].id` | string | No | The id. |
| `exclude_segments[].name` | string | No | The name. |
| `topic_id` | string | No | The id of the topic. |
| `topic` | object \| null | No | The topic. |
| `topic.id` | string | No | The id. |
| `topic.name` | string | No | The name. |
| `audience_count` | integer \| null | No | How many contacts the campaign goes to, once it starts. |
| `queued_count` | integer | No | Emails queued so far. |
| `skipped_count` | integer | No | Contacts skipped, such as suppressed addresses. |
| `paused_reason` | string \| null | No | Why a paused campaign stopped: `manual`, `daily_limit`, `warmup`, `sending_paused` or `domain_unverified`. |
| `failure_reason` | string \| null | No | Why a failed campaign stopped. |
| `scheduled_at` | string \| null | No | When a scheduled campaign starts. |
| `started_at` | string \| null | No | When sending started. |
| `completed_at` | string \| null | No | When sending finished. |
| `warmup_next_limit` | integer \| null | No | The daily limit of the next warmup step when domain warmup paused the campaign, otherwise `null`. |
| `created_at` | string | No | When it was created. |
| `updated_at` | string | No | When it last changed. |

## Request

cURL:

```bash
curl -X POST https://api.inboxjot.com/v1/campaigns/cpg_06d2mfk8r7hm4ps9wb2kd/send \
  -H "Authorization: Bearer $INBOXJOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scheduled_at": "2026-10-22T09:00:00Z"
  }'
```

## Responses

| Status | Description |
| --- | --- |
| 202 | Accepted. The campaign is scheduled or sending. |
| 400 | The from domain is not verified, the subject or content is missing, the postal address is not set, no subscribed contact matches the audience, or the Idempotency-Key header is not valid. |
| 401 | The API key is missing, revoked or expired. |
| 403 | The key is a Sending key or is limited to one domain, or the request comes from outside its IP allowlist. |
| 404 | The campaign does not exist in this workspace. |
| 409 | The campaign is not a draft or scheduled, or a request with the same Idempotency-Key is still running. |
| 413 | The body is larger than 2 MB. |
| 415 | The Content-Type header is not application/json. |
| 422 | The Idempotency-Key was already used with a different request. |
| 429 | Too many requests for this key. Wait for the seconds in the Retry-After header. |
| 500 | Something failed on our side. Retry later. |

202:

```json
{
  "id": "cpg_06d2mfk8r7hm4ps9wb2kd",
  "name": "October product update",
  "status": "scheduled",
  "from": "Acme <hello@acme.dev>",
  "reply_to": null,
  "subject": "What’s new in October",
  "preheader": "Scheduled sends are here",
  "html": "<!DOCTYPE html><html>…</html>",
  "text": "Hi Suhasinee, scheduled sends are here…",
  "template_id": null,
  "template": null,
  "include_segment_ids": [
    "seg_06d2mfk8r2kd7fq4mx9ab"
  ],
  "include_segments": [
    {
      "id": "seg_06d2mfk8r2kd7fq4mx9ab",
      "name": "Customers"
    }
  ],
  "exclude_segment_ids": [],
  "exclude_segments": [],
  "topic_id": "top_06d2mfk8r6pn2tc8yw3hd",
  "topic": {
    "id": "top_06d2mfk8r6pn2tc8yw3hd",
    "name": "Product updates"
  },
  "audience_count": null,
  "queued_count": 0,
  "skipped_count": 0,
  "paused_reason": null,
  "failure_reason": null,
  "scheduled_at": "2026-10-22T09:00:00.000Z",
  "started_at": null,
  "completed_at": null,
  "warmup_next_limit": null,
  "created_at": "2026-10-21T09:00:00.000Z",
  "updated_at": "2026-10-21T09:00:00.000Z"
}
```

400:

```json
{
  "name": "validation_error",
  "status": 400,
  "message": "Add a subject",
  "errors": [
    {
      "message": "Add a subject",
      "location": "subject"
    }
  ],
  "request_id": "5f0c6a2e-8d1b-4c7a-9e3f-2b6d4a1c8e90"
}
```

401:

```json
{
  "name": "unauthorized",
  "status": 401,
  "message": "Unauthorized",
  "errors": [],
  "request_id": "5f0c6a2e-8d1b-4c7a-9e3f-2b6d4a1c8e90"
}
```

403:

```json
{
  "name": "forbidden",
  "status": 403,
  "message": "This key cannot be used from 203.0.113.7",
  "errors": [],
  "request_id": "5f0c6a2e-8d1b-4c7a-9e3f-2b6d4a1c8e90"
}
```

404:

```json
{
  "name": "not_found",
  "status": 404,
  "message": "Campaign not found",
  "errors": [],
  "request_id": "5f0c6a2e-8d1b-4c7a-9e3f-2b6d4a1c8e90"
}
```

409:

```json
{
  "name": "concurrent_idempotent_requests",
  "status": 409,
  "message": "A request with this idempotency key is still in progress. Try again later.",
  "errors": [],
  "request_id": "5f0c6a2e-8d1b-4c7a-9e3f-2b6d4a1c8e90"
}
```

413:

```json
{
  "name": "payload_too_large",
  "status": 413,
  "message": "The request body is too large",
  "errors": [],
  "request_id": "5f0c6a2e-8d1b-4c7a-9e3f-2b6d4a1c8e90"
}
```

415:

```json
{
  "name": "unsupported_media_type",
  "status": 415,
  "message": "The Content-Type must be application/json",
  "errors": [],
  "request_id": "5f0c6a2e-8d1b-4c7a-9e3f-2b6d4a1c8e90"
}
```

422:

```json
{
  "name": "invalid_idempotent_request",
  "status": 422,
  "message": "This idempotency key was already used with a different request",
  "errors": [],
  "request_id": "5f0c6a2e-8d1b-4c7a-9e3f-2b6d4a1c8e90"
}
```

429:

```json
{
  "name": "rate_limit_exceeded",
  "status": 429,
  "message": "Too many attempts. Try again later.",
  "errors": [],
  "request_id": "5f0c6a2e-8d1b-4c7a-9e3f-2b6d4a1c8e90"
}
```

500:

```json
{
  "name": "unknown_error",
  "status": 500,
  "message": "Something went wrong. Please try again later.",
  "errors": [],
  "request_id": "5f0c6a2e-8d1b-4c7a-9e3f-2b6d4a1c8e90"
}
```
