SendSetsDocs
Endpoint reference

Analytics and audit

Read dashboard, deliverability, warmup, campaign, account, and usage analytics, plus your organization's audit trail.

The analytics endpoints expose the same rollups that power the dashboard: an org-wide overview, deliverability posture, warmup progress, per-campaign performance (with daily and hourly breakdowns and side-by-side comparison), mailbox health, and account usage. The audit endpoint returns your organization's activity trail ("who did what, when, from where"). All of these are read-only.

Every analytics route shares one auth gate: Scope READ_ANALYTICS · Org permission view_analytics. The audit route uses the same org permission with a dedicated scope. See permissions for the scope reference and authentication for how to present credentials.

Dates are parsed as YYYY-MM-DD unless noted. Errors follow the standard {error, message, code, request_id} envelope documented in error codes.

Get dashboard analytics

GET /analytics/dashboard

Returns the main dashboard overview for the active organization: aggregate stats, recent activity, top campaigns, account health, and a daily trend series. Sent and engagement totals include email steps only; completed wait and action steps do not inflate them. Account-health buckets are mutually exclusive and use the most severe current connection, sync, or warmup-reputation state. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

ParameterInTypeDescription
periodquerystringOne of 7d, 30d, 90d, measured as UTC calendar days including today. Defaults to 7d; any other value falls back to 7d.

Response

{
  "period": "7d",
  "overall_stats": {
    "total_emails_sent": 1240,
    "total_opens": 612,
    "machine_opens": 88,
    "total_clicks": 143,
    "machine_clicks": 6,
    "total_replies": 57,
    "positive_replies": 18,
    "total_bounces": 9,
    "total_delivered": 1231,
    "open_rate": 49.35,
    "click_rate": 11.53,
    "reply_rate": 4.6,
    "bounce_rate": 0.73,
    "active_campaigns": 4,
    "active_accounts": 12
  },
  "recent_activity": [
    {
      "type": "replied",
      "campaign_id": "b1f2c3d4-0000-0000-0000-000000000001",
      "campaign_name": "Q2 outbound",
      "contact_email": "lead@example.com",
      "contact_id": "c0ffee00-0000-0000-0000-000000000002",
      "timestamp": "2026-06-11T14:02:11Z"
    }
  ],
  "top_campaigns": [
    {
      "campaign_id": "b1f2c3d4-0000-0000-0000-000000000001",
      "name": "Q2 outbound",
      "status": "active",
      "emails_sent": 820,
      "replies": 41,
      "positive_replies": 13,
      "open_rate": 51.2,
      "click_rate": 12.1,
      "reply_rate": 5.0
    }
  ],
  "account_health": {
    "total_accounts": 12,
    "healthy_accounts": 10,
    "warning_accounts": 1,
    "error_accounts": 1
  },
  "daily_trend": [
    { "date": "2026-06-05", "sent": 160, "opens": 79, "clicks": 18, "replies": 7 }
  ]
}

Get deliverability dashboard

GET /analytics/deliverability

Returns the organization's deliverability posture for a time window: bounce, complaint, open, click, and reply counts and rates, suppression and dead-letter pressure, reply-intent breakdown, seed inbox-placement (overall and per recipient provider), warmup-derived placement per recipient domain, an overall health band and a 0-100 composite score (both from the documented thresholds), a daily timeseries, and per-mailbox and per-campaign breakdowns. Requires an organization context. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

ParameterInTypeDescription
fromquerystringWindow start as an RFC 3339 timestamp. Defaults to 7 days ago (UTC).
toquerystringWindow end as an RFC 3339 timestamp. Defaults to now (UTC).

Response

