MisarMisar Docs
MisarMailMisar.BlogMisarReachMisarPostMisar.DevMisarSEOMisar PlatformMisar SSO
API Reference

IP Warmup

Track and manage IP address warmup schedules to build sender reputation gradually

IP warmup is the process of gradually increasing sending volume from a new IP address so that receiving mail servers build trust in your sender reputation. Starting at low volumes and ramping up over 30–60 days significantly reduces the risk of landing in spam folders.

Requires active session (dashboard only).

Why warmup matters

New IP addresses have no sending history. If you immediately send 100,000 emails from a fresh IP, inbox providers will likely block or bulk-folder your messages. A structured warmup schedule builds a positive reputation signal over time.

Typical warmup ramp:

DayMax daily volume
1–250
3–5200
6–7500
8–142,000
15–215,000
22–3010,000
30+No limit (fully warmed)

Elapsed time alone does not prove your mail reaches the inbox. Warmup completion is gated on measured inbox placement from a real seed-inbox network (see Placement measurement). An IP that has finished its ramp but is measured landing in spam is demoted back to warming rather than reported as fully warmed.

Get warmup report

GET/api/warmup

Returns the current warmup state for all tracked IPs plus a pool-level capacity summary. progress_pct is the percentage of today's sending window already used.

Response fields

successboolean

true when the request succeeded.

ipsArray<object>

Tracked IPs. Each entry includes ip_address, domain, pool_name, status, daily_limit_today, sent_today, and progress_pct.

poolsobject

Per-pool capacity. Each pool reports pool, remaining, and capacity.

placementobject

Real inbox-vs-spam measurement. seedsConfigured is the number of active seed inboxes; configured is true when at least one exists. domains is an array of per sending-domain × provider rollups (provider: "all" is the cross-provider aggregate) with probes_measured, landed_inbox, landed_spam, missing, inbox_rate, spam_rate, last_probe_at, and window_start. When configured is false the array is empty and the UI shows "Not configured" rather than a fabricated 100%.

summaryobject

Aggregate counts: totalIPs, warming, active, paused, totalCapacityToday.

GET /api/warmup
{
  "success": true,
  "ips": [
    {
      "ip_address": "198.51.100.42",
      "domain": "mail.example.com",
      "pool_name": "transactional",
      "status": "warming",
      "daily_limit_today": 2000,
      "sent_today": 847,
      "progress_pct": 42
    }
  ],
  "pools": {
    "transactional": { "pool": "transactional", "remaining": 1153, "capacity": 2000 },
    "marketing":     { "pool": "marketing",     "remaining": 4200, "capacity": 5000 },
    "cold":          { "pool": "cold",           "remaining": 0,    "capacity": 0   }
  },
  "placement": {
    "seedsConfigured": 3,
    "configured": true,
    "domains": [
      {
        "sending_domain": "mail.example.com",
        "provider": "all",
        "pool_name": "transactional",
        "probes_measured": 42,
        "landed_inbox": 39,
        "landed_spam": 3,
        "missing": 0,
        "inbox_rate": 92.857,
        "spam_rate": 7.143,
        "last_probe_at": "2025-06-14T09:15:00Z",
        "window_start": "2025-05-31T09:15:00Z"
      }
    ]
  },
  "summary": {
    "totalIPs": 1,
    "warming": 1,
    "active": 0,
    "paused": 0,
    "totalCapacityToday": 7000
  }
}

Initialize an IP for warmup

POST/api/warmup

Register a new IP for warmup tracking. Returns HTTP 201 Created.

Request body

ipAddressstringbodyrequired

Valid IPv4 or IPv6 address.

domainstringbodyrequired

Sending domain associated with this IP.

poolNametransactional | marketing | coldbodyrequired

Which pool this IP belongs to.

targetDailyLimitintegerbodydefault: 50000

Target volume once fully warmed.

Response fields

successboolean

true when the IP was registered for warmup.

configobject

The warmup configuration: ip_address, domain, pool_name, status, daily_limit_today, started_at, target_daily_limit.

POST /api/warmup
Content-Type: application/json
{
  "success": true,
  "config": {
    "ip_address": "198.51.100.42",
    "domain": "mail.example.com",
    "pool_name": "transactional",
    "status": "warming",
    "daily_limit_today": 50,
    "started_at": "2025-06-14T00:00:00Z",
    "target_daily_limit": 50000
  }
}

Pause or resume an IP

PUT/api/warmup

Pause halts the warmup schedule; resume continues from the current day.

Request body

ipAddressstringbodyrequired

The IP to act on.

actionpause | resumebodyrequired

Pause halts the warmup schedule; resume continues from the current day.

Response fields

successboolean

true when the action was applied.

messagestring

Human-readable confirmation of the action.

{ "ipAddress": "198.51.100.42", "action": "pause" }
{ "success": true, "message": "IP warmup paused" }

Pausing an IP does not reset its warmup progress. Resuming picks up where the schedule left off. If an IP is paused for more than 7 days, consider restarting the warmup from day 1 to avoid reputation decay.

Remove an IP from warmup tracking

DELETE/api/warmup

Removes the IP from warmup tracking. The IP remains in its sending pool but is no longer subject to daily ramp limits. Use this when an IP has completed warmup and is operating at full capacity.

Query parameters

ipstringqueryrequired

The IP address to remove from warmup tracking.

