Resources

Marketing analytics

Measure your marketing email — totals, rates, a daily curve, deliverability health, and who is engaging.

The analytics endpoints turn your marketing sends and their provider events into three read-only views: an account-wide overview, a per-campaign broadcast report, and a per-contact engagement score. They aggregate the same messages and events you already see under events and broadcasts — nothing new is tracked, it is read back as rates and trends. The endpoints live under /v1/analytics.

Analytics cover marketing email only — broadcasts and automations, which both send as message_type: marketing. One-off transactional sends via POST /v1/send are not counted in the overview. Everything here is email; there is no other send channel.

How rates are computed

Every endpoint returns a totals object of distinct-message counts and a rates object of fractions in 0..1 (so 0.3547 means 35.47%, not 35). Rates are rounded to four decimal places and clamped to [0, 1] — open, click, and delivery events are independent tallies that can briefly race, so a numerator never renders above its denominator.

The denominator is the count of messages that actually reached the provider. A message counts as sent once its status is sent, delivered, bounced, or complained — the provider accepted it. Messages that were never handed off (queued, scheduled, failed, cancelled) and sandbox test_mode traffic are excluded from the account-wide population, so they never inflate or deflate a rate.

Rate definitions
RateNumerator ÷ denominator
delivery_ratedelivered ÷ sent
open_rateopened ÷ delivered (falls back to sent when no deliveries are recorded)
click_rateclicked ÷ delivered (same sent fallback)
click_to_open_rateclicked ÷ opened
bounce_ratebounced ÷ sent
complaint_ratecomplained ÷ sent
unsubscribe_rateunsubscribed ÷ delivered (same sent fallback)
Counts in totals are distinct messages, not raw events — a recipient who opens an email five times counts once toward opened. unsubscribed is the number of email addresses that moved to an unsubscribed state in the window, so it is tracked separately from the opens-and-clicks tallies.

Account overview

GET /v1/analytics/overview aggregates all marketing traffic for your account over the last days and returns the headline numbers: totals, derived rates, a daily timeseries, a deliverability health summary, and your top_campaigns.

Query parameters

daysintegerOptional
Lookback window in calendar days (UTC), ending today inclusive. Omitted or ≤0 defaults to 30; values above 365 are clamped to 365. The same window drives the headline totals and the daily timeseries, so the chart sums match the totals.

The response:

{
  "days": 30,
  "totals": {
    "sent": 18420,
    "delivered": 18100,
    "opened": 6420,
    "clicked": 1840,
    "bounced": 220,
    "complained": 8,
    "unsubscribed": 42
  },
  "rates": {
    "delivery_rate": 0.9826,
    "open_rate": 0.3547,
    "click_rate": 0.1017,
    "click_to_open_rate": 0.2866,
    "bounce_rate": 0.0119,
    "complaint_rate": 0.0004,
    "unsubscribe_rate": 0.0023
  },
  "timeseries": [
    { "date": "2026-06-14", "sent": 640, "opened": 228, "clicked": 65 }
  ],
  "deliverability": {
    "bounce_rate": 0.0119,
    "complaint_rate": 0.0004,
    "health": "good",
    "notes": []
  },
  "top_campaigns": [
    {
      "id": "bc_a1b2c3",
      "name": "October newsletter",
      "sent_at": "2026-06-14T10:00:00Z",
      "sent": 1840,
      "delivered": 1798,
      "opened": 642,
      "clicked": 190,
      "open_rate": 0.3571,
      "click_rate": 0.1057
    }
  ]
}

Daily timeseries

timeseries is a dense, zero-filled array of one point per day, oldest to newest, over the exact same window as the totals. Each point has a UTC date (YYYY-MM-DD) and the sent, opened, and clicked counts for that day. Days with no activity are still present with zeros, so you can plot the curve without gaps.

Deliverability health

deliverability echoes bounce_rate and complaint_rate and rolls them into a single health status with human-readable notes. The thresholds mirror common ESP guidance — keep complaints under 0.1% and bounces under a few percent to protect your sender reputation.

Deliverability health
HealthWhen it fires
goodComplaints at or below 0.1% and bounces at or below 2%. No notes.
warningBounce rate is elevated — above 2%, or above 5%. A note explains which.
criticalSpam complaint rate is above 0.1% — the note recommends pausing and cleaning your list.
A critical complaint rate is the one to act on immediately. Mailbox providers throttle or block senders whose complaint rate stays above 0.1%, so pause the send, prune unengaged addresses, and review what triggered the complaints before you continue.

