Mailpark
API reference

Broadcasts

One email to many people, such as a product update. An API key can only draft a broadcast: it waits in awaiting_approval until a person approves it at approval_url in the web app. Give that link to the founder.

After approval Mailpark sends to a small sample first (per trust level), waits, checks bounces and complaints, then sends the rest. If something looks wrong the broadcast is held with a hold_reason and hold_fix.

The Broadcast object
type Broadcast = {
  id: string; name: string; subject: string;
  template: string | null; html: string;
  topic: string;
  audience_filter: AudienceFilter; audience_count: number;
  sequence_id: string | null;     // enrol recipients in a sequence instead
  send_at: string | null;         // null = send on approval
  status: "draft" | "awaiting_approval" | "scheduled" | "test_sending"
        | "sending" | "held" | "sent" | "cancelled" | "rejected";
  approval_url: string;
  hold_reason: string | null; hold_fix: string | null;
  test_send: { enabled: boolean; sample_size: number; sent: number; result: "pending" | "healthy" | "unhealthy" | null; … };
  stats: { sent: number; delivered: number; opened: number; clicked: number; bounced: number; complained: number; unsubscribed: number };
  created_by: { actor_type: "user" | "api_key"; actor_id: string; actor_name: string };
  approved_by: { user_id: string; name: string; at: string } | null;
  created_at: string; updated_at: string;
};

type AudienceFilter = {            // all parts are ANDed
  lists?: string[];                // on at least one of these lists
  topic?: string;                  // subscribed to the topic
  fields?: Record<string, string | number | boolean>;
  event?: { name: string; within_days?: number; did_not?: boolean };
};

Draft a broadcast

POST/v1/broadcasts

Creates a broadcast that waits for a person to approve it. The response's approval_url is the page where they do: always pass it on.

scope broadcasts:draft

Body

  • namestringrequired

    Internal name.

  • templatestring

    A template key. Or send html, or sequence_id.

  • htmlstring

    Inline HTML instead of a template.

  • subjectstring

    Overrides the template's subject.

  • dataobject

    Values for the template's variables.

  • topicstring

    Recipients unsubscribed from this topic are skipped.

  • audience_filterobject

    Who gets it: lists, topic, fields, event. See the Broadcast object above.

  • sequence_idstring

    Enrol the audience in this sequence instead of sending one email.

  • send_atstring

    ISO-8601 time to send. Default: on approval.

  • The status is always awaiting_approval. API keys can't approve, reject or release broadcasts.
curl -X POST https://api.mailpark.io/v1/broadcasts \
  -H "Authorization: Bearer $MAILPARK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "October product update",
    "template": "product-update",
    "subject": "What'\''s new in Acme: October",
    "data": {
      "headline": "Dark mode is here"
    },
    "topic": "product-updates",
    "audience_filter": {
      "event": {
        "name": "signed_up",
        "within_days": 90
      }
    }
  }'
Response · 200
{
  "id": "bc_oct",
  "name": "October product update",
  "subject": "What's new in Acme: October",
  "template": "product-update",
  "html": "<!doctype html><html>…</html>",
  "topic": "product-updates",
  "audience_filter": {
    "event": {
      "name": "signed_up",
      "within_days": 90
    }
  },
  "audience_count": 1240,
  "sequence_id": null,
  "enrolled": 0,
  "send_at": null,
  "status": "awaiting_approval",
  "approval_url": "https://app.mailpark.io/campaigns/bc_oct",
  "hold_reason": null,
  "hold_fix": null,
  "test_send": {
    "enabled": true,
    "sample_size": 62,
    "sent": 0,
    "bounced": 0,
    "complained": 0,
    "wait_minutes": 60,
    "result": null
  },
  "stats": {
    "sent": 0,
    "delivered": 0,
    "opened": 0,
    "clicked": 0,
    "bounced": 0,
    "complained": 0,
    "unsubscribed": 0
  },
  "created_by": {
    "actor_type": "api_key",
    "actor_id": "key_8Hq2",
    "actor_name": "Claude Code"
  },
  "approved_by": null,
  "created_at": "2026-10-22T10:02:11Z",
  "updated_at": "2026-10-22T10:02:11Z"
}
▶ Try it in the playground

