Mailpark
API reference

Emails

Send one email, or up to 100 in a batch, and read what happened to each one. Every email is either transactional (one person's action triggered it: resets, receipts, invites) or marketing (updates, tips, onboarding). Marketing email always carries an unsubscribe link and respects topic opt-outs and daily limits; transactional email is never blocked by them.

With a test key nothing leaves Mailpark: the email is rendered, logged as delivered and put in the test inbox.

The Message object
type Message = {
  id: string;                     // msg_…
  to: string; from: string; subject: string;
  type: "transactional" | "marketing";
  template: string | null; template_version: number | null;
  status: "queued" | "sent" | "delivered" | "bounced" | "complained"
        | "suppressed" | "failed" | "held" | "cancelled";
  environment: "test" | "live";
  contact_id: string | null; broadcast_id: string | null; enrollment_id: string | null;
  error_reason: string | null;    // plain language
  opened: boolean; clicked: boolean;
  opened_at: string | null;
  opened_likely_automatic: boolean; // only machine pre-loads so far (Apple MPP, scanners)
  created_at: string; updated_at: string;
  timeline?: { at: string; kind: string; detail: string | null }[]; // GET /v1/emails/{id}
  html?: string;                                                     // GET /v1/emails/{id}
};

Send an email

POST/v1/emails

Sends one email to one recipient, from a template or from inline HTML. Returns 202 straight away; the email is sent from a queue.

scope emails:send

Body

  • tostringrequired

    One email address.

  • templatestring

    A template key, e.g. password-reset. Or send subject and html instead.

  • subjectstring

    Required without a template. Overrides the template's subject.

  • htmlstring

    Inline HTML, with {{variables}}, when you don't use a template.

  • dataobject

    Values for the template's {{variables}}.

  • typestring

    transactional or marketing. Defaults to the template's type, else transactional.

  • topicstring

    Marketing only: the topic a recipient can unsubscribe from.

  • from_namestring

    Display name, e.g. Jane from Acme.

  • reply_tostring

    Where replies go.

  • Set an Idempotency-Key so a retry never sends twice. The SDK sets one automatically; pass your own (e.g. password-reset:<user_id>:<token>) to make retries safe across processes.
  • A suppressed or unsubscribed recipient is not an error: you get 202 with status: "suppressed" and an error_reason, and the message is logged.
  • Test keys finish immediately with delivered. Addresses containing bounce, soft or complain simulate those outcomes.
  • Never loop single sends to email many people. Draft a broadcast instead.
curl -X POST https://api.mailpark.io/v1/emails \
  -H "Authorization: Bearer $MAILPARK_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: password-reset:user_123:abc123" \
  -d '{
    "to": "jane@example.com",
    "template": "password-reset",
    "data": {
      "reset_url": "https://acme.dev/reset/abc123"
    }
  }'
Response · 202
{
  "id": "msg_01JB4Q7Z2K",
  "status": "queued",
  "environment": "live",
  "next_steps": [
    "Check delivery with GET /v1/emails/msg_01JB4Q7Z2K/explain."
  ]
}
▶ Try it in the playground

Send a batch

POST/v1/emails/batch

Up to 100 emails in one request, each with its own recipient and data. One bad email doesn't fail the batch: it comes back with status: "rejected" and an error.

scope emails:send

Body

  • emailsobject[]required

    Up to 100 emails, each shaped like the body of Send an email.

curl -X POST https://api.mailpark.io/v1/emails/batch \
  -H "Authorization: Bearer $MAILPARK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "emails": [
      {
        "to": "jane@example.com",
        "template": "receipt",
        "data": {
          "amount": "$29.00"
        }
      },
      {
        "to": "sam@example.com",
        "template": "receipt",
        "data": {
          "amount": "$99.00"
        }
      }
    ]
  }'
Response · 202
{
  "data": [
    {
      "id": "msg_01JB4Q8A1M",
      "status": "queued"
    },
    {
      "id": "msg_01JB4Q8A1N",
      "status": "queued"
    }
  ]
}
▶ Try it in the playground

List emails

GET/v1/emails

The send log, newest first. Filter by recipient, status, type, day or broadcast.

scope logs:read

Query parameters

  • tostring

    Recipient address.

  • statusstring

    queued, sent, delivered, bounced, complained, suppressed, failed, held or cancelled.

  • typestring

    transactional or marketing.

  • sincestring

    ISO-8601 time; only emails created after it.

  • daystring

    YYYY-MM-DD, in the account's timezone.

  • broadcast_idstring

    Only emails from this broadcast.

  • openedboolean

    true or false.

  • limitinteger

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

  • cursorstring

    The next_cursor from the previous page.

