# Start a journey from an order

Source: https://kirora.io/docs/api/recipe-order-journey

Track Order Completed for a contact and send them a thank-you email from a journey that the order starts.

This recipe sends a thank-you email after every order. You create a journey that starts
on the `Order Completed` event, activate it, track an order for a contact, and check that
the contact entered the journey.

Every `POST` here carries an `Idempotency-Key`, so running a step twice is safe: the
second run returns the first response. If you change a request's body, change its key
too, or the API returns 409 `IDEMPOTENCY_KEY_REUSED`.

## Before you start

- **An API key** with `journeys:write`, `journeys:activate`, `contacts:write`,
  `events:write`, `contacts:read` and `messages:read`. See [Authentication](https://kirora.io/docs/api/reference/authentication).
- **Room in the plan's event allowance.** Free takes 1,000 events a month, enough to try
  this recipe; past the allowance, tracking returns 403 `PLAN_LIMIT_REACHED`.
- **What marketing email needs:** a `from` address on a verified sending domain with a
  DMARC record, and the workspace's postal address. Activation checks both.
- **cURL and jq**, and `KIRORA_API_URL` and `KIRORA_API_KEY` set as in the
  [Quickstart](https://kirora.io/docs/api/quickstart).

Replace `mail.juniperkitchen.com` with your verified domain in step 1.

## 1. Create the journey

The journey starts on `Order Completed` (names are case-sensitive), lets a contact enter
again with every order, and sends one email that names the order from the event's
properties:

```sh
curl "$KIRORA_API_URL/v1/journeys" \
  --fail-with-body --silent --show-error -o journey.json \
  -H "Authorization: Bearer $KIRORA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: recipe-order-journey-create-001" \
  -d '{
    "name": "Order thank-you",
    "trigger": {
      "type": "event",
      "name": "Order Completed",
      "reentry": { "mode": "repeatable", "waitAfterEntry": { "value": 0, "unit": "days" } }
    },
    "steps": [
      {
        "id": "thank-you-email",
        "type": "send",
        "channel": "email",
        "content": {
          "subject": "Thanks for your order",
          "from": "Juniper Kitchen <hello@mail.juniperkitchen.com>",
          "templateSnapshot": {
            "schemaVersion": 1,
            "blocks": [
              { "id": "title", "type": "heading", "text": "Thanks for your order" },
              { "id": "body", "type": "text", "text": "We have order {{ event.properties.order_id | default: \"your order\" }} and are packing it now." },
              { "id": "footer", "type": "footer", "text": "{{ org.name }}\n{{ org.address }}", "locked": true }
            ],
            "theme": {}
          }
        },
        "nextStep": null
      }
    ]
  }'
JOURNEY_ID=$(jq -er '.data.id' journey.json)
```

The response is `201` with the journey in `status: "draft"`.

## 2. Activate it

```sh
curl "$KIRORA_API_URL/v1/journeys/$JOURNEY_ID/activate" \
  --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $KIRORA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: recipe-order-journey-activate-001" \
  -d '{}'
```

The response is `200` with the journey `active`. Only events tracked from now on start it.
A 409 `DRAFT_INCOMPLETE` lists what is missing in `errors`; the other refusals are
in [Campaigns and journeys](https://kirora.io/docs/api/campaigns-and-journeys#errors-you-will-meet).

## 3. Create the customer

Marketing email goes only to contacts who consented. This creates the contact with your
own id and records how they agreed. `delivered@example.com` is a
[test address](https://kirora.io/docs/api/transactional#test-addresses): the email goes through every check, is
reported as delivered, and reaches no inbox.

```sh
curl "$KIRORA_API_URL/v1/contacts/identify" \
  --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $KIRORA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: recipe-order-journey-contact-001" \
  -d '{
    "externalId": "customer-4821",
    "email": "delivered@example.com",
    "firstName": "Amaka",
    "consent": {
      "email": true,
      "statement": { "basis": "opted_in", "evidence": "Checkout opt-in on juniperkitchen.com" }
    }
  }'
```

The response is `201` when the contact is new, `200` when it already existed.

## 4. Track the order

Track `Order Completed` with the order's properties, in major units (see
[E-commerce events](https://kirora.io/docs/api/ecommerce-events)). Use the order id in the `Idempotency-Key`, so
a retried order is recorded once:

```sh
curl "$KIRORA_API_URL/v1/events/track" \
  --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $KIRORA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1001-completed" \
  -d '{
    "externalId": "customer-4821",
    "name": "Order Completed",
    "properties": {
      "order_id": "1001",
      "total": 45.00,
      "currency": "USD",
      "products": [
        { "product_id": "sku-paprika", "name": "Smoked paprika", "price": 15.00, "quantity": 3 }
      ]
    }
  }'
```

The response is `201`: the event is stored. The journey picks it up in the background
afterwards, not within this request. A contact that does not exist returns 404; tracking
never creates one.

## 5. Check the enrollment

List the contact's journeys, by your own id:

```sh
curl "$KIRORA_API_URL/v1/contacts/customer-4821/journeys?idType=externalId" \
  --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $KIRORA_API_KEY"
```

An entry whose `journey.id` is your journey's shows the contact entered: `status` is `active` while it
moves through the steps and `completed` once it has. If the list is empty, read it again
shortly. Then read the message it sent:

```sh
curl "$KIRORA_API_URL/v1/messages?journeyId=$JOURNEY_ID" \
  --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $KIRORA_API_KEY"
```

For the test address the message ends `delivered`
with `simulated: true`. To learn of each enrollment as it happens, subscribe to the
`journey.enrolled` webhook instead of reading the list.

## If nothing happens

- **No enrollment:** the event name differs from the trigger's `name` (case included), or the
  journey was not `active` when the event was tracked.
- **An enrollment, but the message is `skipped`:** `skip.reason` says why, for example
  `consent_missing` when the contact has no email consent.

## Next steps

- [Campaigns and journeys](https://kirora.io/docs/api/campaigns-and-journeys): journey states, pausing and ending
- [Objects and rules](https://kirora.io/docs/api/models#journey-definitions): waits, branches and every step type
- [E-commerce events](https://kirora.io/docs/api/ecommerce-events): the order properties and `Ordered Product`
- [Webhooks](https://kirora.io/docs/api/webhooks): receive `journey.enrolled` and `message.*` events