# Messages

Source: https://kirora.io/docs/api/reference/messages

The message log holds every message Kirora sent or tried to send, from campaigns, journeys and transactional sends, with what became of it. One send to one contact on one channel is one message, so a campaign to 1,000 contacts is 1,000 messages. Export the log as CSV for analysis. See [Message log and exports](https://kirora.io/docs/api/messages). A contact's messages: `GET /v1/contacts/{id}/messages`.

## Endpoints

- [List outbound messages](https://kirora.io/docs/api/reference/listMessages): `GET /v1/messages`
- [Retrieve an outbound message](https://kirora.io/docs/api/reference/getMessage): `GET /v1/messages/{id}`
- [Retrieve a message's content](https://kirora.io/docs/api/reference/getMessageContent): `GET /v1/messages/{id}/content`
- [List a push message's deliveries](https://kirora.io/docs/api/reference/listMessageDeliveries): `GET /v1/messages/{id}/deliveries`
- [Export sent messages as CSV](https://kirora.io/docs/api/reference/createMessageExport): `POST /v1/messages/exports`
- [Retrieve a message export](https://kirora.io/docs/api/reference/getMessageExport): `GET /v1/messages/exports/{id}`
- [Download a message export](https://kirora.io/docs/api/reference/downloadMessageExport): `GET /v1/messages/exports/{id}/download`

## The OutboundMessage object

```json
{
  "id": "0192f3b0-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
  "messageType": "marketing",
  "channel": "email",
  "status": "delivered",
  "campaign": {
    "id": "0192f3d3-5e6f-7071-8c83-9e0f1a2b3c4d",
    "name": "October newsletter"
  },
  "journey": null,
  "template": null,
  "copy": null,
  "contact": {
    "id": "0192f3a1-7c2e-7d4b-9a10-3e5f6a7b8c9d",
    "externalId": "customer-4821",
    "email": "amaka@example.com",
    "firstName": "Amaka",
    "lastName": "Obi"
  },
  "recipientEmail": "amaka@example.com",
  "simulated": false,
  "correlationId": null,
  "sendAt": null,
  "sentAt": "2026-10-01T09:31:00.000Z",
  "deliveredAt": "2026-10-01T09:31:04.000Z",
  "openedAt": "2026-10-01T10:02:00.000Z",
  "openedByMachine": false,
  "clickedAt": null,
  "clickedByMachine": false,
  "complainedAt": null,
  "bounce": null,
  "failure": null,
  "skip": null,
  "createdAt": "2026-10-01T09:30:58.000Z"
}
```

- `id` (string, required, uuid)
- `messageType` (string, required, one of `marketing`, `transactional`) — `marketing` (from `campaign` or `journey`) or `transactional` (neither).
- `channel` (string, required, one of `email`, `push`)
- `status` (string, required, one of `queued`, `sending`, `skipped`, `sent`, `failed`, `delivered`, `bounced`, `complained`)
- `campaign` (object | null, required) — The campaign it came from; null for any other message.
  - `id` (string, required, uuid)
  - `name` (string, required) — The campaign's current name.
- `journey` (object | null, required) — The journey it came from, deleted or not; null for any other message.
  - `id` (string | null, required, uuid) — Null once the journey is deleted.
  - `name` (string | null, required) — The journey's current name; null once it is deleted.
  - `deleted` (boolean, required) — The journey was deleted after sending this. Its messages and their counts stay; nothing names the journey any more.
  - `enrollmentId` (string, required, uuid) — The enrollment that sent it.
  - `stepId` (string, required) — The id of the step that sent it.
- `template` (object | null, required) — The stored template and published version it was sent from; null for inline content.
  - `id` (string, required, uuid)
  - `version` (integer, required, min > 0)
- `copy` (object | null, required) — Set on a `cc` or `bcc` copy; null on the message itself.
  - `kind` (string, required, one of `cc`, `bcc`)
  - `of` (string, required, uuid) — The message it is a copy of.
- `contact` (object | null, required) — Who it was for; null when the contact was deleted, or the message passed the retention period: it no longer names anyone.
  - `id` (string, required, uuid)
  - `externalId` (string | null, required)
  - `email` (string | null, required) — The contact's email now, which may differ from `recipientEmail`.
  - `firstName` (string | null, required) — The contact's first name now.
  - `lastName` (string | null, required) — The contact's last name now.
- `recipientEmail` (string | null, required) — The email address the message went to, frozen at the first attempt; null until then, and for push.
- `simulated` (boolean, required) — Sent to a reserved test address (`example.com` and the like): it passed every check a real send does but was never handed to the provider. Any outcome was simulated from the address, as `bounce@example.com` simulates a bounce.
- `correlationId` (string | null, required) — The reference a transactional send carried; null otherwise.
- `sendAt` (string | null, required, date-time) — When a timed transactional send is due; null for one sent at once.
- `sentAt` (string | null, required, date-time) — When it was handed to the provider.
- `deliveredAt` (string | null, required, date-time) — When the provider confirmed delivery.
- `openedAt` (string | null, required, date-time) — First open, when tracked: a person's first open or click, or a machine's open while only a machine has opened it.
- `openedByMachine` (boolean, required) — Only a machine has opened it so far: a mail app fetched its images with no reader (Apple Mail Privacy Protection, Gmail's prefetch on delivery) or a mail security scanner checked it. A click or a later open by a person clears it.
- `clickedAt` (string | null, required, date-time) — A person's first click, when tracked.
- `clickedByMachine` (boolean, required) — A machine followed one of its links: a mail security scanner checking it on delivery, a link preview. Such a click sets no `clickedAt` and sends no webhook.
- `complainedAt` (string | null, required, date-time) — When the recipient marked it as spam (the first complaint); null for a simulated one.
- `bounce` (object | null, required) — The hard bounce: set when `status` is `bounced`; null otherwise. A soft bounce is a `failure` with reason `transient_bounce`.
  - `occurredAt` (string, required, date-time) — When it happened.
  - `type` (string, required, one of `mailbox_not_found`, `mailbox_full`, `suppressed`, `general`) — Why: `mailbox_not_found`, `mailbox_full`, `suppressed` (the provider refuses the address), or `general` for any other reason.
  - `detail` (string | null, required) — The receiving server's reply, such as `smtp; 550 5.1.1 user unknown`; null for a simulated bounce.
- `failure` (object | null, required) — Set when `status` is `failed`; null otherwise.
  - `occurredAt` (string, required, date-time) — When it happened.
  - `reason` (string, required) — Why sending broke, such as `provider_rejected` (the provider refused the content) or `transient_bounce`. `not_sent`: a problem on Kirora's side stopped it; nothing in your content, audience or setup caused it, and Kirora is alerted. In rare cases the provider may have delivered it before the problem.
- `skip` (object | null, required) — Set when `status` is `skipped`; null otherwise.
  - `occurredAt` (string, required, date-time) — When it happened.
  - `reason` (string, required) — Which guard stopped it before sending, such as `suppressed`, `consent_missing` or `cancelled`; `fallback_not_needed`: a transactional `fallbackOrder` fallback whose first channel arrived; `plan_limit_reached`: a fallback the plan had no room for when it was due; `sending_review_required`: email from a sending domain Kirora has not approved yet.
  - `suppressionReason` (string | null, required, one of `manual`, `imported`, `unsubscribed`, `bounced`, `complained`, `complained_transactional`) — With `suppressed`: why the address is blocked: `unsubscribed`, `bounced`, `complained`, `complained_transactional`, `manual` or `imported`. Null otherwise.
- `createdAt` (string, required, date-time)