List broadcasts

GET/v1/broadcasts

Broadcasts, newest first.

Query parameters

  • statusstring

    e.g. awaiting_approval, sending, held, sent.

  • limitinteger

    How many to return, 1–100. Default 50.

  • cursorstring

    The next_cursor from the previous page.

curl https://api.mailpark.io/v1/broadcasts \
  -H "Authorization: Bearer $MAILPARK_API_KEY"
Response · 200
{
  "data": [
    {
      "id": "bc_oct",
      "name": "October product update",
      "subject": "What's new in Acme: October",
      "template": "product-update",
      "html": "<!doctype html><html>…</html>",
      "topic": "product-updates",
      "audience_filter": {
        "event": {
          "name": "signed_up",
          "within_days": 90
        }
      },
      "audience_count": 1240,
      "sequence_id": null,
      "enrolled": 0,
      "send_at": null,
      "status": "awaiting_approval",
      "approval_url": "https://app.mailpark.io/campaigns/bc_oct",
      "hold_reason": null,
      "hold_fix": null,
      "test_send": {
        "enabled": true,
        "sample_size": 62,
        "sent": 0,
        "bounced": 0,
        "complained": 0,
        "wait_minutes": 60,
        "result": null
      },
      "stats": {
        "sent": 0,
        "delivered": 0,
        "opened": 0,
        "clicked": 0,
        "bounced": 0,
        "complained": 0,
        "unsubscribed": 0
      },
      "created_by": {
        "actor_type": "api_key",
        "actor_id": "key_8Hq2",
        "actor_name": "Claude Code"
      },
      "approved_by": null,
      "created_at": "2026-10-22T10:02:11Z",
      "updated_at": "2026-10-22T10:02:11Z"
    }
  ],
  "next_cursor": null
}
▶ Try it in the playground

Get a broadcast

GET/v1/broadcasts/{id}

Status, test-send result, hold reason and stats.

Path parameters

  • idstringrequired

    bc_…

curl https://api.mailpark.io/v1/broadcasts/bc_oct \
  -H "Authorization: Bearer $MAILPARK_API_KEY"
Response · 200
{
  "id": "bc_oct",
  "name": "October product update",
  "subject": "What's new in Acme: October",
  "template": "product-update",
  "html": "<!doctype html><html>…</html>",
  "topic": "product-updates",
  "audience_filter": {
    "event": {
      "name": "signed_up",
      "within_days": 90
    }
  },
  "audience_count": 1240,
  "sequence_id": null,
  "enrolled": 0,
  "send_at": null,
  "status": "awaiting_approval",
  "approval_url": "https://app.mailpark.io/campaigns/bc_oct",
  "hold_reason": null,
  "hold_fix": null,
  "test_send": {
    "enabled": true,
    "sample_size": 62,
    "sent": 0,
    "bounced": 0,
    "complained": 0,
    "wait_minutes": 60,
    "result": null
  },
  "stats": {
    "sent": 0,
    "delivered": 0,
    "opened": 0,
    "clicked": 0,
    "bounced": 0,
    "complained": 0,
    "unsubscribed": 0
  },
  "created_by": {
    "actor_type": "api_key",
    "actor_id": "key_8Hq2",
    "actor_name": "Claude Code"
  },
  "approved_by": null,
  "created_at": "2026-10-22T10:02:11Z",
  "updated_at": "2026-10-22T10:02:11Z"
}
▶ Try it in the playground

Update a broadcast

PATCH/v1/broadcasts/{id}

Changes a draft. Only while it is draft or awaiting_approval.

Path parameters

  • idstringrequired

    bc_…

Body

  • namestring

    Internal name.

  • subjectstring

    Subject line.

  • dataobject

    Template values.

  • audience_filterobject

    Who gets it.

  • send_atstring

    When to send.