DELETE /api/warmup?ip=198.51.100.42

Placement measurement

Elapsed-time warmup only proves you ramped volume — not that your mail reached the inbox. To measure real deliverability, MisarMail sends tokenized probe emails from every warming pool/domain to a network of seed inboxes (Gmail, Outlook, Yahoo, …), then IMAP-connects to each seed to classify where the probe landed: inbox, spam, or missing.

Results roll up per sending-domain × provider into warmup_domain_placement and feed measured_inbox_rate / measured_spam_rate / placement_samples back onto each IP. Completion is gated on this measurement:

  • ≥ 90% inbox rate over ≥ 12 samples (rolling 14-day window) → healthy, may complete.
  • 75–90% → at risk.
  • < 90% with enough samples → blocks completion; an already-active IP is demoted to warming.
  • No active seeds → placement is reported as configured: false ("Not configured") and warmup degrades gracefully to the legacy time-based schedule — it never reports a fabricated 100%.

Placement numbers only appear once at least one active seed inbox is configured and the placement cron has completed a send → check → rollup cycle.

Seed inboxes

Seed inboxes are the real mailboxes the placement checker IMAPs into. Manage your own seeds via these session-authenticated endpoints. The IMAP password is never returned — responses expose only has_password. Platform-shared seeds (shared: true) are visible to every tenant but editable only by service-role tooling.

List seed inboxes

GET/api/warmup/seeds

Returns your own seeds plus any platform-shared seeds you can measure against.

Response fields

successboolean

true when the request succeeded.

seedsArray<object>

Each seed: id, provider, email, imap_host, imap_port, imap_user, imap_tls, inbox_folder, spam_folders, is_active, last_checked_at, last_error, has_password, and shared.

GET /api/warmup/seeds
{
  "success": true,
  "seeds": [
    {
      "id": "3f9c…",
      "provider": "gmail",
      "email": "seed.mailbox@gmail.com",
      "imap_host": "imap.gmail.com",
      "imap_port": 993,
      "imap_user": "seed.mailbox@gmail.com",
      "imap_tls": true,
      "inbox_folder": "INBOX",
      "spam_folders": ["[Gmail]/Spam"],
      "is_active": true,
      "last_checked_at": "2025-06-14T09:20:00Z",
      "last_error": null,
      "has_password": true,
      "shared": false
    }
  ]
}

Add a seed inbox

POST/api/warmup/seeds

Register a seed inbox. Returns HTTP 201 Created.

Request body

providergmail | outlook | yahoo | proton | zoho | apple | otherbodyrequired

The mailbox provider.

emailstringbodyrequired

The seed mailbox address.

imapHoststringbodyrequired

IMAP server hostname (e.g. imap.gmail.com).

imapPassstringbodyrequired

IMAP password or app-specific password. Stored server-side; never returned.

imapUserstringbodydefault: email

IMAP login username. Defaults to email when omitted.

imapPortintegerbodydefault: 993

IMAP port (1–65535).

imapTlsbooleanbodydefault: true

Whether to connect over TLS.

inboxFolderstringbodydefault: INBOX

Folder treated as the inbox.

spamFoldersstring[]body

Folders treated as spam (first match wins). Defaults cover Gmail/Outlook/Yahoo naming.

isActivebooleanbodydefault: true

Whether this seed participates in measurement.

{
  "provider": "gmail",
  "email": "seed.mailbox@gmail.com",
  "imapHost": "imap.gmail.com",
  "imapPass": "app-specific-password",
  "spamFolders": ["[Gmail]/Spam"]
}
{ "success": true, "seed": { "id": "3f9c…", "has_password": true, "shared": false } }

Update a seed inbox

PUT/api/warmup/seeds

Update a seed you own, by id. Only the supplied fields change. Send imapPass blank or omit it to leave the stored password untouched.

Request body

idstringbodyrequired

The seed id to update.

imapPassstringbody

Supply a non-empty value only to rotate the password; omit to keep the existing one.

All fields accepted by POST (provider, email, imapHost, imapUser, imapPort, imapTls, inboxFolder, spamFolders, isActive) may also be sent to PUT to update them.

{ "id": "3f9c…", "isActive": false }
{ "success": true, "seed": { "id": "3f9c…", "is_active": false, "has_password": true } }

Remove a seed inbox

DELETE/api/warmup/seeds

Deletes a seed you own.

Query parameters

idstringqueryrequired

The seed id to remove.

DELETE /api/warmup/seeds?id=3f9c…
{ "success": true, "message": "Seed removed" }

IP pool types

PoolUse case
transactionalWelcome emails, password resets, order receipts — high-priority deliverability
marketingNewsletters, promotional campaigns, drip sequences
coldOutreach to contacts who have never interacted with your brand

Example: start a warmup

curl -X POST /api/warmup \
  -H "Content-Type: application/json" \
  -d '{
    "ipAddress": "198.51.100.42",
    "domain": "mail.example.com",
    "poolName": "transactional",
    "targetDailyLimit": 50000
  }'
const res = await fetch('/api/warmup', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    ipAddress: '198.51.100.42',
    domain: 'mail.example.com',
    poolName: 'transactional',
    targetDailyLimit: 50000,
  }),
});
const { config } = await res.json();
console.log(`Warmup started. Today's limit: ${config.daily_limit_today}`);