curl 'https://api.mailpark.io/v1/emails?to=jane%40example.com' \
  -H "Authorization: Bearer $MAILPARK_API_KEY"
Response · 200
{
  "data": [
    {
      "id": "msg_01JB4Q7Z2K",
      "to": "jane@example.com",
      "from": "Acme <noreply@mail.acme.dev>",
      "subject": "Reset your Acme password",
      "type": "transactional",
      "template": "password-reset",
      "template_version": 3,
      "status": "delivered",
      "environment": "live",
      "contact_id": "con_7Yt1",
      "broadcast_id": null,
      "enrollment_id": null,
      "error_reason": null,
      "opened": true,
      "clicked": false,
      "opened_at": "2026-10-22T10:04:40Z",
      "opened_likely_automatic": false,
      "created_at": "2026-10-22T10:02:11Z",
      "updated_at": "2026-10-22T10:02:14Z"
    }
  ],
  "next_cursor": null
}
▶ Try it in the playground

Get an email

GET/v1/emails/{id}

One email with its full timeline (queued, sent, delivered, opened…) and the rendered html.

scope logs:read

Path parameters

  • idstringrequired

    The message id, msg_….

curl https://api.mailpark.io/v1/emails/msg_01JB4Q7Z2K \
  -H "Authorization: Bearer $MAILPARK_API_KEY"
Response · 200
{
  "id": "msg_01JB4Q7Z2K",
  "to": "jane@example.com",
  "from": "Acme <noreply@mail.acme.dev>",
  "subject": "Reset your Acme password",
  "type": "transactional",
  "template": "password-reset",
  "template_version": 3,
  "status": "delivered",
  "environment": "live",
  "contact_id": "con_7Yt1",
  "broadcast_id": null,
  "enrollment_id": null,
  "error_reason": null,
  "opened": true,
  "clicked": false,
  "opened_at": "2026-10-22T10:04:40Z",
  "opened_likely_automatic": false,
  "created_at": "2026-10-22T10:02:11Z",
  "updated_at": "2026-10-22T10:02:14Z",
  "timeline": [
    {
      "at": "2026-10-22T10:02:11Z",
      "kind": "created",
      "detail": null
    },
    {
      "at": "2026-10-22T10:02:11Z",
      "kind": "queued",
      "detail": "Transactional lane"
    },
    {
      "at": "2026-10-22T10:02:12Z",
      "kind": "sent",
      "detail": "From mail.acme.dev"
    },
    {
      "at": "2026-10-22T10:02:14Z",
      "kind": "delivered",
      "detail": "Accepted by gmail-smtp-in.l.google.com"
    },
    {
      "at": "2026-10-22T10:04:40Z",
      "kind": "opened",
      "detail": null,
      "likely_automatic": false
    }
  ],
  "html": "<!doctype html><html>…</html>"
}
▶ Try it in the playground

Explain a delivery

GET/v1/emails/{id}/explain

Answers "why didn't they get it?" in plain language: a summary, a verdict, each stage the email went through, and what to fix.

scope logs:read

Path parameters

  • idstringrequired

    The message id, msg_….

  • verdict is delivered, in_progress, not_delivered or blocked. Each step's status is ok, warning, error, pending or skipped.
curl https://api.mailpark.io/v1/emails/msg_01JB4Q7Z2K/explain \
  -H "Authorization: Bearer $MAILPARK_API_KEY"
Response · 200
{
  "message_id": "msg_01JB4Q7Z2K",
  "summary": "Jane's password reset email was delivered to Gmail at 10:02 and opened at 10:04.",
  "verdict": "delivered",
  "steps": [
    {
      "label": "Your app asked Mailpark to send it",
      "status": "ok",
      "detail": "POST /v1/emails at 10:02:11 with template password-reset."
    },
    {
      "label": "Recipient allowed",
      "status": "ok",
      "detail": "jane@example.com is not suppressed or unsubscribed."
    },
    {
      "label": "Sent from your domain",
      "status": "ok",
      "detail": "mail.acme.dev, signed with DKIM."
    },
    {
      "label": "Accepted by the inbox provider",
      "status": "ok",
      "detail": "Gmail accepted it at 10:02:14."
    },
    {
      "label": "Opened",
      "status": "ok",
      "detail": "First opened at 10:04:40."
    }
  ],
  "fix": null,
  "next_steps": []
}
▶ Try it in the playground