Email tools

Email Warmup API: Start, Pause and Monitor

How to run mailbox warm-up through the Outreach2day email warmup API: enable, pause and disable operations, idempotency keys, polling and warm-up states.

Outreach2dayPublished 6 min read
On this page

An email warmup API lets you start, pause and check mailbox warm-up from code instead of a dashboard. That matters once you manage mailboxes in batches: a new client's mailboxes should start warming the day they are ready, a mailbox with problems should stop, and your own tools should know which mailboxes are still warming.

This article covers the three warm-up endpoints of the Outreach2day API, with curl and Python examples. It assumes you have an API key and a workspace ID; the cold email API overview explains both. The curl examples use two shell variables:

export O2D="https://public.outreach2day.com"
export O2D_KEY="ot2d_your_key"

What Warm-Up Does and How Long It Takes

Warm-up sends a small, growing number of emails between a new mailbox and other mailboxes that open and reply to them. Mailbox providers see a sender with a normal history before any cold email goes out. The email warm-up service article explains the mechanism, and the warm-up tools comparison lists the options.

Plan for at least two weeks of warm-up before a new mailbox sends cold email (see how long to warm up before sending), and keep warm-up running while the mailbox sends.

Email Warmup API Endpoints

Endpoint

What it does

POST /warmup/operations?workspaceId=

Starts an operation: enable, pause, disable or remove for a set of mailboxes

GET /warmup/operations/{operation_id}?workspaceId=

Returns the operation's status and a result per mailbox

GET /warmup/mailboxes?workspaceId=

Returns the current warm-up state of every mailbox in the workspace

All three use the query parameter workspaceId and the header Authorization: Bearer ot2d_....

Warm Up Inboxes via API in Batches

The request body needs an action, the mailboxes and an idempotencyKey:

curl -X POST "$O2D/warmup/operations?workspaceId=123" \
  -H "Authorization: Bearer $O2D_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "enable",
    "mailboxes": [
      "anna@acmemail.com",
      "ben@acmemail.com"
    ],
    "idempotencyKey": "client-a-enable-2026-09-28"
  }'

The response is the operation:

{
  "operationId": "...",
  "status": "requested",
  "action": "enable",
  "workspaceId": 123,
  "requestedCount": 2,
  "prevalidatedCount": 2,
  "acceptedCount": 0,
  "rejectedCount": 0,
  "successCount": 0,
  "failureCount": 0,
  "rejections": [],
  "items": [
    {
      "mailboxId": 501,
      "mailbox": "anna@acmemail.com",
      "status": "requested"
    }
  ]
}

Items carry more fields (timestamps, errorCode, errorMessage); the example is shortened.

Select Mailboxes by Filter Instead of by Address

For a whole domain, you can send a target instead of a list of addresses. With "mode": "all_filtered", the operation applies to every mailbox that matches the filters, for example all mailboxes on one domain:

{
  "action": "enable",
  "target": {
    "mode": "all_filtered",
    "filters": {"domainId": [101]}
  },
  "idempotencyKey": "enable-domain-101-2026-09-28"
}

Filters also accept status, warmupState, tagId and search, and you can leave out mailboxes with excludedMailboxes. The domain IDs come from GET /domains?workspace_id=123.

Idempotency Keys

idempotencyKey is required and must be 8 to 128 characters. If a request with the same key already created an operation in the workspace, the API returns that operation instead of starting a new one. So when a request times out and you are not sure it arrived, send it again with the same key.

The key is matched per workspace, whatever the body says: a second request with the same key but a different action or mailbox list returns the first operation and does nothing. Build one key per batch, for example the client, the action and a batch number or a short hash of the addresses. Do not generate a new random key per attempt, or a retry starts a second operation.

Poll the Operation Until It Finishes

Warm-up changes are applied in the background. Poll GET /warmup/operations/{operation_id} while status is requested or processing. Every other status is final:

Final status

Meaning

accepted

The mailboxes were admitted to warm-up. For enable this is the normal success result, and successCount stays 0

completed

Every mailbox in the operation succeeded

partial

Part of the batch went through: some mailboxes were rejected or failed

failed, failed_timeout

The operation did not complete

needs_reconciliation

The result is unclear and is being checked

An operation result says the request went through. To see whether a mailbox is actually warming, read GET /warmup/mailboxes (below), where its warmupState becomes warming.

In Python, with the api() helper from the API overview:

import time

WS = 123
op = api("POST", "/warmup/operations",
         params={"workspaceId": WS},
         json={
             "action": "enable",
             "mailboxes": ["anna@acmemail.com"],
             "idempotencyKey": "client-a-enable-001",
         })

