Skip to main content
GET
/
api
/
email
/
accounts
/
warmup-analytics
Get warmup analytics for all email accounts
curl --request GET \
  --url https://app.puffle.ai/api/email/accounts/warmup-analytics \
  --header 'Authorization: Bearer <token>'
{
  "analytics": {
    "aaaa1111-1111-1111-1111-111111111111": {
      "email": "[email protected]",
      "total_sent": 142,
      "total_received": 138,
      "health_score": 98,
      "health_score_label": "Excellent",
      "warmup_reputation": 72,
      "instantly_warmup_status": 1,
      "daily_data": [
        {
          "date": "2026-04-20",
          "sent": 12,
          "received": 11
        },
        {
          "date": "2026-04-21",
          "sent": 15,
          "received": 14
        }
      ]
    },
    "bbbb2222-2222-2222-2222-222222222222": {
      "email": "[email protected]",
      "total_sent": 205,
      "total_received": 199,
      "health_score": 100,
      "health_score_label": "Excellent",
      "warmup_reputation": 92,
      "instantly_warmup_status": 1,
      "daily_data": []
    }
  },
  "transitions": {
    "bbbb2222-2222-2222-2222-222222222222": {
      "warmup_status": "active",
      "status": "active"
    }
  }
}

Overview

Separated from GET /api/email/accounts so the UI can render the account list immediately and lazy-load the slower warmup data without blocking the page. Two things happen in one pass:
  1. Analytics fetch. For every account with warmup_status ∈ {warming, warmed, active} and a set email_address, we pull total_sent, total_received, health_score, warmup_reputation (0–100), and daily_data from Instantly. Fetches are serialised to avoid Instantly’s rate limits.
  2. Auto-transitions. Two transitions are applied inline and reflected in the transitions map:
    • Ban / suspend: if Instantly’s instantly_warmup_status is -1 (banned), -2 (spam unknown), or -3 (suspended), we set our warmup_status to banned/suspended and status to error.
    • Graduate: if a warming account has warmup_reputation ≥ 90 AND total_sent ≥ 200, we flip both warmup_status and status to active.

AI agent notes

Warmup is a multi-day process. warmup_reputation climbs slowly and the daily counts update incrementally. Expect graduation (warmingactive) in roughly 2–3 weeks for a new inbox, not hours. Polling this endpoint more than once every few minutes is wasteful.Reading the response:
  • analytics is keyed by accounts.id — not by email address.
  • Accounts that failed their Instantly fetch are silently omitted from analytics. Check the account list for which IDs you expected.
  • transitions is a diff, not a snapshot. It only contains entries for rows that changed status on this call — empty object {} means nothing flipped.
Gate campaigns on status: "active", not warmup_status. An active account is send-eligible; a warming account blocks campaign emails.When an account turns banned: alert the user. There is no retry path — the user must create a new email address on the same domain (or a new domain).Chains with: retryAccountWarmup for stalled pending accounts, launchCampaign once all senders are active.

Authorizations

Authorization
string
header
required

Bearer authentication header of the form Bearer <token>, where <token> is your auth token.

Response

Per-account warmup analytics plus any status transitions applied on this call.

analytics
object
required

Map of accounts.id → analytics payload. Only accounts with warmup_status ∈ {warming, warmed, active} and a set email_address are included. Accounts that failed to fetch (Instantly-side error) are silently omitted.

transitions
object
required

Map of accounts.id → the status change applied during this call. Present only when a graduation or ban/suspend actually flipped the row — empty object {} if nothing changed. Use this to refresh UI state without re-reading accounts.