Top campaigns

top_campaigns lists your most recent sent broadcasts in the window (up to 8), newest first, each with its own sent, delivered, opened, clicked counts and an open_rate / click_rate. sent_atis the broadcast's completion time (RFC 3339, UTC) and may be omitted for a campaign that has no recorded completion. Use the id to pull the full per-campaign report below.

Broadcast report

GET /v1/analytics/broadcasts/{id} drills into a single campaign. It returns the same shape of totals and rates as the overview, plus a per-campaign timeseries and a top_links click map. Because the report is scoped by broadcast_id, it includes that broadcast's own data even for a sandbox test broadcast.

Pass ?days=N to size the daily timeseries window; the headline totals and ratescover the broadcast's full lifetime regardless of the window.

{
  "broadcast_id": "bc_a1b2c3",
  "totals": { "sent": 1840, "delivered": 1798, "opened": 642, "clicked": 190 },
  "rates": {
    "delivery_rate": 0.9772,
    "open_rate": 0.3571,
    "click_rate": 0.1057,
    "click_to_open_rate": 0.2960,
    "bounce_rate": 0.0168,
    "complaint_rate": 0.0011,
    "unsubscribe_rate": 0
  },
  "timeseries": [
    { "date": "2026-06-14", "sent": 1840, "opened": 642, "clicked": 190 }
  ],
  "top_links": [
    { "url": "https://acme.com/launch", "clicks": 120, "unique_clicks": 98 }
  ]
}

The click map

top_links ranks the URLs clicked in this campaign (up to 20), most clicks first. Each entry carries the url, total clicks, and unique_clicks (distinct messages that clicked it). It is read from the link recorded on each click event.

The click map and every click-based rate depend on open and click tracking being enabled for your sending domain. Sendara records a click only when the provider rewrites your links and reports the click-through; with tracking off, click events never arrive, so top_links comes back empty and clicked stays at 0 even though the campaign delivered. See events for how open and click events are produced.

Contact engagement

GET /v1/analytics/contacts/{id}returns one contact's lifetime marketing engagement: how many messages they were sent, how many they opened and clicked, their open_rate / click_rate, when they last_engaged_at, and a computed score (0..100) with a statusbucket. The contact must belong to your account; matching is by the contact's email against your marketing sends.

{
  "contact_id": "ct_a1b2c3",
  "sent": 24,
  "opened": 18,
  "clicked": 6,
  "open_rate": 0.75,
  "click_rate": 0.25,
  "last_engaged_at": "2026-06-14T10:04:30Z",
  "score": 82,
  "status": "engaged"
}

The engagement score

scoreblends the contact's open and click rates — weighting clicks more heavily — into a 0..100 number, then decays it by how long since they last engaged. A click-heavy contact who engaged recently scores high; a once-active contact who has gone quiet decays toward zero. last_engaged_at is the most recent open or click (RFC 3339, UTC), or absent if they have never engaged.

Engagement status buckets
StatusMeaning
engagedOpened or clicked within the last 30 days. Full score, no decay.
coolingLast engaged 30–90 days ago. Score is decayed (×0.7).
dormantLast engaged more than 90 days ago. Score is decayed more heavily (×0.4).
never_engagedReceived marketing email but never opened or clicked. score is 0.
no_sendsNo marketing messages have been sent to this contact yet. score is 0.
Engagement scores pair naturally with segments: pull dormant and never-engaged contacts into a list to suppress or re-engage them, and keep your actively engaged audience clean to hold your deliverability health at good.

Errors

Analytics endpoints use the standard error envelope.

Analytics error codes
CodeStatusWhen it fires
invalid_request400A required path id (broadcast or contact) is missing or empty.
unauthorized401Missing or invalid API key.
forbidden403Key lacks the read scope these endpoints require.
An unknown broadcast or contact id is not an error — the report simply comes back with zeroed totals (and a no_sends status for a contact), because there is no matching marketing traffic to aggregate.

Scopes

All three analytics endpoints are read-only and require a key with the read scope. There is nothing to write here — analytics only read back the messages and events your marketing sends already produced. See Authentication for how scopes work.