{
  "from": "2026-06-05T00:00:00Z",
  "to": "2026-06-12T00:00:00Z",
  "events_total": 1380,
  "bounce_count": 9,
  "complaint_count": 1,
  "unsubscribe_count": 4,
  "reply_count": 57,
  "open_count": 612,
  "click_count": 143,
  "suppressed_recipients": 21,
  "dlq_pending": 0,
  "intent_positive": 18,
  "intent_negative": 6,
  "intent_out_of_office": 11,
  "intent_question": 9,
  "intent_neutral": 13,
  "intent_automated": 22,
  "emails_sent": 1240,
  "bounce_rate": 0.73,
  "complaint_rate": 0.08,
  "open_rate": 49.35,
  "click_rate": 11.53,
  "reply_rate": 4.6,
  "spam_placement_rate": 6.5,
  "inbox_placement_rate": 93.5,
  "placement_samples": 40,
  "band": "healthy",
  "score": 91,
  "timeseries": [
    {
      "date": "2026-06-05",
      "sent": 160,
      "bounces": 1,
      "complaints": 0,
      "opens": 79,
      "clicks": 18,
      "replies": 7,
      "unsubscribes": 1
    }
  ],
  "by_mailbox": [
    {
      "email_account_id": "a0a1a2a3-0000-0000-0000-000000000003",
      "email": "sales@yourdomain.com",
      "sent": 420,
      "bounces": 2,
      "complaints": 0,
      "bounce_rate": 0.48,
      "complaint_rate": 0.0,
      "band": "healthy"
    }
  ],
  "by_campaign": [
    {
      "campaign_id": "b1f2c3d4-0000-0000-0000-000000000001",
      "name": "Q2 outbound",
      "sent": 820,
      "bounces": 5,
      "complaints": 1,
      "bounce_rate": 0.61,
      "complaint_rate": 0.12,
      "band": "watch"
    }
  ],
  "by_provider": [
    {
      "provider": "gmail",
      "samples": 24,
      "inbox": 22,
      "promotions": 1,
      "spam": 1,
      "other": 0,
      "inbox_rate": 91.67,
      "spam_rate": 4.17
    }
  ],
  "warmup_placement": [
    {
      "provider": "outlook",
      "domain": "outlook.com",
      "delivered": 132,
      "spam": 6,
      "inbox_rate": 95.45,
      "spam_rate": 4.55
    }
  ]
}

The intent_* counters and reply_count are two different things and do not add up to each other. reply_count counts reply deliverability events, which are the ones a provider or your own integration reports to record an event. The intent_* counters are the replies SendSets classified as they arrived in a connected mailbox, one per reply, including the machine ones: a vacation notice lands in intent_out_of_office and an autoresponder, ticket acknowledgement or bounce notice in intent_automated. A workspace that reports no reply events sees reply_count at zero with intents counted normally.

spam_placement_rate and inbox_placement_rate are omitted when there are no seed samples in the window. by_provider rolls the same seed samples up per recipient provider; warmup_placement is the continuous warmup signal per recipient domain, where delivered counts verified warmup arrivals and spam the subset the recipient's provider filed into junk. score starts at 100 and subtracts saturating penalties for bounce rate (up to 40 points, maxed at 10%), complaint rate (up to 30 points, maxed at 0.30%), and spam placement (up to 40 points, maxed at 40%).

Get warmup analytics

GET /analytics/warmup

Returns warmup send and reply statistics over a date range, with a summary and per-day series. Optionally scoped to a single mailbox. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

Results are scoped to the selected workspace, so every member with access sees the same mailbox history. Workspace-wide results combine all mailboxes into one row per date. average_daily uses active days (dates with a warmup statistics row) as its denominator. target_progress is total sends divided by total planned target volume across those active days and may exceed 100 when sends beat the plan.

ParameterInTypeDescription
fromquerystringRequired. Range start (YYYY-MM-DD).
toquerystringRequired. Range end (YYYY-MM-DD).
email_idquerystring (uuid)Optional. Limit to one email account. Invalid UUIDs are ignored.

Response

{
  "email_account_id": "a0a1a2a3-0000-0000-0000-000000000003",
  "email": "",
  "date_range": {
    "from": "2026-06-01T00:00:00Z",
    "to": "2026-06-12T00:00:00Z"
  },
  "summary": {
    "total_sent": 210,
    "total_replied": 84,
    "average_daily": 17.5,
    "reply_rate": 40.0,
    "target_progress": 87.5,
    "days_active": 12
  },
  "daily_stats": [
    { "date": "2026-06-01", "emails_sent": 12, "emails_replied": 5, "target_volume": 12 }
  ]
}

