> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dev.gojinko.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Send your own customer emails

> Receive a webhook event for every email Jinko sends your travelers, with everything the email shows, and ask Jinko to stop sending its own.

Jinko emails your travelers at the key moments of a booking: payment, confirmation and e-tickets, cancellation, exchange and failure notices. Each of those moments is also a webhook event, and the event carries what the email shows. Use the events to send emails in your own brand, next to Jinko's or, once Jinko has turned its emails off for your tenant, in place of them.

```
subscribe to the events → each event carries data.customer_emails[] → you send your email
```

## Before you start

* A webhook subscription for your tenant, in [Dashboard → Webhooks](https://dashboard.gojinko.com/developers/webhooks). [Webhooks](/guides/webhooks) covers registering an endpoint, verifying the signature, retries and idempotency. This page covers only what is specific to customer emails.
* Until this feature launches, it works only in the development environment: subscribe to the new events and read the delivery log on the development dashboard, [dashboard.dev.gojinko.com/developers/webhooks](https://dashboard.dev.gojinko.com/developers/webhooks). The Dashboard links on this page go to the production dashboard, where both appear at launch.

## Events and the emails they carry

| Event | `status` | When it fires | Jinko's emails at that moment | Fallback |
| - | - | - | - | - |
| `booking.processing` | `processing` | Payment is authorized and the booking is being confirmed with the suppliers. No supplier reference exists yet. | `booking-confirmation` | no |
| `booking.completed` | `confirmed` | Every item of the booking is confirmed and the payment is captured. | `flight-ticketing-confirmation`, `hotel-booking-confirmation`, `booking-confirmation` (one for ground items, one for car items) | yes |
| `booking.failed` | `failed` | The booking could not be confirmed. The payment hold is released and nothing is charged. | `booking-failed` | yes |
| `servicing.completed` | `servicing.completed` | A cancellation of one booked item is complete and its money is settled. A cancellation can refund nothing, for example on a non-refundable item: `data.refund` is then `null`. | `flight-cancellation`, `hotel-cancellation`, `car-cancellation` | yes |
| `servicing.exchange_confirmed` | `servicing.exchange_confirmed` | The supplier confirmed an exchange of one booked item. For a flight, the new e-ticket number can follow in `servicing.ticket_issued`. | `flight-exchange`, `car-exchange` | yes |
| `servicing.ticket_issued` | `servicing.ticket_issued` | The new e-ticket number of an exchanged flight is available. | `flight-exchange-ticket-number` | yes |
| `servicing.failed` | `servicing.failed` | An exchange or a refund could not be completed. The original booking is unchanged unless the payload says otherwise. | `exchange-failed`, `refund-failed` | yes |

* One event can carry several emails: a booking with a flight and a hotel produces one `booking.completed` with two entries.
* With Jinko's emails on, Jinko sends its email first and then the event.
* `booking.partial` is unchanged. Jinko sends no email at that moment, so it carries no `customer_emails`.
* The four `servicing.*` events can fire more than once for a booking, so their `event_id` ends with a suffix that names the occurrence, for example `evt_402_servicing.completed_si-5531`. Treat `event_id` as an opaque string. Retries of one occurrence reuse its id.
* The **Fallback** column says whether Jinko still sends its own email when your endpoint did not receive the event: see [When Jinko still sends its email](#when-jinko-still-sends-its-email).

The webhook form in the dashboard lists every event. With a legacy person-bound key, pass the event names in `events` on `POST /v1/webhooks`.

## What an event carries

Each event keeps the six envelope fields and any `data` it already had (see [Webhooks](/guides/webhooks)). Its `data` gains the manage-booking link and one `customer_emails` entry per email:

```json theme={null}
{
  "event": "booking.completed",
  "booking_ref": "JNK-8F3D21",
  "status": "confirmed",
  "occurred_at": "2027-06-02T08:00:00Z",
  "event_id": "evt_4471_booking.completed",
  "livemode": true,
  "data": {
    "manage_booking_url": "https://app.gojinko.com/bookings/JNK-8F3D21?ln=Martin",
    "customer_emails": [
      {
        "template": "flight-ticketing-confirmation",
        "to": "jane.martin@example.com",
        "subject": "Your tickets are confirmed – JNK-8F3D21",
        "content": {
          "pnr": "JNK-8F3D21",
          "airline_pnr": "XM9L7Q",
          "destination_city_name": "Paris",
          "passengers": [
            { "first_name": "Jane", "last_name": "Martin", "type": "Adult", "ticket_number": "0167654321098" }
          ]
        }
      },
      {
        "template": "hotel-booking-confirmation",
        "to": "jane.martin@example.com",
        "subject": "Your stay at Hôtel Marais is confirmed",
        "content": {
          "pnr": "JNK-8F3D21",
          "supplier_booking_id": "4412983",
          "hotel": { "name": "Hôtel Marais" },
          "stay": { "duration_label": "3 nights" },
          "guests": [{ "first_name": "Jane", "last_name": "Martin" }]
        },
        "calendar": {
          "files": [
            {
              "method": "REQUEST",
              "filename": "jinko-JNK-8F3D21.ics",
              "content_type": "text/calendar; charset=utf-8; method=REQUEST",
              "content": "BEGIN:VCALENDAR\r\n…"
            }
          ]
        }
      }
    ]
  }
}
```

| Field | Notes |
| - | - |
| `data.manage_booking_url` | The manage-booking link Jinko's emails carry. It includes the traveler's last name. Absent on `booking.failed`, `servicing.ticket_issued` and `servicing.failed`. |
| `data.customer_emails[]` | One entry per email Jinko sends, or would send, at that moment, in sending order. The entries are the same whether Jinko's own emails are on or off. |
| `customer_emails[].template` | Which email this is. It tells you the shape of `content`. |
| `customer_emails[].to` | The address Jinko's email goes to: the booking contact. Absent when the booking has none. |
| `customer_emails[].subject` | The subject line of Jinko's email. |
| `customer_emails[].content` | The values the email shows, under snake\_case keys. Dates, times and amounts are display strings, formatted as the email shows them. Empty values are left out; numbers and booleans are always sent. Styling (brand, colors, header copy) is not included. |
| `customer_emails[].calendar` | The calendar files the email attaches, in the `{ files: [{ method, filename, content_type, content }] }` shape of the booking read. Present only when the email attaches one. |

The example shows only some fields of each `content`. The `content` shape of each template is a schema named after the template: for example `FlightTicketingConfirmationEmailContent` or `HotelCancellationEmailContent`. Until this feature launches, those schemas are only in the development API's OpenAPI document, [`https://api.dev.gojinko.com/doc`](https://api.dev.gojinko.com/doc); at launch they join the API reference. A later version can add fields to `content` and entries to `customer_emails`, so ignore what you don't use.

The three new `servicing.*` events also carry `operation_kind` (`cancel`, `void`, `exchange` or `refund`) and, when known for that operation, `item`, `booking_ref` and `operation`. `servicing.completed` keeps every field described in [Webhooks](/guides/webhooks). `booking.failed` keeps `failure_reason` and `failure_message`.

<Warning>
  These payloads contain traveler data: names, email addresses, itineraries and ticket numbers. Jinko keeps the payload of each delivery for the delivery log and replay, and deletes it 30 days after the delivery's last attempt or replay.
</Warning>

## Ask Jinko to stop sending its emails

Jinko can turn its customer emails off for one of your tenants. Email [dev@gojinko.com](mailto:dev@gojinko.com) with the tenant id and the environment.

* **Per tenant and per environment.** Your sandbox tenant and your production tenant are switched separately.
* **All or nothing.** With emails off, Jinko sends none of the emails in the table above for that tenant's bookings, except as a fallback (next section).
* **Which bookings.** Bookings made with that tenant's keys, including keys that name an end user. Bookings made with a legacy person-bound key keep Jinko's emails.
* **When.** The change applies within about a minute, including to bookings already in progress.
* **The events don't change.** Every event and its `customer_emails` are sent the same way with emails on or off.

## When Jinko still sends its email

With emails off, Jinko still sends the email of an event marked **yes** in the Fallback column when none of the tenant's subscriptions received that event:

* **No subscription lists the event:** Jinko sends its email at once.
* **Every delivery failed its last attempt** (8 attempts over about 80 minutes): Jinko sends its email then.
* **The outcome is still unknown 3 hours after the event:** Jinko sends its email.

Jinko sends a fallback email once. When any subscription received the event, Jinko sends nothing. A replay of the event after the fallback does not withdraw the email Jinko already sent.

`booking.processing` has no fallback: with emails off, Jinko does not send the payment-authorized email.

## Delivery log and replay

Every attempt to deliver an event is recorded.

* **Dashboard:** [Dashboard → Webhooks](https://dashboard.gojinko.com/developers/webhooks), then **Deliveries** on the webhook. Each delivery shows its status, attempts, last HTTP status, last error, timestamps and the payload as sent.
* **API, with a legacy person-bound key:** `GET /v1/webhooks/{id}/deliveries` lists deliveries newest first. It takes `limit` (1 to 100, default 20), `cursor` (the `next_cursor` of the previous page), `status` and `event_type`. `POST /v1/webhooks/{id}/deliveries/{delivery_id}/replay` replays one.

| `status` | Meaning |
| - | - |
| `pending` | Not attempted yet, or a replay is queued. |
| `delivered` | Your endpoint answered `2xx`. |
| `failed` | The last attempt failed and another one is scheduled. |
| `exhausted` | The last of the 8 attempts failed. Jinko does not retry it. |

A replay sends the same event again: same `event_id`, same payload, signed with a new `X-Jinko-Timestamp`. It starts a fresh set of attempts and adds one to the delivery's `replay_count`. Replay is accepted for a `delivered` or `exhausted` delivery. It is refused with `delivery_in_progress` while the delivery is `pending` or `failed`, and with `payload_purged` once its payload has been deleted, 30 days after the delivery's last attempt or replay.

Because a replay keeps the `event_id`, a receiver that deduplicates on `X-Jinko-Event-Id` treats it as the event it already processed, if it did.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.