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

# Send your own customer emails

> Use Jinko webhook data to send booking emails in your own brand.

Jinko webhook events can include the subject, recipient, content, and calendar files for each traveler email. Use this data to send booking and servicing emails in your own brand.

## How it works

1. Subscribe to the booking and servicing events you need.
2. Read each entry in `data.customer_emails`.
3. Render the content with your own templates and branding.
4. Send the email to the address in `customer_emails[].to`.

You can send your emails alongside Jinko's or ask Jinko to stop its own emails for your tenant.

## Before you start

Create a webhook subscription for your tenant on the [Webhooks page in the dashboard](https://dashboard.gojinko.com/developers/webhooks). The [Webhooks guide](/guides/webhooks) explains endpoint registration, signature verification, retries, and replay. This page covers the email data carried by those events.

## Events and the emails they carry

| Event | `status` (deprecated) | 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. A seat can still be pending (see below). | `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` | 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 |

**Seat confirmation:** Jinko sends `booking.completed` and the flight booking confirmation email once the flight is ticketed, even if a seat is still pending. Read `booked_ancillaries` for each seat's outcome. Keep the seat pending in your confirmation email until its outcome is known. See [Webhooks](/guides/webhooks#choose-events) and [Seat maps](/concepts/ancillaries/seats).

1. One event can carry several emails. A trip with a flight and a hotel can produce two entries in one `booking.completed` event.
2. When Jinko emails are on, Jinko sends its email before the event.
3. `booking.partial` carries no `customer_emails` because Jinko sends no email at that moment.
4. Treat `event_id` as an opaque value. Retries of the same event reuse it.
5. The **Fallback** column shows whether Jinko sends its email when your endpoint does not receive the event.

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 one `customer_emails` entry per email and, when the email carries it, the manage-booking link:

```json theme={null}
{
  "event": "booking.completed",
  "booking_ref": "JNK-8F3D21",
  "status": "confirmed",
  "occurred_at": "2027-06-02T08:00:00Z",
  "event_id": "evt_e5a09b3c7d12",
  "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`. They are in the [OpenAPI contract](https://api.gojinko.com/doc), and in the API reference under the delivery `payload` of [`GET /v1/webhooks/{id}/deliveries`](/api/webhooks/deliveries). 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, `booked_item_id`, `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.

## Use your own email delivery

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

1. **Per tenant and environment:** sandbox and production are configured separately.
2. **All or nothing:** Jinko turns off every email in the table for that tenant, except fallback emails.
3. **Tenant-key bookings only:** bookings made with legacy person-bound keys keep Jinko emails.
4. **Fast activation:** the change applies within about a minute, including to bookings already in progress.
5. **No webhook change:** events and `customer_emails` stay the same.

## When Jinko still sends its email

When Jinko emails are off, an event marked **yes** in the Fallback column still triggers a Jinko email if none of the tenant's subscriptions receive it:

1. No subscription includes the event.
2. Every delivery reaches its last failed attempt after 8 attempts over about 80 minutes.
3. The delivery outcome is still unknown 3 hours after the event.

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.

## Handle delivery issues

Every delivery attempt is recorded. Use the delivery log to inspect failures and replay an event.

1. **Dashboard:** open [Webhooks in the dashboard](https://dashboard.gojinko.com/developers/webhooks), select the webhook, then open **Deliveries**.
2. **API with a legacy person-bound key:** [`GET /v1/webhooks/{id}/deliveries`](/api/webhooks/deliveries) lists deliveries. [`POST /v1/webhooks/{id}/deliveries/{delivery_id}/replay`](/api/webhooks/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. Deliveries that failed before this log existed are not retried and can't be replayed. |
| `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.