curl -X PATCH https://api.mailpark.io/v1/broadcasts/bc_oct \
  -H "Authorization: Bearer $MAILPARK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "New in Acme: dark mode"
  }'
Response · 200
{
  "id": "bc_oct",
  "name": "October product update",
  "subject": "New in Acme: dark mode",
  "template": "product-update",
  "html": "<!doctype html><html>…</html>",
  "topic": "product-updates",
  "audience_filter": {
    "event": {
      "name": "signed_up",
      "within_days": 90
    }
  },
  "audience_count": 1240,
  "sequence_id": null,
  "enrolled": 0,
  "send_at": null,
  "status": "awaiting_approval",
  "approval_url": "https://app.mailpark.io/campaigns/bc_oct",
  "hold_reason": null,
  "hold_fix": null,
  "test_send": {
    "enabled": true,
    "sample_size": 62,
    "sent": 0,
    "bounced": 0,
    "complained": 0,
    "wait_minutes": 60,
    "result": null
  },
  "stats": {
    "sent": 0,
    "delivered": 0,
    "opened": 0,
    "clicked": 0,
    "bounced": 0,
    "complained": 0,
    "unsubscribed": 0
  },
  "created_by": {
    "actor_type": "api_key",
    "actor_id": "key_8Hq2",
    "actor_name": "Claude Code"
  },
  "approved_by": null,
  "created_at": "2026-10-22T10:02:11Z",
  "updated_at": "2026-10-22T10:02:11Z"
}
▶ Try it in the playground

Cancel a broadcast

POST/v1/broadcasts/{id}/cancel

Stops a broadcast that hasn't finished. Emails already sent stay sent.

Path parameters

  • idstringrequired

    bc_…

curl -X POST https://api.mailpark.io/v1/broadcasts/bc_oct/cancel \
  -H "Authorization: Bearer $MAILPARK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
Response · 200
{
  "id": "bc_oct",
  "name": "October product update",
  "subject": "What's new in Acme: October",
  "template": "product-update",
  "html": "<!doctype html><html>…</html>",
  "topic": "product-updates",
  "audience_filter": {
    "event": {
      "name": "signed_up",
      "within_days": 90
    }
  },
  "audience_count": 1240,
  "sequence_id": null,
  "enrolled": 0,
  "send_at": null,
  "status": "cancelled",
  "approval_url": "https://app.mailpark.io/campaigns/bc_oct",
  "hold_reason": null,
  "hold_fix": null,
  "test_send": {
    "enabled": true,
    "sample_size": 62,
    "sent": 0,
    "bounced": 0,
    "complained": 0,
    "wait_minutes": 60,
    "result": null
  },
  "stats": {
    "sent": 0,
    "delivered": 0,
    "opened": 0,
    "clicked": 0,
    "bounced": 0,
    "complained": 0,
    "unsubscribed": 0
  },
  "created_by": {
    "actor_type": "api_key",
    "actor_id": "key_8Hq2",
    "actor_name": "Claude Code"
  },
  "approved_by": null,
  "created_at": "2026-10-22T10:02:11Z",
  "updated_at": "2026-10-22T10:02:11Z"
}
▶ Try it in the playground

Preview an audience

POST/v1/broadcasts/audience-preview

How many contacts a filter reaches, with a sample. Opted-out contacts are already left out.

Body

  • audience_filterobjectrequired

    { lists?, topic?, fields?, event? }

  • topicstring

    The broadcast's topic.

curl -X POST https://api.mailpark.io/v1/broadcasts/audience-preview \
  -H "Authorization: Bearer $MAILPARK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "audience_filter": {
      "lists": [
        "lst_beta"
      ]
    },
    "topic": "product-updates"
  }'
Response · 200
{
  "count": 40,
  "sample": [
    {
      "id": "con_7Yt1",
      "email": "jane@example.com"
    }
  ]
}
▶ Try it in the playground