# Performance tracking

> Every campaign recipient is sent as an individual tracked email, so opens, clicks, bounces, and complaints surface per-contact in the dashboard and via webhooks.

A campaign is not a single send — it fans out into **one tracked email per recipient**. Each of those emails is logged exactly like a transactional [`POST /emails`](https://www.sendping.co/docs/api/emails-send) send, with its own `id`, status, and event timeline.

That means campaign performance reuses the same delivery, open, and click tracking as the rest of the platform — there is no separate analytics surface to learn.

## What is tracked

- **Delivery** — `email.sent`, `email.delivered`, and `email.delivery_delayed` per recipient.
- **Engagement** — `email.opened` and `email.clicked`, when open/click tracking is enabled on the sending domain.
- **Negative signals** — `email.bounced` and `email.complained`, which also add the address to your account suppression list.
- **Unsubscribes** — recipients who opt out via the per-campaign unsubscribe link, flipping that contact to `unsubscribed` in the audience.

> **Note:** Open and click tracking is a per-domain setting. If a campaign shows deliveries but no opens, confirm tracking is enabled on the from-domain under Domains.

## Headline metrics

Open a sent campaign in the dashboard to see its performance right away. The headline insights are:

- **Emails delivered** — how many copies the receiving servers accepted.
- **Open rate** — the share of delivered emails that were opened.
- **Click rate** — the share of delivered emails with at least one tracked-link click.
- **Replied**, **Bounced** and **Complained** — the raw counts behind those signals.

> **Warning:** Open rates can be inaccurate. Some inbox providers pre-fetch or proxy the tracking pixel (e.g. Apple Mail Privacy Protection), inflating opens, while others block it entirely, suppressing them. Treat open rate as a directional signal, not an exact count.

## Reading results

Each recipient email carries an event log you can read the same way as any other email:

- In the **dashboard**, open the campaign to see per-recipient status and aggregate delivery, open, and click counts.
- Via the **API**, retrieve an individual recipient email with [`GET /emails/:id`](https://www.sendping.co/docs/api/emails-get) to inspect its `last_event` and status.
- Via **webhooks**, subscribe to the `email.*` events to stream delivery and engagement to your own systems in real time. See [Event types](https://www.sendping.co/docs/webhooks/events).

## Aggregate stats endpoint

For a single rolled-up view, call `GET /campaigns/:id/stats`. It returns the campaign’s aggregate open and click counts (with the underlying delivered/sent totals, plus replies, bounces and complaints) and the top clicked links — the same numbers shown on the dashboard, fetched in one request.

**Node.js**

```js
import { SendPing } from 'sendping';

const mb = new SendPing('mb_xxxxxxxxx');

const { data, error } = await mb.campaigns.stats('CAMPAIGN_ID');
console.log({ data, error });
```

**Ruby**

```ruby
require "sendping"

SendPing.api_key = "mb_xxxxxxxxx"

SendPing::Campaigns.stats("CAMPAIGN_ID")
```

**PHP**

```php
<?php
require 'vendor/autoload.php';

use SendPing\SendPing;

$sendping = SendPing::client('mb_xxxxxxxxx');

$sendping->campaigns->stats('CAMPAIGN_ID');
```

**Python**

```python
import sendping

sendping.api_key = "mb_xxxxxxxxx"

sendping.Campaigns.stats("CAMPAIGN_ID")
```

**Go**

```go
import "github.com/shekhu10/sendping-sdks/sendping-go"

client := sendping.NewClient("mb_xxxxxxxxx")

stats, err := client.Campaigns.Stats("CAMPAIGN_ID")
```

**Rust**

```rust
use sendping::SendPing;

let mb = SendPing::new("mb_xxxxxxxxx");

let _stats = mb.campaigns.stats("CAMPAIGN_ID").await?;
```

**Java**

```java
import co.sendping.SendPing;
import co.sendping.SendPingResponse;

SendPing sendping = new SendPing("mb_xxxxxxxxx");

SendPingResponse response = sendping.campaigns().stats("CAMPAIGN_ID");
```

**.NET**

```csharp
using SendPing;

ISendPing sendping = SendPingClient.Create("mb_xxxxxxxxx");

var resp = await sendping.CampaignRetrieveStatsAsync("CAMPAIGN_ID");
```

**cURL**

```bash
curl -X GET 'https://www.sendping.co/api/campaigns/CAMPAIGN_ID/stats' \
  -H 'Authorization: Bearer mb_xxxxxxxxx'
```

**CLI**

```bash
sendping campaigns stats CAMPAIGN_ID
```

It returns a `campaign_stats` object. The counts are `total` (one per recipient copy sent), `delivered`, `opened`, `clicked`, `replied`, `bounced` and `complained` — `opened`, `clicked` and `replied` are unique per recipient, not raw event counts. The `rates` object carries `delivery`, `open`, `click`, `reply`, `bounce` and `complaint` as percentages: `open`, `click` and `reply` are measured against `delivered` (falling back to `total` when no delivery events were recorded), while `delivery`, `bounce` and `complaint` are measured against `total`. Every rate is a percentage rounded to **one decimal place** (so `98.1`, not `98.06`). `links` lists the top clicked tracked links as `{ url, clicks }`, ordered by clicks descending, and is capped at the **50** most-clicked links.

```json
{
  "object": "campaign_stats",
  "campaign_id": "8f5c2a1e-7b3d-4f9a-9c12-2e6d4a7b8c90",
  "total": 1240,
  "delivered": 1216,
  "opened": 498,
  "clicked": 96,
  "replied": 12,
  "bounced": 18,
  "complained": 1,
  "rates": {
    "delivery": 98.1,
    "open": 41,
    "click": 7.9,
    "reply": 1,
    "bounce": 1.5,
    "complaint": 0.1
  },
  "links": [
    { "url": "https://yourdomain.com/whats-new", "clicks": 74 },
    { "url": "https://yourdomain.com/pricing", "clicks": 22 }
  ]
}
```

The same fields (without `object`, `campaign_id` and `links`) are returned inline as the `statistics` field of [`GET /campaigns/:id`](https://www.sendping.co/docs/api/campaigns-get). Errors: `not_found` if the campaign does not exist or is not yours.

Because bounces and complaints feed account-wide suppression, contacts that hard-bounce or complain are automatically excluded from subsequent campaigns — keeping your sender reputation healthy without manual list hygiene.
