Campaigns

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` 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.
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.
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` 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.

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.

import { SendPing } from 'sendping';

const mb = new SendPing('mb_xxxxxxxxx');

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

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.

{
  "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`. 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.