email_account_id is the zero UUID when no email_id filter is supplied.

Get campaign analytics

GET /analytics/campaigns/:id

Returns a single campaign's performance summary plus per-sequence-step stats. total_contacts counts enrolled leads, and emails_pending is the remaining planned email-step sends across those leads. The campaign must belong to the selected workspace. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

ParameterInTypeDescription
idpathstring (uuid)Campaign id.

Response

{
  "campaign_id": "b1f2c3d4-0000-0000-0000-000000000001",
  "name": "Q2 outbound",
  "status": "active",
  "date_range": { "from": "0001-01-01T00:00:00Z", "to": "0001-01-01T00:00:00Z" },
  "summary": {
    "total_contacts": 500,
    "emails_sent": 820,
    "emails_pending": 60,
    "unique_opens": 410,
    "machine_opens": 52,
    "unique_clicks": 99,
    "machine_clicks": 4,
    "replies": 41,
    "bounces": 5,
    "unsubscribes": 3,
    "open_rate": 50.0,
    "click_rate": 12.07,
    "reply_rate": 5.0,
    "bounce_rate": 0.61
  },
  "steps": [
    {
      "step_id": "5e9e0001-0000-0000-0000-000000000004",
      "name": "Intro",
      "position": 1,
      "emails_sent": 500,
      "opens": 260,
      "machine_opens": 31,
      "clicks": 61,
      "machine_clicks": 2,
      "replies": 28,
      "bounces": 3,
      "open_rate": 52.0,
      "click_rate": 12.2,
      "reply_rate": 5.6,
      "bounce_rate": 0.6
    }
  ],
  "engagement": {
    "countries": [{ "key": "US", "opens": 120, "clicks": 31 }, { "key": "DE", "opens": 44, "clicks": 9 }],
    "clients": [{ "key": "Gmail", "opens": 98, "clicks": 20 }, { "key": "Outlook", "opens": 51, "clicks": 12 }],
    "devices": [{ "key": "desktop", "opens": 140, "clicks": 37 }, { "key": "mobile", "opens": 24, "clicks": 3 }]
  }
}

engagement groups the campaign's human opens and clicks by country (ISO code), mail client or browser, and device type (desktop, mobile, tablet), counting distinct contacts per bucket; a click counts as an open. Each list holds the busiest eight; an empty key is unknown. Country needs the GeoLite2 database on the consumer, otherwise every country row is unknown.

