# Email delivery events

> Forwarding delivery, open, bounce and complaint events from your email service to Sayproof.

Source: /en/integrate/email-events/

When your email service reports to Sayproof whether invitations reached the recipient and whether they were opened:

- The invitation funnel on the **Statistics › Invitations** tab is calculated in full: invitations sent, delivered, opened, bounced and turned into a review.
- Addresses that bounce permanently, that complain or that unsubscribe are automatically added to the list of people who do not want emails, and no further invitations are sent to them.

If no delivery events are received, send records remain in the "Sent" state and the open rate is shown as "unknown"; this value is not counted as zero.

## Connecting

1. On the **Email delivery events** tab of the **Integration** screen (shown only if your own system sends the invitations), press **Connect** on the **Email delivery events** card. The **Email delivery events password** is shown only once.
2. Once connected, open the collapsed **Addresses to enter in your email service** section on the card; it lists the endpoint addresses to enter in your email service. Enter the matching address in the webhook setting of the service you use.

| Service | Authentication |
|---|---|
| Generic (signed notice) | The request is signed with the same three headers as signed order sending |
| Amazon SES (through SNS), SendGrid, Postmark, Mailgun | The address contains an access code specific to your brand |

:::caution
Keep addresses that contain an access code as you would a password, and do not share them. If you suspect that an address has fallen into the wrong hands, disconnect with **Disconnect** on the card and connect again. For these services, the identity is verified by the access code in the address.
:::

The service you use is shown on the card once the first event arrives.

To disconnect, press **Disconnect** on the card; after confirmation, events from your email service are no longer taken. To connect again, create a new password with **Connect** and enter it in your email service.

## Events

| Event | Effect |
|---|---|
| `delivered` | The send's state becomes "Delivered" |
| `deferred` | The send's state becomes "Delayed, will be retried" |
| `opened` | "Opened" is recorded; the send is also counted as delivered |
| `bounced` | The send's state becomes "Bounced". If the bounce is permanent, the address is added to the list of people who do not want emails |
| `complained` | The send's state becomes "Customer complained" and the address is added to the list |
| `unsubscribed` | The address is added to the list |

The state only moves forward: a `deferred` event that arrives late does not undo the "Delivered" state. Only a hash of the address is written to the list, not the address itself. Service notices (rejection notices) are not affected by this list.

Only an event that matches an email sent by Sayproof adds an address to the list; an event that does not match is stored, is not counted in `matched` and does not change the list. The address added is the one the email was sent to; a different recipient address in the event is ignored.

## Generic format

For your own email system, or for a service that is not listed:

```http
POST https://api.sayproof.example/v1/mail-events/generic
Content-Type: application/json
X-Sayproof-Secret: PASSWORD-ID
X-Sayproof-Timestamp: 2026-09-28T10:00:00+03:00
X-Sayproof-Signature: …
```

```json
{
  "events": [
    {
      "type": "delivered",
      "reference": "SEND-ID",
      "message_id": "service-message-id",
      "recipient": "jane@example.com",
      "occurred_at": "2026-09-28T10:00:00+03:00",
      "permanent": false,
      "event_id": "evt-1",
      "detail": "hard_bounce"
    }
  ]
}
```

- A single event or a list of up to 500 events can be sent; the body can be at most 1 MB.
- `type` is one of: `delivered`, `deferred`, `bounced`, `complained`, `opened`, `unsubscribed`. Other types are added to the `ignored` count in the response and are not processed.
- `permanent: true` is only taken into account for a `bounced` event.
- `detail` is stored as a code; email addresses in it are replaced with the word `address` before it is stored.
- If a text field contains a character that cannot be stored (NUL `\u0000` or an unpaired surrogate), the request is refused with `400 invalid / field_invalid`.
- If an `event_id` that has already been processed is sent again, it is not processed and is added to the `duplicates` count.
- The signature rules are the same as for [order transfer](/en/integrate/orders.md#signature). Up to 600 requests can be sent in 5 minutes per password.
- The ID in the `X-Sayproof-Secret` header is the **Password ID** column of the **Email delivery events password** row in the table in the **Advanced: signing passwords** section of the **Channels › Keys** tab.

Response: `{"accepted", "matched", "duplicates", "ignored", "opted_out"}`.

### Matching an event to an email

Sayproof determines which email an event belongs to from the `reference` field first and, if nothing is found, from the service's message ID (`message_id`).

- Every email Sayproof sends carries the `X-Sayproof-Message` header. Send this value back as `reference` in your service's event. For the services supported out of the box, the field read is `custom_args.sayproof_message` for SendGrid, `Metadata.sayproof_message` for Postmark and `user-variables.sayproof_message` for Mailgun.
- If you [send invitations from your own system](/en/integrate/own-sending.md), add the `event_id` value from the Sayproof request to the email as the `X-Sayproof-Message` header; this value comes back as `reference` in the event.
