# Monitor delivery and engagement

Source: https://developer.nylas.com/docs/v3/agent-accounts/monitor-delivery-engagement/

The Send events, Tracking events, and Send alerts APIs report on messages you send from an Agent Account or through [Transactional send](/docs/v3/getting-started/transactional-send/). Use them to check the delivery status of a message for each recipient, track delivery, bounce, and complaint rates over time, measure opens and link clicks, and get a webhook notification when a sending domain's rates cross a threshold you set.

> **Info:** 
> **These APIs cover Agent Account and Transactional send messages only.** Messages sent from a [connected grant](/docs/v3/auth/), such as a Google or Microsoft account, aren't included. Call the endpoints with your application's API key, not a grant access token.

## Use webhooks for real-time events

These APIs are for reviewing patterns and trends over hours, days, and months, and for debugging the delivery of individual messages after they're sent. They aren't for following each message as it happens. Avoid polling them to find out when a message is delivered, bounces, or gets opened. Subscribe to webhook notifications instead. Nylas sends a notification for each event as soon as it happens.

The Send events API records the same delivery outcomes as the existing delivery notifications, and the Tracking events API records the same opens and link clicks as the tracking notifications.

| Event type     | Agent Account notification                                                                       | Transactional send notification                                                                                          |
| -------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| `delivered`    | [`message.delivered`](/docs/reference/notifications/agent-accounts/message-delivered/)           | [`message.transactional.delivered`](/docs/reference/notifications/transactional-email/message-transactional-delivered/) |
| `bounced`      | [`message.bounced`](/docs/reference/notifications/agent-accounts/message-bounced/)               | [`message.transactional.bounced`](/docs/reference/notifications/transactional-email/message-transactional-bounced/)     |
| `complaint`    | [`message.complaint`](/docs/reference/notifications/agent-accounts/message-complaint/)           | [`message.transactional.complaint`](/docs/reference/notifications/transactional-email/message-transactional-complaint/) |
| `rejected`     | [`message.rejected`](/docs/reference/notifications/agent-accounts/message-rejected/)             | [`message.transactional.rejected`](/docs/reference/notifications/transactional-email/message-transactional-rejected/)   |
| `opened`       | [`message.opened`](/docs/reference/notifications/message-tracking/message-opened/)               | [`message.opened`](/docs/reference/notifications/message-tracking/message-opened/)                                       |
| `link_clicked` | [`message.link_clicked`](/docs/reference/notifications/message-tracking/message-link_clicked/) | [`message.link_clicked`](/docs/reference/notifications/message-tracking/message-link_clicked/)                         |