machine_opens is the subset of unique_opens from automated fetchers (UA-less clients, known scanner networks, and opens inside the instance's automated-open window, which starts when the step is dispatched to a worker). The stripped WebKit signature shared by Apple MPP and Outlook image loading is an Apple-prefetch signal only inside that window; by itself it does not prove automation. Human opens are unique_opens minus machine_opens. machine_clicks counts the contacts whose only clicks on a step were automated (a security gateway walking the links); those are not part of unique_clicks or total_clicks, which only ever count a person's click.

Each entry in steps carries that step's own rates: open_rate, click_rate, reply_rate and bounce_rate are percentages of that step's emails_sent, not of the campaign's, so a follow-up that reached a tenth of the contacts still compares against the first touch. They are 0 while the step has sent nothing. machine_opens and machine_clicks follow the same rules as their summary counterparts, scoped to the step.

Get campaign daily stats

GET /analytics/campaigns/:id/daily

Returns per-day send, open, click, and reply counts for one campaign over a date range. The campaign must belong to the caller. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

ParameterInTypeDescription
idpathstring (uuid)Campaign id.
fromquerystringRequired. Range start (YYYY-MM-DD).
toquerystringRequired. Range end (YYYY-MM-DD).

Response

The series is returned under a data envelope.

{
  "data": [
    { "date": "2026-06-05", "sent": 120, "opens": 61, "clicks": 14, "replies": 6 },
    { "date": "2026-06-06", "sent": 110, "opens": 58, "clicks": 12, "replies": 5 }
  ]
}

Get campaign hourly stats

GET /analytics/campaigns/:id/hourly

Returns per-hour stats for one campaign on a single day. The campaign must belong to the caller. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

ParameterInTypeDescription
idpathstring (uuid)Campaign id.
datequerystringDay to report (YYYY-MM-DD). Defaults to today.

Response

The series is returned under a data envelope, with the resolved date echoed back. Each item's hour is 0-23.

{
  "data": [
    { "hour": 9, "sent": 22, "opens": 11, "clicks": 3, "replies": 1 },
    { "hour": 10, "sent": 30, "opens": 16, "clicks": 4, "replies": 2 }
  ],
  "date": "2026-06-11"
}

Compare campaigns

GET /analytics/campaigns/compare

Returns side-by-side performance for up to 10 campaigns over a date range. Every requested campaign must belong to the caller. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

ParameterInTypeDescription
idsquerystringRequired. Comma-separated campaign UUIDs. Invalid entries are dropped; the list is capped at 10. At least one valid id is required.
fromquerystringRequired. Range start (YYYY-MM-DD).
toquerystringRequired. Range end (YYYY-MM-DD).

Response

{
  "campaigns": [
    {
      "campaign_id": "b1f2c3d4-0000-0000-0000-000000000001",
      "name": "Q2 outbound",
      "status": "active",
      "emails_sent": 820,
      "open_rate": 50.0,
      "click_rate": 12.07,
      "reply_rate": 5.0,
      "bounce_rate": 0.61
    },
    {
      "campaign_id": "b1f2c3d4-0000-0000-0000-000000000005",
      "name": "Reactivation",
      "status": "paused",
      "emails_sent": 410,
      "open_rate": 44.1,
      "click_rate": 9.8,
      "reply_rate": 3.4,
      "bounce_rate": 1.2
    }
  ],
  "period": {
    "from": "2026-06-01T00:00:00Z",
    "to": "2026-06-12T00:00:00Z"
  }
}

List account statuses

GET /analytics/accounts

Returns the health and usage status of every email account in the selected workspace. The request fails if any account's status cannot be calculated, so clients never receive a silently incomplete list. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

Response

The list is returned under a data envelope (no cursor; all of the caller's accounts are included). Each item has the same shape as get account status.

{
  "data": [
    {
      "id": "a0a1a2a3-0000-0000-0000-000000000003",
      "email": "sales@yourdomain.com",
      "provider": "google",
      "status": "active",
      "last_synced_at": "2026-06-12T08:00:00Z",
      "health": { "status": "healthy", "score": 100, "issues": [] },
      "errors": [],
      "daily_usage": {
        "date": "2026-06-12",
        "campaign_sent": 18,
        "campaign_limit": 50,
        "warmup_sent": 22,
        "warmup_limit": 40
      },
      "in_campaign": true
    }
  ]
}

Get account status

GET /analytics/accounts/:id

Returns the detailed status for one email account: a combined health score (folding in warmup-pool reputation), active errors, today's UTC usage, warmup status, and warmup-pool health. The account must belong to the selected workspace. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

ParameterInTypeDescription
idpathstring (uuid)Email account id.

Response

{
  "id": "a0a1a2a3-0000-0000-0000-000000000003",
  "email": "sales@yourdomain.com",
  "provider": "google",
  "status": "active",
  "last_synced_at": "2026-06-12T08:00:00Z",
  "health": {
    "status": "warning",
    "score": 90,
    "issues": ["Warmup reputation needs watching"]
  },
  "errors": [
    {
      "id": "e1e1e1e1-0000-0000-0000-000000000006",
      "error_code": "IMAP_AUTH",
      "severity": "WARNING",
      "title": "Mailbox reconnect recommended",
      "message": "Token nearing expiry",
      "created_at": "2026-06-11T22:14:00Z"
    }
  ],
  "daily_usage": {
    "date": "2026-06-12",
    "campaign_sent": 18,
    "campaign_limit": 50,
    "warmup_sent": 22,
    "warmup_limit": 40
  },
  "warmup_status": {
    "enabled": true,
    "paused": false,
    "started_at": "2026-05-20T00:00:00Z",
    "current_volume": 22,
    "target_volume": 33,
    "max_volume": 40,
    "reply_rate": 35,
    "days_active": 23
  },
  "warmup_health": {
    "state": "watch",
    "score": 78,
    "spam_score": 0,
    "evaluated_at": "2026-06-12T06:00:00Z"
  },
  "in_campaign": true
}

warmup_status is present only when warmup has ever been enabled; warmup_health is present only when the mailbox is in a warmup pool. in_campaign reports whether the mailbox currently backs a live campaign.

Get usage overview

GET /analytics/usage

Returns account, campaign, and contact usage counters for the selected workspace. campaigns.emails_sent counts sent email steps in the requested period; wait and action steps do not count. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

The api object is a compatibility placeholder. For real API-key request totals, error rates, latency, and endpoint breakdowns use GET /api-keys/usage/summary and the API-key analytics endpoints.

ParameterInTypeDescription
periodquerystringday counts from 00:00 UTC today, week is a rolling seven-day window, and month is a rolling one-month window. Defaults to day; any other value falls back to day.

Response

{
  "user_id": "11111111-0000-0000-0000-000000000007",
  "period": "day",
  "email_accounts": { "total": 12, "active": 11, "in_warmup": 8, "with_errors": 1 },
  "campaigns": { "total": 9, "active": 4, "paused": 2, "draft": 3, "emails_sent": 124 },
  "contacts": { "total": 8200, "subscribed": 8050, "added_today": 120 },
  "api": { "total_calls": 0, "daily_limit": 50000, "top_endpoints": [] }
}

List audit logs

GET /audit-logs

Returns the organization-wide activity trail for the caller's current organization ("who did what, when, from where"). The organization is always taken from the session and never from a client parameter, so one organization can never read another's trail. Auth: Scope READ_AUDIT_LOGS · Org permission view_analytics.

ParameterInTypeDescription
limitqueryintPage size. Defaults to 50; must be between 10 and 200 or a 400 is returned.
cursorquerystring (uuid)Opaque cursor from pagination.next_cursor. Invalid cursors return 400.
actor_idquerystring (uuid)Filter to a single acting member.
entity_idquerystring (uuid)Filter to a single entity.
entity_typequerystringFilter by entity type (for example campaign, contact, email_account, api_key, webhook).
actionquerystringFilter by action (for example create, update, delete, send, revoke).
datequerystringSingle-day filter (YYYY-MM-DD), expanded to that whole UTC day.
start_datequerystringRange start. RFC 3339 or YYYY-MM-DD. Overrides date.
end_datequerystringRange end. RFC 3339 or YYYY-MM-DD. Overrides date.

Response

A data plus pagination envelope with an opaque cursor. actor is null when the acting user has since been deleted; entity_id, changes, and metadata are omitted when empty.

{
  "data": [
    {
      "id": "9c9c9c9c-0000-0000-0000-000000000008",
      "org_id": "0a0a0a0a-0000-0000-0000-000000000009",
      "user_id": "11111111-0000-0000-0000-000000000007",
      "actor": {
        "id": "11111111-0000-0000-0000-000000000007",
        "first_name": "Ada",
        "last_name": "Lovelace",
        "email": "ada@yourdomain.com"
      },
      "action_date": "2026-06-12T08:14:00Z",
      "action": "update",
      "entity_type": "campaign",
      "entity_id": "b1f2c3d4-0000-0000-0000-000000000001",
      "ip_address": "203.0.113.10",
      "user_agent": "Mozilla/5.0",
      "changes": { "status": "paused" },
      "timestamp": "2026-06-12T08:14:00Z"
    }
  ],
  "pagination": {
    "next_cursor": "c1_b3BhcXVlLWN1cnNvcg",
    "has_more": true
  }
}

Secret values (API key material, webhook secrets, passwords) are never recorded in changes or metadata; the trail records only that a field changed.

On this page