RUNNING = {"requested", "processing"}
while op["status"] in RUNNING:
    time.sleep(10)
    op = api("GET",
             f"/warmup/operations/{op['operationId']}",
             params={"workspaceId": WS})

print("operation", op["status"])
for r in op["rejections"]:
    print("rejected", r["mailbox"], r["code"])

Rejected Mailboxes

Mailboxes that cannot take the action are listed in rejections with a code, and the rest of the batch goes ahead:

code

Cause

MAILBOX_NOT_FOUND

The address is not a mailbox in this workspace

MAILBOX_STATUS_NOT_ALLOWED

The mailbox status does not allow warm-up (for example, it is still being set up or is deactivated)

Warm-up accepts mailboxes whose status is active, warmup_in_progress, warmup_paused or warmup_error. If every mailbox is rejected, the operation ends as failed.

A request with no valid addresses returns 400.

Pause, Disable or Remove Warm-Up

The same endpoint handles the other actions:

  • pause stops warm-up for now. Use it while you investigate a mailbox, then send enable again.

  • disable turns warm-up off for the mailbox.

  • remove removes the mailbox from warm-up. It is the only action that also accepts mailboxes whose status blocks the other actions, such as deactivated ones.

curl -X POST "$O2D/warmup/operations?workspaceId=123" \
  -H "Authorization: Bearer $O2D_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "pause",
    "mailboxes": ["ben@acmemail.com"],
    "idempotencyKey": "pause-ben-2026-09-28"
  }'

Read the Warm-Up State of Every Mailbox

GET /warmup/mailboxes returns one row per mailbox:

curl "$O2D/warmup/mailboxes?workspaceId=123" \
  -H "Authorization: Bearer $O2D_KEY"
[
  {
    "mailboxId": 501,
    "mailbox": "anna@acmemail.com",
    "mailboxStatus": "active",
    "warmupState": "warming",
    "warmupEnabled": true,
    "warmupStartedAt": "2026-09-14T09:12:00Z",
    "warmupPausedAt": null,
    "latestOperationId": "...",
    "latestOperationStatus": "completed"
  }
]

The rows have a few more fields; the example is shortened. warmupState is one of not_started, warming, paused, error or disabled. warmupStateReason is set when the last observation of the mailbox is out of date (provider_observation_stale).

A report of mailboxes that have warmed for 14 days or more, or that need attention:

from datetime import datetime, timezone

rows = api("GET", "/warmup/mailboxes",
           params={"workspaceId": 123})
now = datetime.now(timezone.utc)

for m in rows:
    started = m.get("warmupStartedAt")
    days = 0
    if started:
        t = datetime.fromisoformat(
            started.replace("Z", "+00:00"))
        if t.tzinfo is None:
            t = t.replace(tzinfo=timezone.utc)
        days = (now - t).days
    ready = m["warmupState"] == "warming" and days >= 14
    flag = "ready" if ready else m["warmupState"]
    print(m["mailbox"], days, flag)

Fourteen days is the minimum. Before a mailbox sends cold email, also check its DNS with the SPF, DKIM and DMARC test, then add the mailboxes to Instantly or Smartlead if you send from there. To run these calls from Claude with an approval step, see the MCP server for email warmup.

FAQ

Does the API choose the warm-up volume?

No. The API turns warm-up on, off or into pause; daily warm-up volume is managed by Outreach2day and is not a request field.

Does the API return a reputation score or inbox rate?

No. It returns warm-up state (warmupState, warmupStartedAt, the latest operation), not a score. Mailbox analytics are in the app.

Should warm-up stay on while I send?

Yes. Keep it running while the mailbox sends cold email; it is included in the mailbox price.

How often should I poll?

Every few seconds for an operation you just started, and once a day or less for GET /warmup/mailboxes. The API allows 10 requests a second and 300 a minute per user and per workspace.

Can I start warm-up on mailboxes that are not in Outreach2day?

No. Operations only apply to mailboxes in the workspace; other addresses come back as MAILBOX_NOT_FOUND.

Is warm-up included in the price?

Yes. Warm-up is included in the per-mailbox price on every plan; see pricing.

Get Started

Create an account and order mailboxes in the app, then create a key on the API keys page and start warm-up with the request above.

Get an API key

Free guide

Send 100,000 cold emails a month

How to send 100,000 cold emails a month while keeping quality, engagement and conversion rates high: domains, mailboxes, warm-up, leads, copy and scaling.

Step-by-step playbook · 19 min read
  • Email tools8 min read

    Cold Email API for Domains, Warm-Up and Replies

  • Email tools7 min read

    MCP Server for Email Warmup With Claude

See deliverability issues before they kill performance

Monitor mailbox health in real time, spot degradation early, and keep warmup, protection, and sending in one place.