The `accepted` send event, which records that Nylas accepted a message for sending, has no matching notification. To find out when a rate crosses a threshold, [create a send alert](#get-alerted-when-bounce-or-complaint-rates-rise) instead of polling the metrics endpoints. For every endpoint, see the [Send events](/docs/reference/api/send-events/) and [Tracking events](/docs/reference/api/tracking-events/) API references.

## Find out why a message didn't reach a recipient

The Send events API records every step of delivery for each recipient: Nylas accepted the message, then the recipient's mail server delivered it, bounced it, or reported a spam complaint. Each bounce includes the reason and the mail server's response, such as `550 5.1.1 user unknown`. Events are kept for 90 days.

To check one recipient, list their events with `GET /v3/activity/send`. Filter by `recipient` and `event_type` to see only the bounces for that address. You can also filter by `message_id`, `sending_domain`, `from`, or `recipient_domain`, and page through up to 200 events per request.

```bash
curl --request GET \
  --url "https://api.us.nylas.com/v3/activity/send?recipient=leyah@example.org&event_type=bounced&limit=50" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'

```

```json
{
  "request_id": "2272234665-44970660-e8c4-4ecd-8a9f-23465958cf5b",
  "data": [
    {
      "id": "1110f3b5fa7cc381421942082f1e8c8fe2b85f0072b6c67b123a2bf4d8286848",
      "event_type": "bounced",
      "bounce_class": "hard",
      "timestamp": 1792785245,
      "message_id": "5a8f7b2e-3c1d-4e9f-8a6b-2d4c6e8f0a1b",
      "from": "support@mail.example.com",
      "recipient": "leyah@example.org",
      "subject": "Your order has shipped",
      "reason": "General (5.1.1)",
      "detail": {
        "bounce_type": "Permanent",
        "status": "5.1.1",
        "diagnostic_code": "smtp; 550 5.1.1 user unknown"
      }
    }
  ]
}
```

A `hard` bounce means the address doesn't exist or won't accept mail, so stop sending to it. A `soft` bounce, such as a full mailbox, is usually temporary. To see every recipient of one message at once, call [`GET /v3/activity/send/messages/{message_id}`](/docs/reference/api/send-events/get-message-send-events/). It returns each recipient's current status and their full timeline. For a transactional send, the message ID is the `Message-ID` header, so percent-encode it in the path.

## Check the health of your sending

A sending health summary grades your delivery, bounce, and complaint rates over the last 7 days and compares each rate with the previous period. Each rate gets a status of `ok`, `warn`, or `crit`. For example, a hard bounce rate of 5% or more is `warn`, and a complaint rate of 0.5% or more is `crit`.

Call `GET /v3/metrics/send/summary` with a date window of up to 365 days. Pass `sending_domain` to check one domain. When the health `state` is `at_risk` or `critical`, the response includes up to 3 `findings`. Each finding names the likely cause, such as one recipient domain bouncing most of your mail, and recommends what to do.

```bash
curl --request GET \
  --url "https://api.us.nylas.com/v3/metrics/send/summary?start=2026-10-24&end=2026-10-30" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'

```

```json
{
  "data": {
    "health": {
      "state": "at_risk",
      "findings": [
        {
          "rule": "concentration",
          "severity": "watch",
          "title": "example.org is driving hard bounces",
          "recommendation": "Addresses at this domain are failing permanently. Remove them from your lists.",
          "logs_filter": {
            "recipient_domain": "example.org",
            "event_type": ["bounced"],
            "bounce_class": "hard"
          }
        }
      ]
    },
    "totals": { "sent": 600, "delivered": 556, "bounced_hard": 42, "complaints": 0 },
    "metrics": {
      "bounce_rate": { "value": 7, "delta_pp": 6.4, "prior_value": 0.6, "status": "warn" }
    }
  }
}
```

Each finding's `logs_filter` holds query parameters for `GET /v3/activity/send`. Pass them as they are to list the events behind the finding. Health needs at least 100 sent messages in the last 7 days. Below that, the state is `insufficient_data`.

## Spot trends and problem areas

Rates over a whole window can hide the day a problem started or the one domain causing it. The time series and breakdown endpoints split your send metrics by day, week, or month across up to 365 days, and by sending domain, Agent Account, or recipient domain. Use them for dashboards or to investigate a bad week.

`GET /v3/metrics/send/timeseries` returns one bucket per period with counts and rates, including periods with no mail. `GET /v3/metrics/send` groups the same counts by `group_by` and sorts them. This request ranks recipient domains by bounce rate, which is a quick way to find a mailbox provider that's rejecting your mail.

```bash
curl --request GET \
  --url "https://api.us.nylas.com/v3/metrics/send?group_by=recipient_domain&sort=bounce_rate:desc&limit=10" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'

```

```json
{
  "data": {
    "group_by": "recipient_domain",
    "window": { "start": "2026-10-24", "end": "2026-10-30" },
    "rows": [
      {
        "key": "example.org",
        "sent": 412,
        "delivered": 380,
        "bounced_hard": 30,
        "complaints": 0,
        "delivery_rate": 92.23,
        "bounce_rate": 7.28,
        "complaint_rate": 0,
        "is_others": false
      }
    ]
  }
}
```

Each message counts on the day it was sent. A bounce that arrives the next day adds to the send day's numbers, so the last day or two can still change. Group by `agent_account` to compare the senders on a shared domain.

## Get alerted when bounce or complaint rates rise

Send alerts watch your rates for you. Create a rule with a metric, a threshold, and a time window, and Nylas checks it every 5 minutes for each sending domain. When a domain crosses the threshold, you get an `alert.triggered` notification to your webhook, so you can pause a campaign before the problem grows.

Create a rule with `POST /v3/alerts/send`. This rule fires when any domain's hard bounce rate over the last hour goes above 5%. The `min_volume` of 50 stops it from firing on a quiet hour, where 1 bounce out of 3 messages is a 33% rate. An application can have up to 20 rules.

```bash
curl --request POST \
  --url "https://api.us.nylas.com/v3/alerts/send" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "metric": "bounce_rate",
    "threshold": 5,
    "window_hours": 1,
    "min_volume": 50
  }'

```

To receive alerts, [create a webhook](/docs/v3/notifications/) that subscribes to the `alert.triggered` trigger. Each notification includes the rule, the sending domain, the measured rate, and the counts behind it. While the domain stays past the threshold, the notification repeats every 24 hours with the same alert `id` and an incremented `notification_seq`.

```json [alertPayload-Notification (JSON)]

{
  "specversion": "1.0",
  "type": "alert.triggered",
  "source": "/nylas/send",
  "id": "<WEBHOOK_ID>",
  "time": 1793290500,
  "webhook_delivery_attempt": 1,
  "data": {
    "application_id": "<NYLAS_APPLICATION_ID>",
    "object": {
      "object": "alert",
      "id": "4f9c2a7e1b3d5f6a8c0e2b4d6f8a1c3e",
      "grant_id": "",
      "status": "firing",
      "notification_seq": 1,
      "started_at": 1793290500,
      "triggered_at": 1793290500,
      "sending_domain": "mail.example.com",
      "rule": {
        "id": "8069c06e-a4c0-4bfa-9535-0f286575cc0e",
        "application_id": "<NYLAS_APPLICATION_ID>",
        "metric": "bounce_rate",
        "direction": "above",
        "threshold": 5,
        "window_hours": 1,
        "min_volume": 50,
        "created_at": 1792785235,
        "updated_at": 1792785235
      },
      "observed": {
        "value": 8.33,
        "window": {
          "start": 1793286600,
          "end": 1793290200
        },
        "totals": {
          "sent": 120,
          "delivered": 109,
          "bounced_hard": 10,
          "bounced_soft": 1,
          "bounced_undetermined": 0,
          "rejected": 0,
          "complaints": 0
        }
      }
    }
  }
}


```

When an alert fires, check the [sending health summary](#check-the-health-of-your-sending) for the cause. If you're ramping up a new domain, slow down and follow the [domain warming](/docs/v3/agent-accounts/domain-warming/) schedule. Sustained high rates can [pause sending](/docs/v3/agent-accounts/send-limits/#when-nylas-pauses-or-blocks-sending) for a domain.

## See how recipients engage with your messages

The Tracking events API records every open and link click on messages you send with [message tracking](/docs/v3/email/message-tracking/) turned on. Use it to see whether a message was opened, which links people clicked, and how open and click rates compare across date ranges, sending domains, and Agent Accounts. Nylas keeps these events for 90 days.

To see engagement for one message, call `GET /v3/activity/tracking/messages/{message_id}`. It returns the total opens, when the message was first and last opened, and the clicks on each link. Opens and clicks are counted per message, not per recipient, so a message to 3 people shows their combined activity.

```bash
curl --request GET \
  --url "https://api.us.nylas.com/v3/activity/tracking/messages/<MESSAGE_ID>" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'

```

```json
{
  "data": {
    "message_id": "7e15afb2-ba2b-4f3b-b72d-cbbf04921fc1",
    "label": "october-newsletter",
    "tracking": { "opens": true, "links": true },
    "opens": { "total": 3, "first_at": 1792782317, "last_at": 1792782415 },
    "clicks": {
      "total": 3,
      "first_at": 1792782323,
      "last_at": 1792782330,
      "links": [
        { "link_index": 0, "url": "https://www.example.com/", "clicks": 2 },
        { "link_index": 1, "url": "https://www.example.com/pricing", "clicks": 1 }
      ]
    }
  }
}
```

For trends, `GET /v3/metrics/tracking/summary` returns your open and click rates against the previous period, and `GET /v3/metrics/tracking/timeseries` buckets them by day, week, or month. `GET /v3/metrics/tracking` compares sending domains or Agent Accounts. To measure one campaign, set a `tracking_options.label` when you send and filter the tracking feed by `label`.

## Things to know about delivery and engagement data

Delivery and engagement data follows a few rules that affect how you read it. Activity covers the last 90 days, metrics cover up to 365 days, and every count belongs to the day the message was sent. Keep these in mind when you compare recent days with older ones.

- **Activity is kept for 90 days, metrics for longer.** The event feeds and per-message endpoints go back 90 days. The metrics endpoints accept any window of up to 365 days.
- **Messages count on the day they were sent.** Outcomes and opens add to the send day, so rates for the last few days keep changing as results arrive. Compare complete periods when you can.
- **Open rates show trends, not exact reads.** Some mail clients and privacy features load images automatically, which counts as an open. Don't treat one open as proof that someone read the message.
- **Rates use different denominators.** Bounce rate divides hard bounces by sent messages. Complaint rate divides complaints by delivered messages. Open and click rates divide by the messages sent with that kind of tracking on.

## What's next

- [Send events API reference](/docs/reference/api/send-events/) for every filter and response field
- [Tracking events API reference](/docs/reference/api/tracking-events/) for engagement endpoints
- [Send alerts API reference](/docs/reference/api/send-alerts/) to create, update, and delete alert rules
- [Email deliverability for Agent Accounts](/docs/v3/agent-accounts/deliverability/) to set up SPF, DKIM, and DMARC
- [Agent Account usage limits](/docs/v3/agent-accounts/send-limits/) for the bounce and complaint rates that pause sending
- [Tracking messages](/docs/v3/email/message-tracking/) to turn on open and link click tracking