Interactive documentation · Developer portal · OpenAPI specification · EN · FR · DE · IT · PT · ES

Mailpro™ Email API v2 : Reference

Base URL : https://api.mailpro.com/v2

The Mailpro™ Email API v2 lets you programmatically manage contact lists, email templates, sender addresses, and campaigns — and send both bulk and transactional emails.

Authentication

Every endpoint accepts three authentication methods. Use whichever fits your stack.

> Legacy Bearer JWT and IdClient+ApiKey keep working unchanged and are not > scope-gated. Only OAuth 2.0 access tokens are subject to the per-endpoint > scope checks shown on each operation below.

Method 1 — OAuth 2.0 (recommended for integrations)

Mailpro runs one OAuth 2.0 authorization server for all of its APIs, at a single version-neutral surface: https://api.mailpro.com/oauth/. There is no per-version OAuth endpoint — you obtain a token from /oauth/token and the same token works on this Email v2 API, on CRM v3 and on SMS. Which endpoints it may call is governed by the token's scopes, not the API base URL. Issuer (iss claim): https://api.mailpro.com/oauth/.

EndpointURL
AuthorizeGET https://api.mailpro.com/oauth/authorize
Token / RefreshPOST https://api.mailpro.com/oauth/token
JWKSGET https://api.mailpro.com/oauth/.well-known/jwks
DiscoveryGET https://api.mailpro.com/oauth/.well-known/openid-configuration

Grants: authorization_code + PKCE (S256, mandatory), client_credentials, refresh_token (rotation). Access-token TTL 20 min; refresh token 30 days (sliding, single-use); auth code 60 s.

Quickstart — get a token and call this API (replace placeholders; never commit a real secret):

curl -X POST https://api.mailpro.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "scope=email:send email:read"

Then call this API with Authorization: Bearer ACCESS_TOKEN. The same token works on the CRM v3 and SMS APIs. A token missing an endpoint's required scope gets 403 insufficient_scope. Full flow walk-through and scope catalog: see the CRM v3 API docs › Authentication. Email-relevant scopes: email:send, email:read, email:templates:read, email:templates:write, contacts:read, contacts:write, account:read.

Method 2 — Bearer JWT (legacy)

Authorization: Bearer <access_token>

Get a token via POST /token (form-encoded, OAuth2 password grant):

POST https://api.mailpro.com/v2/token
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=YOUR_USERNAME&password=YOUR_API_KEY

The same token is also valid on the CRM API v3 and the SMS API — authenticate once, call any API. Tokens are valid for 30 days by default; use grant_type=refresh_token to renew without re-authenticating. Not scoped — prefer OAuth 2.0 for installable integrations.

Method 3 — IdClient + ApiKey

Two credentials:

ParameterTypeDescription
IdClientintegerYour numeric Mailpro client ID, found in Settings → API access
ApiKeystringYour API key, found in Settings → API access

Send them in either of these places — pick one:

If you provide credentials in more than one place, the header wins.

OAuth scope enforcement per endpoint (Email v2)

For OAuth-issued tokens, each endpoint group requires a scope (legacy auth is exempt). Each operation lists its scope under Authorization. Summary:

EndpointsRequired scope
/send/* (campaigns, transactional, scheduling, test — except /send/{IdSend}/status)email:send
/stats/* (except /stats/del), /history/list, /send/{IdSend}/statusemail:read
/message/*, /senderEmail/* (read)email:templates:read
/message/*, /senderEmail/* (add/edit/del), /stats/delemail:templates:write
/list/list, /email/listcontacts:read
/list/{add,edit,del}, /email/{add,addSingle,edit,del}, /import/uploadcontacts:write
/account/*account:read

Scope rejection returns 403 with { "error": "insufficient_scope", "required_scope": "…" }.

Response format

All endpoints support json (default) and xml. Specify the format in the URL:

Pagination

Same rule on the three Mailpro APIs (Email v2, CRM v3, SMS): 5,000 items per page, by default and at most.

Unknown parameters are ignored, never rejected. When a request carries a parameter the API does not recognise, the response includes X-Ignored-Parameters (the names) and X-Documentation (a link to this page), so a typo does not go unnoticed.

Error responses

HTTPMeaning
400Bad request — missing or invalid required parameter
401Unauthorized — invalid IdClient or ApiKey
403Forbidden — account limit reached (e.g. max lists)
404Not found — the requested resource does not exist
500Internal server error

Errors from business rules:

Delivery tracking (transactional emails)

Every single email you send (POST /send/sendmail, the CRM API v3, the SMTP relay or an automation) gets an identifier. It can be followed until the recipient's server accepts or rejects it. Campaigns are not covered here: use the statistics instead.

statusMeaningFinal?
queuedAccepted, waiting to be sent.no
scheduledPlanned for later (DatePlanned).no
sentHanded over by our servers, no answer from the recipient server yet.no
deliveredAccepted by the recipient server (DiagCode = its SMTP reply).yes
bouncedRejected. BounceType: hard = invalid address or permanent refusal; soft = refused after retries (mailbox full, server unreachable…).yes
not_foundUnknown id, or an id that does not belong to your account.yes

Polling without looping

Statuses are kept for 365 days.

Webhooks

Mailpro™ can notify your server in real time when certain events occur on your account (an email is delivered, a campaign finishes, a contact bounces, etc.). Stripe-style — your URL receives an HMAC-SHA256 signed POST as soon as the event fires.

Each event sent to your webhook counts as 1 request against your API request quota.

How it works

> ⏱ Activation delay — after you create, update or delete a webhook, the > change takes up to 10 minutes to propagate across the Mailpro API > servers. During that window events may not yet match your new subscription > (or may still match a just-removed one). This is the time required to > refresh the in-memory subscription cache on every API instance.

Payload envelope

All events share a common envelope:

{
  "id":         "evt_8f3a9b2c4d1e5f6789abcdef01234567",
  "type":       "email.delivered",
  "created":    1748194800,
  "apiVersion": "v2",
  "account":    { "id": 12345, "name": "ACME Corp" },
  "data":       { "messageId": "...", "email": "[email protected]", "...": "..." }
}
FieldFormatDescription
idevt_ + 32 hexUnique event ID — use as an idempotency key on your side.
type<domain>.<verb>Canonical event type.
createdUnix seconds UTCIncluded in the HMAC signature.
apiVersion"v2"Which Mailpro API published the event.
account.idintYour Mailpro client ID (= IdClient).
account.namestringYour account name (handy for client-side debug).
dataobjectEvent payload — shape depends on type (see catalog below).

Event catalog

EventTriggerdata shape
pingTest via POST /webhooks/{id}/test{ message }
email.processedSend accepted by pipeline{ messageId, email, campaignId, processedAt }
email.deliveredA single email was accepted by the recipient server — API v2/v3, SMTP or Automation (not campaigns). Emitted from the PowerMTA delivery feed.{ messageId, email, idSending, sendingType, diagCode, deliveredAt }
email.openedTracking pixel hit{ messageId, email, campaignId, openedAt, ip, userAgent }
email.clickedClick on tracked link{ messageId, email, campaignId, clickedAt, url, ip, userAgent }
email.bouncedHard or soft bounce{ messageId, email, campaignId, bouncedAt, bounceType, reason }
email.spam_reportFBL complaint{ messageId, email, campaignId, reportedAt }
email.unsubscribedOpt-out link clicked{ messageId, email, campaignId, unsubscribedAt }
email.droppedRefused before send{ messageId, email, campaignId, droppedAt, reason }
campaign.startedDispatch begins{ campaignId, name, totalRecipients, startedAt }
campaign.finishedAll recipients processed{ campaignId, name, totalSent, totalFailed, finishedAt }
credits.lowEmail credit balance under your alert threshold{ remaining, threshold }

Subscribe to all events of this API by passing ["*"] in the events array.

HMAC signature

Every POST carries an X-Mailpro-Signature header:

X-Mailpro-Signature: t=1748194800,v1=7f9ac2b6e4d1c8a3...
X-Mailpro-Event-Id:  evt_8f3a9b2c4d1e5f6789abcdef01234567
X-Mailpro-Event-Type: email.delivered
X-Mailpro-Attempt:   1

To verify:

Node.js
const crypto = require('crypto');

function verifyMailproSignature(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
  if (Math.abs(Date.now() / 1000 - parseInt(parts.t)) > 300) return false;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(parts.t + '.' + rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(parts.v1, 'hex'),
    Buffer.from(expected, 'hex'));
}
PHP
function verifyMailproSignature(string $rawBody, string $header, string $secret): bool {
    parse_str(str_replace(',', '&', $header), $p);
    if (abs(time() - (int)$p['t']) > 300) return false;
    $expected = hash_hmac('sha256', $p['t'] . '.' . $rawBody, $secret);
    return hash_equals($expected, $p['v1']);
}
Python
import hmac, hashlib, time

def verify_mailpro_signature(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split('=') for p in header.split(','))
    t = int(parts['t'])
    if abs(time.time() - t) > 300:
        return False
    expected = hmac.new(
        secret.encode(),
        f"{t}.".encode() + raw_body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, parts['v1'])
C#
using System.Security.Cryptography;
using System.Text;

static bool VerifyMailproSignature(string rawBody, string header, string secret) {
    var parts = header.Split(',')
        .Select(p => p.Split('='))
        .ToDictionary(a => a[0], a => a[1]);
    var t = long.Parse(parts["t"]);
    if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - t) > 300) return false;
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes($"{t}.{rawBody}"));
    var expected = Convert.ToHexString(hash).ToLowerInvariant();
    return CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(expected),
        Encoding.UTF8.GetBytes(parts["v1"]));
}

Retry and auto-disable

AttemptDelay before next try
1 → 210 seconds
2 → 31 minute
3 → 45 minutes
4 → 530 minutes
5 → 62 hours
6 → 76 hours
7abandoned (delivery marked failed)

After 10 consecutive failures on the same webhook, it is automatically disabled (isActive: false) and you receive a notification email. Re-enable it with PUT /webhooks/{id} once your endpoint is fixed.

Success = HTTP 2xx

We treat your webhook as successful only if you return a status in 200–299. Any 3xx/4xx/5xx or a timeout (> 10 s) counts as a failure and triggers retries. If your endpoint is permanently dead, return 410 Gone explicitly — retries still happen but the failure reason is clearer in your delivery log.

Limits

LimitValue
Webhooks per account20
Subscribed events per webhook50
Max payload size256 KB
HTTP timeout per attempt10 seconds
Delivery history retention30 days

Contents

Authentication

Obtain a Bearer JWT token to authenticate all other API calls. The v2 /token endpoint is a 307 redirect to the canonical /v3/token issuer — your HTTP client follows the redirect transparently (POST method and body are preserved).

POST /token

Get access token (307 redirect to /v3/token)

Obtains a Bearer JWT token using Mailpro client credentials. Pass the returned access_token as Authorization: Bearer <token> in all subsequent requests. The same token is also valid on the CRM API v3 and the SMS API.

> ⚙ Implementation detail — POST /v2/token is a 307 Temporary > Redirect to POST /v3/token (configured at the IIS web.config > rewrite layer). HTTP 307 preserves both the method (POST) and > the request body, so any standards-compliant HTTP client follows > the redirect transparently — you keep posting to /v2/token and get > a valid JWT back. There is no duplicated token-issuance logic; the > Email v2 API never issues tokens itself.

Supported grant types
Grant typeUse case
passwordStandard integration — username + API key
refresh_tokenRenew an expiring access token without re-authenticating
client_credentialsServer-to-server with client ID only
Token lifetime

Use grant_type=refresh_token to renew the access token without re-authenticating. Store tokens server-side (Vault, env vars); never embed in browser JS or mobile app binaries. See the CRM API v3 /token doc for the full security note.

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
grant_typestring: password, refresh_token, client_credentialsyes
usernamestringYour Mailpro username (email). Required for password grant.
passwordstringYour Mailpro API key (GUID format). Required for password grant.
refresh_tokenstringPrevious refresh token. Required for refresh_token grant.
client_idstringYour Mailpro client ID (integer). Required for client_credentials grant.

Responses

CodeDescription
200Token issued successfully. Returned by POST /v3/token after the transparent 307 follow — your HTTP client sees a normal 200 with the same body shape it would receive directly from the v3 token endpoint.
307Temporary Redirect to POST /v3/token, issued by the IIS rewrite rule in web.config. Standards-compliant HTTP clients follow this automatically and preserve the original POST method + form body. Visible only to clients that explicitly disable redirect following (e.g. curl --max-redirs 0).
400Invalid credentials or unsupported grant type

Example 200

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 31535999,
  "userName": "12345",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "application": null
}

Example 400

{
  "error": "invalid_grant",
  "error_description": "The username or password is incorrect."
}

Account

Check your email credit balance, monthly API quota, and subscription status.

GET /account/credits.{format}

Get email credit balance

Returns the remaining email credit balance for your Mailpro account. Each email sent consumes one credit. Credits do not expire with the subscription, but are only usable while the subscription is active.

OAuth scope : account:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Responses

CodeDescription
200Credit balance returned successfully
401Invalid credentials

Example 200

{
  "Credits": 48500
}

GET /account/subscriptionEndDate.{format}

Get subscription end date

Returns the expiry date of the current Mailpro subscription. After the expiry date, sends are blocked until the subscription is renewed.

OAuth scope : account:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Responses

CodeDescription
200Subscription end date returned successfully
401Invalid credentials

Example 200

{
  "SubscriptionEndDate": "2027-01-15T00:00:00"
}

GET /account/limits.{format}

Get monthly API quota

Returns the monthly API request quota for the authenticated client.

Quota calculation: the limit is calibrated from the email subscription credits (typically subscription_credits × 5). For a 10,000-email/month plan, this yields a 50,000-request monthly budget. Reset on the subscription anniversary date (not the 1st of the calendar month).

What consumes credits:

What is FREE (does not consume credits):

The quota is shared across all 3 Mailpro APIs (v2, v3, sms) — one client = one monthly budget across the whole platform.

OAuth scope : account:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Responses

CodeDescription
200Quota snapshot
401Invalid credentials
404No quota record found (newly created account; row will be created on the first authenticated request).

Example 200

{
  "MonthlyLimit": 50000,
  "Remaining": 32847,
  "Used": 17153,
  "IsFree": false,
  "RemainedSendsForFree": 0
}

Lists

Manage lists (contact lists). A list groups contacts for bulk campaigns. Maximum 1,000 lists per account (same limit on the three Mailpro APIs and in the app). Type 0 = standard list. Type 1 = blacklist (used for unsubscribe management).

GET /list/list.{format}

List all lists

Returns all lists for the account, ordered by creation date (newest first) unless sortByName=true is set. Use type=0 for regular lists or type=1 for blacklists.

> Legacy alias: /addressbook/list.{format} — same endpoint, kept for backward > compatibility. New integrations should use /list/*.

OAuth scope : contacts:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
titleFilterquerystringFilter by title (partial, case-insensitive match)
typequeryinteger: 0, 10 = standard lists only, 1 = blacklists only. Omit to return all.
numberOfRecordsqueryintegerItems per page — 5,000 by default and at most (see Pagination).
pageNumberqueryintegerPage number (1-based). Default is 1.
sortByNamequerybooleanSet to true to sort results alphabetically by title

Responses

CodeDescription
200Lists returned successfully. > The response also contains a legacy duplicate field AddressBookList (identical content) > and each item has a legacy duplicate AddressBookID (identical to ListID). New > integrations should ignore them.
401Invalid credentials

Example 200

{
  "Lists": [
    {
      "ListID": 101,
      "Title": "Newsletter — France",
      "Type": 0,
      "NumberEmails": 4521,
      "CreationDate": "2025-01-15T10:30:00"
    },
    {
      "ListID": 102,
      "Title": "Unsubscribers",
      "Type": 1,
      "NumberEmails": 312,
      "CreationDate": "2025-01-15T10:31:00"
    }
  ]
}

POST /list/add.{format}

Create a list

Creates a new list (contact list). The list starts empty — add contacts with /email/add or /email/addSingle. Maximum 1,000 lists per account (same limit on the three Mailpro APIs and in the app). Returns HTTP 422 if the limit is reached.

> Legacy alias: /addressbook/add.{format} — same endpoint, kept for backward compatibility.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
TitlestringyesDisplay name for the new list
Typeinteger: 0, 10 = standard list (default), 1 = blacklist

Responses

CodeDescription
200List created successfully. > A legacy duplicate field AddressBookList is also returned (identical content).
400Missing Title
401Invalid credentials
422List limit (1,000) reached

Example 200

{
  "Lists": [
    {
      "ListID": 103,
      "Title": "Spring Campaign 2026",
      "Type": 0,
      "NumberEmails": 0,
      "CreationDate": "2026-04-14T12:00:00"
    }
  ]
}

POST /list/edit.{format}

Rename a list

Updates the title of an existing list. The contacts inside are not affected.

> Legacy alias: /addressbook/edit.{format} — same endpoint, kept for backward compatibility. > The AddressBookID parameter name is also accepted as a legacy alias for ListID.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
ListIDintegeryesID of the list to rename (from /list/list). Legacy alias AddressBookID is also accepted.
TitlestringyesNew title for the list

Responses

CodeDescription
200List renamed successfully
400Missing ListID or Title
401Invalid credentials
404List not found

POST /list/del.{format}

Delete a list

Permanently deletes a list and all the contacts it contains. This action cannot be undone.

> Legacy alias: /addressbook/del.{format} — same endpoint, kept for backward compatibility. > The AddressBookID parameter name is also accepted as a legacy alias for ListID.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
ListIDintegeryesID of the list to delete. Legacy alias AddressBookID is also accepted.

Responses

CodeDescription
200List deleted successfully
400Missing ListID
401Invalid credentials
404List not found

Example 200

{
  "Result": "OK"
}

Contacts

Manage individual email contacts. Contacts belong to one or more lists. Use bulk import (/email/add) for large batches, or single-contact add (/email/addSingle) for individual sign-ups with custom field values.

GET /email/list.{format}

List contacts

Returns a paginated list of email contacts for the account. Filter by list or by email pattern. Results are ordered alphabetically when sortByName=true, otherwise by insertion order.

OAuth scope : contacts:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
emailFilterquerystringFilter by email address (partial, case-insensitive match)
listIdqueryintegerReturn only contacts in this list. Legacy alias addressBookId is also accepted.
numberOfRecordsqueryintegerItems per page — 5,000 by default and at most (see Pagination).
pageNumberqueryintegerPage number (1-based). Default is 1.
sortByNamequerybooleanSet to true to sort results alphabetically by email address

Responses

CodeDescription
200Contact list returned successfully
401Invalid credentials

Example 200

{
  "Emails": [
    {
      "IdEmail": 10001,
      "Email": "[email protected]",
      "Field1": "Alice",
      "Field2": "Dupont"
    }
  ]
}

POST /email/add.{format}

Import contacts (bulk)

Imports one or more email addresses into a list in a single request. Provide addresses in EmailList separated by semicolons.

Example: [email protected];[email protected];[email protected]

This endpoint only imports email addresses. To also set custom field values, use /email/addSingle for each contact instead.

Previously unsubscribed addresses are silently skipped unless Force=1 is set.

List selection: provide ListId (or its legacy alias AddressBookId). At least one is required.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
ListIdintegerID of the destination list. Legacy alias AddressBookId is also accepted.
EmailListstringyesSemicolon-separated email addresses to import
Forceinteger: 0, 1Set to 1 to re-import addresses that were previously unsubscribed. Default: 0 (unsubscribed addresses are skipped).

Responses

CodeDescription
200Contacts imported successfully
400Missing EmailList
401Invalid credentials

Example 200

{
  "Result": "3 contacts imported"
}

POST /email/addSingle.{format}

Add a single contact

Adds one contact to a list with optional custom field values.

Field1 through Field25 map to the custom column definitions of your list. The mapping depends on the order columns were defined — check your list settings in the Mailpro interface to see which field number corresponds to which column (e.g. Field1 = FirstName, Field2 = LastName, Field3 = City…).

List selection: provide ListId (or its legacy alias AddressBookId).

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
ListIdintegerID of the destination list. Legacy alias AddressBookId is also accepted.
EmailstringyesEmail address of the new contact
Forceinteger: 0, 1Set to 1 to re-add a previously unsubscribed address
Field1stringValue for custom field 1 (column mapping depends on your list)
Field2stringValue for custom field 2
Field3stringValue for custom field 3
Field4stringValue for custom field 4
Field5stringValue for custom field 5

Responses

CodeDescription
200Contact added successfully
400Missing Email
401Invalid credentials

Example 200

{
  "EmailId": 10002,
  "Email": "[email protected]",
  "Field1": "Alice",
  "Field2": "Dupont",
  "ListID": 101
}

POST /email/edit.{format}

Update a contact

Updates the email address and/or custom field values of an existing contact. Identify the contact by EmailId (from /email/list). Both EmailId and Email are required.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
EmailIdintegeryesID of the contact to update (from /email/list)
EmailstringyesNew email address for the contact
Field1stringNew value for custom field 1
Field2stringNew value for custom field 2
Field3stringNew value for custom field 3

Responses

CodeDescription
200Contact updated successfully
400Missing EmailId or Email
401Invalid credentials
404Contact not found

POST /email/del.{format}

Delete a contact

Permanently deletes a contact by its unique identifier.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
EmailIdintegeryesID of the contact to delete (from /email/list)

Responses

CodeDescription
200Contact deleted successfully
400Missing EmailId
401Invalid credentials
404Contact not found

Messages

Manage HTML email templates. A message template stores your email subject, HTML body, and unsubscribe link configuration. Templates are reused across campaigns.

GET /message/list.{format}

List message templates

Returns saved HTML email templates for the account. By default, the HTML body is not included in the response to keep payloads small. Set bodyHTML=true to include it (automatically limits results to 10 per page).

OAuth scope : email:templates:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
subjectFilterquerystringFilter by subject (partial, case-insensitive match)
numberOfRecordsqueryintegerItems per page — 5,000 by default and at most (see Pagination).
pageNumberqueryintegerPage number (1-based). Default is 1.
bodyHTMLquerybooleanSet to true to include the full HTML body in each result. When true, results are automatically limited to 10 per page regardless of numberOfRecords.

Responses

CodeDescription
200Message templates returned successfully
401Invalid credentials

Example 200

{
  "MessageList": [
    {
      "IDMessage": 501,
      "Subject": "Spring Newsletter — April 2026",
      "Language": "EN",
      "LinkAlign": "center",
      "LinkUp": 0,
      "UpdateDate": "2026-03-10T14:22:00"
    }
  ]
}

POST /message/add.{format}

Create a message template

Creates a new HTML email template. The template stores the subject, HTML body, and unsubscribe link settings. Once created, use the returned IDMessage when launching a campaign.

OAuth scope : email:templates:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
SubjectstringyesEmail subject line (max 255 characters)
BodyHTMLstringyesFull HTML content of the email body
LanguagestringyesLanguage code for unsubscribe link localization (e.g. EN, FR, DE)
LinkAlignstring: left, center, rightyesHorizontal alignment of the unsubscribe link
LinkUpinteger: 0, 1yesPosition of the unsubscribe link — 0 = bottom of email, 1 = top

Responses

CodeDescription
200Message template created successfully
400Missing required field
401Invalid credentials

Example 200

{
  "IDMessage": 502,
  "Subject": "Spring Newsletter — April 2026",
  "Language": "EN",
  "LinkAlign": "center",
  "LinkUp": 0
}

POST /message/edit.{format}

Update a message template

Updates the content and metadata of an existing message template.

OAuth scope : email:templates:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
MessageIdintegeryesID of the message template to update (from /message/list)
SubjectstringyesNew email subject line
BodyHTMLstringyesNew HTML content of the email body
LanguagestringyesLanguage code for unsubscribe link localization
LinkAlignstring: left, center, rightyes
LinkUpinteger: 0, 1yes

Responses

CodeDescription
200Message template updated successfully
400Missing required field
401Invalid credentials
404Message template not found

POST /message/del.{format}

Delete a message template

Permanently deletes a message template by its unique identifier. Templates used by pending scheduled campaigns cannot be deleted.

OAuth scope : email:templates:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
MessageIdintegeryesID of the message template to delete (from /message/list)

Responses

CodeDescription
200Message template deleted successfully
400Missing MessageId
401Invalid credentials
404Message template not found

Campaigns

Send bulk email campaigns to a list and manage scheduled sends. A campaign uses a message template and a sender address.

GET /send/list.{format}

List scheduled campaigns

Returns the list of scheduled and recently processed email campaigns. Results are ordered by planned send date (most recent first).

OAuth scope : email:send

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
subjectMessageFilterquerystringFilter by campaign subject (partial, case-insensitive match)
numberOfRecordsqueryintegerItems per page — 5,000 by default and at most (see Pagination).
pageNumberqueryintegerPage number (1-based). Default is 1.

Responses

CodeDescription
200Campaign list returned successfully
401Invalid credentials

Example 200

{
  "sends": [
    {
      "IDSend": 3001,
      "ListID": 101,
      "SenderEmail": "[email protected]",
      "SenderName": "Your Company Newsletter",
      "DatePlanned": "2026-04-20T09:00:00",
      "SubjectMessage": "Spring Newsletter — April 2026",
      "NumberCredit": 4521
    }
  ]
}

POST /send/campaign.{format}

Launch a campaign

Schedules or immediately sends an email campaign to a list using a saved message template and a sender address.

Required:

Scheduling: set DatePlanned to a future ISO 8601 date/time to schedule the send. Omit DatePlanned to send immediately.

Statistics tracking: set Campaign=1 and provide a TitleCampaign to enable open/click/bounce tracking in the Statistics section.

OAuth scope : email:send

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
ListIdintegerID of the recipient list (from /list/list). Legacy alias IDAddressBook is also accepted.
IDMessageintegeryesID of the message template to send (from /message/list)
IDEmailExpintegeryesID of the sender email address to use (from /senderEmail/list)
IDUnsubscribeListintegerList used to collect unsubscribed contacts (blacklist). Legacy alias IDUnsubscribeAddressBook is also accepted.
DatePlannedstring (date-time)Scheduled send date/time (ISO 8601). Omit to send immediately.
Campaigninteger: 0, 1Set to 1 to track this send as a named campaign in statistics
TitleCampaignstringCampaign title shown in statistics (required when Campaign=1)

Responses

CodeDescription
200Campaign scheduled or sent successfully. > The response also returns legacy aliases AddressBookID and UnsubscribeAddressBookID > (identical to ListID / UnsubscribeListID). New integrations should ignore them.
400Missing IDMessage or IDEmailExp
401Invalid credentials
403Not enough email credits
404List, message, or sender not found

Example 200

{
  "send": {
    "IDSend": 3002,
    "ListID": 101,
    "UnsubscribeListID": 102,
    "IDCampaign": 8001,
    "SenderName": "Your Company Newsletter",
    "SenderEmail": "[email protected]",
    "DatePlanned": "2026-04-20T09:00:00",
    "SubjectMessage": "Spring Newsletter — April 2026",
    "NumberCredit": 4521
  }
}

POST /send/del.{format}

Cancel or delete a campaign

Cancels a pending scheduled campaign send, or deletes a completed one. Only future-scheduled sends can be cancelled — sends in progress cannot be stopped.

OAuth scope : email:send

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
IdSendintegeryesID of the campaign send to cancel or delete (from /send/list)

Responses

CodeDescription
200Campaign cancelled or deleted successfully
400Missing IdSend
401Invalid credentials
404Campaign not found

Example 200

{
  "Result": "Campaign Deleted"
}

GET /send/scheduled.{format}

List my scheduled email campaigns

Returns the email campaigns and single sends scheduled for a future date that have not yet been dispatched. Useful before calling /cancel or /reschedule. Records are sorted by DatePlanned ascending.

OAuth scope : email:send

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
numberOfRecordsqueryintegerItems per page — 5,000 by default and at most (see Pagination).
pageNumberqueryintegerPage number (1-based). Default is 1.

Responses

CodeDescription
200List of scheduled campaigns / sends. > Each record also includes legacy aliases AddressBookID and > UnsubscribeAddressBookID (identical to ListID / UnsubscribeListID).
401Invalid credentials

Example 200

{
  "sends": [
    {
      "IDSend": 5001,
      "ListID": 101,
      "UnsubscribeListID": 102,
      "IDCampaign": 8001,
      "SenderName": "Your Company Newsletter",
      "SenderEmail": "[email protected]",
      "DatePlanned": "2026-05-01T09:00:00",
      "SubjectMessage": "Spring Sale 2026",
      "NumberCredit": 4521
    }
  ]
}

POST /send/{IdSend}/cancel.{format}

Cancel a scheduled campaign / send

Cancels a scheduled email send (campaign or single) that has not yet been dispatched. If the send has already gone out (or doesn't exist / belongs to another client), returns 409 Conflict.

OAuth scope : email:send

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
IdSendpathintegeryes

Responses

CodeDescription
200Send cancelled
401Invalid credentials
409Already dispatched or not found

Example 200

{
  "Result": "OK",
  "Cancelled": 5001
}

PATCH /send/{IdSend}/reschedule.{format}

Reschedule a scheduled campaign / send

Updates the DatePlanned of a scheduled email send. New date must be in the future and within 90 days.

OAuth scope : email:send

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
IdSendpathintegeryes

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
PlanDatestring (date-time)yes

Responses

CodeDescription
200Send rescheduled
400Missing or invalid PlanDate
401Invalid credentials
409Already dispatched, not found, or new date out of range (must be future and within 90 days)

Example 200

{
  "Result": "OK",
  "IdSend": 5001,
  "NewPlanDate": "2026-12-31T14:30:00"
}

POST /send/sendingTest.{format}

Send a test email

Sends a preview of a message template to a specified recipient for review before launching the real campaign. The sender, template, and test recipient are all identified by their IDs.

OAuth scope : email:send

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
MessageIdintegeryesID of the message template to preview (from /message/list)
IDEmailExpFromintegeryesID of the sender address to use (from /senderEmail/list)
IDEmailExpTointegeryesID of the recipient sender address — the test email is sent to this address

Responses

CodeDescription
200Test email sent successfully
400Missing required field
401Invalid credentials

Example 200

{
  "SendingTest": {
    "IdSendingTest": 7001,
    "ToEmail": "[email protected]",
    "FromEmail": "[email protected]",
    "SenderName": "Your Company Newsletter"
  }
}

Transactional

Send one-off transactional emails to individual recipients. Unlike campaigns, transactional emails do not require a list — you pass the recipient email and HTML body directly.

GET /send/mail/scheduled.{format}

List scheduled single emails

Returns the single emails sent with /send/sendmail and a future date that have not left yet. Campaigns are listed by /send/scheduled.

OAuth scope : email:send

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Responses

CodeDescription
200Scheduled emails returned
401Invalid credentials

Example 200

{
  "scheduledMails": [
    {
      "IdSingleSend": 880123,
      "Email": "[email protected]",
      "Subject": "Your invoice",
      "DatePlanned": "2026-12-01T09:00:00"
    }
  ]
}

POST /send/mail/{IdSingleSend}/cancel.{format}

Cancel a scheduled single email

Cancels the email as long as it has not left.

OAuth scope : email:send

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
IdSingleSendpathinteger (int64)yesIdSingleSend returned by /send/sendmail

Responses

CodeDescription
200Email cancelled
401Invalid credentials
409Send not found, or already dispatched

Example 200

{
  "Result": "OK",
  "Cancelled": 880123
}

POST /send/mail/{IdSingleSend}/reschedule.{format}

Reschedule a scheduled single email

Changes the send date. The new date must be in the future and within 90 days. PATCH is also accepted.

OAuth scope : email:send

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
IdSingleSendpathinteger (int64)yesIdSingleSend returned by /send/sendmail

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
PlanDatestring (date-time)yesNew send date (ISO 8601)

Responses

CodeDescription
200Email rescheduled
400Missing or invalid date
401Invalid credentials
409Send not found, already dispatched, or date out of range

Example 200

{
  "Result": "OK",
  "IdSingleSend": 880123,
  "NewPlanDate": "2026-12-15T09:00:00"
}

POST /send/sendmail.{format}

Send a transactional email

Sends a one-off transactional email to one or more recipients. Unlike campaigns, this endpoint does not require a list — the recipient address and HTML body are passed directly.

Required:

EmailData format: [email protected],Alice,Dupont,Paris The first value is the email address; values 2–26 map to Field1–Field25 for personalization variables in your HTML template.

Multiple recipients: separate multiple EmailData entries with semicolons: [email protected],Alice;[email protected],Bob

Scheduling: set DatePlanned to a future ISO 8601 date/time, or omit to send immediately.

OAuth scope : email:send

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (multipart/form-data)

NameTypeRequiredDescription
IDEmailExpintegeryesID of the sender email address to use (from /senderEmail/list)
EmailDatastringyesRecipient in email,field1,field2,...,field25 format (comma-separated). The first value is the email address; remaining values are personalization fields mapped to Field1–Field25 in your HTML template. Local part of the email (before @) accepts letters, digits and . _ - + — including RFC 5322 sub-addressing (user+tag@domain, e.g. Gmail, Fastmail, Proton). Other special characters RFC 5322 permits (e.g. ! # $ % & ' * / = ? ^ { | } ~) and quoted local parts are not accepted.
BodyHTMLstringyesFull HTML content of the email body
SubjectstringyesEmail subject line (max 255 characters)
attachmentsarray of string (binary)Optional file attachments — send the request as multipart/form-data and add one file part per attachment (any field name). Up to 5 files, 10 MB per file, 15 MB total. Accepted types: PDF, Word, Excel, PowerPoint, images (JPG, PNG, GIF, WebP), ZIP, CSV, TXT — executable and script files are rejected. Billing: the send keeps its usual 1 credit, plus 1 credit per 512 KB of attachments beyond the first 512 KB (attachments up to ~512 KB add nothing). Every other field of this endpoint (ReplyTo, ActivateStatistics, CustomHeaders, …) works exactly as in the form-urlencoded variant.
KeepInMyFilesbooleanfalse (default): the attachments are used for this send only — they are stored outside your account, do not count toward your storage quota and are deleted automatically after 35 days. true: the attachments are also kept in your account's files (folder Secure/Files/Sending/api) and count toward your storage quota.

Responses

CodeDescription
200Transactional email sent or scheduled successfully
400Missing required field or invalid email format
401Invalid credentials
403Not enough email credits
404Sender not found or email address unsubscribed

Example 200

{
  "SingleSend": {
    "IdSingleSend": 8001,
    "IdEmailExp": 201,
    "DatePlanned": "2026-04-15T14:30:00",
    "Subject": "Your order #12345 is confirmed",
    "Email": "[email protected]",
    "Field1": "Alice",
    "Field2": "Dupont",
    "UnsubscriptionLink": false,
    "ActivateStatistics": true
  }
}

GET /send/{IdSend}/status.{format}

Get delivery status of a transactional email

Returns the current delivery status of a transactional email sent with /send/sendmail. Pass the IdSingleSend that call returned.

Possible values: delivered (accepted by the recipient server, see DiagCode), bounced (rejected — BounceType: hard invalid address or permanent refusal, soft refused after retries), sent (handed to the mail server, no answer yet), scheduled (not sent yet, DatePlanned in the future) and queued (waiting to be dispatched). delivered and bounced are final: stop polling. Every email has a final status at most 48 hours after sending; poll every 15 minutes at most (each call counts against your API quota) or use the email.delivered / email.bounced webhooks. Only the client that sent the email can read its status — any other id returns 404.

OAuth scope : email:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
IdSendpathintegeryesThe IdSingleSend returned by /send/sendmail.

Responses

CodeDescription
200Current delivery status
401Invalid credentials
404No transactional send found with this id for this client

Example 200

{
  "status": {
    "IdSingleSend": 8001,
    "Email": "[email protected]",
    "Status": "delivered",
    "DatePlanned": "2026-04-15T14:30:00",
    "StatusDate": "2026-04-15T14:30:07",
    "DiagCode": "2.0.0 (success)",
    "BounceType": null
  }
}

Sender Addresses

List and manage validated sender email addresses. Only verified addresses can be used as the From address in campaigns.

GET /senderEmail/list.{format}

List validated sender addresses

Returns the list of sender email addresses configured for the account. Only addresses with IsValid: true can be used when sending campaigns. New addresses are added with /senderEmail/add and become valid once the link in the validation email is clicked.

OAuth scope : email:templates:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
emailFilterquerystringFilter by email address (partial, case-insensitive match)
numberOfRecordsqueryintegerItems per page — 5,000 by default and at most (see Pagination).
pageNumberqueryintegerPage number (1-based). Default is 1.
sortByNamequerybooleanSet to true to sort results by sender name alphabetically

Responses

CodeDescription
200Sender addresses returned successfully
401Invalid credentials

Example 200

{
  "ExpEmailList": [
    {
      "IDEmailExp": 201,
      "Email": "[email protected]",
      "SenderName": "Your Company Newsletter",
      "IsValid": true,
      "CreationDate": "2025-06-01T09:00:00"
    },
    {
      "IDEmailExp": 202,
      "Email": "[email protected]",
      "SenderName": "Your Company Support",
      "IsValid": true,
      "CreationDate": "2025-06-01T09:05:00"
    }
  ]
}

POST /senderEmail/add.{format}

Add a sender address

Adds the address with the same checks as the app and sends it a validation email. It can be used once the link in that email is clicked. An account can have up to 25 senders, validated or pending.

OAuth scope : email:templates:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
EmailstringyesSender address
SenderNamestringyesName shown to recipients

Responses

CodeDescription
200Address added. DomainHasMx is false when the domain has no MX record.
400Email or SenderName missing
401Invalid credentials
422Address refused: invalid, already among the senders of the account, public mail domain, forbidden name, or limit of 25 reached. The message gives the reason.
503Temporarily unavailable, please try again

Example 200

{
  "Result": "OK",
  "ConfirmationSent": true,
  "DomainHasMx": true
}

POST /senderEmail/{IdSenderEmail}/resendValidation.{format}

Send the validation email again

For an address still waiting for validation.

OAuth scope : email:templates:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
IdSenderEmailpathintegeryesID of the sender, from /senderEmail/list

Responses

CodeDescription
200Email sent again
401Invalid credentials
404Sender not found
409This sender is already validated
503Temporarily unavailable, please try again

Example 200

{
  "Result": "OK",
  "ConfirmationSent": true
}

GET /senderEmail/{IdSenderEmail}/authentication.{format}

Check the domain of a sender (SPF, DKIM, DMARC, MX)

Checks the domain live in DNS and returns the records to publish. Read-only. DKIM is activated in the app: DkimSettingsUrl opens the right screen.

OAuth scope : email:templates:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
IdSenderEmailpathintegeryesID of the sender, from /senderEmail/list

Responses

CodeDescription
200Status of the domain. Spf.RecommendedRecord is built on your current record: publish it only when Spf.ChangeNeeded is true.
401Invalid credentials
404Sender not found
503Temporarily unavailable, please try again

Example 200

{
  "Domain": "example.com",
  "Spf": {
    "Valid": true,
    "Record": "v=spf1 include:spf.mailpro.com -all",
    "RecommendedRecord": "v=spf1 include:spf.mailpro.com -all",
    "ChangeNeeded": false
  },
  "Dkim": {
    "Valid": true,
    "Records": [
      {
        "Host": "key._domainkey.example.com",
        "Value": "v=DKIM1;k=rsa;p=MIIBIjANBgkq...",
        "Validated": true
      }
    ]
  },
  "Dmarc": {
    "Valid": true,
    "Record": "v=DMARC1; p=quarantine"
  },
  "Mx": {
    "Valid": true,
    "Record": "mail.example.com"
  },
  "DkimSettingsUrl": "https://app.mailpro.com/dkim/list?d=example.com"
}

POST /senderEmail/del.{format}

Remove a sender address

Removes a validated sender email address from the account. Sender addresses used by scheduled campaigns cannot be deleted.

OAuth scope : email:templates:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
idEmailExpintegeryesID of the sender address to remove (from /senderEmail/list)

Responses

CodeDescription
200Sender address removed successfully
400Missing idEmailExp
401Invalid credentials

Example 200

{
  "Result": "EmailSender Deleted"
}

Statistics

Access per-campaign performance metrics: opens, clicks, bounces, and more. Detailed breakdowns by hour, day, country, OS, and email client are available.

GET /stats/list.{format}

List campaign statistics

Returns per-campaign performance metrics: sent, opens, clicks, bounces (BadMail), unsubscribes, complaints, spam-trap hits and the deliverability score. Filter by date range or campaign title. Results are ordered by send date (most recent first).

OAuth scope : email:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
titleCampaignFilterquerystringFilter by campaign title (partial, case-insensitive match)
numberOfRecordsqueryintegerItems per page — 5,000 by default and at most (see Pagination).
pageNumberqueryintegerPage number (1-based). Default is 1.
dateStartquerystring (date-time)Start of date range filter (ISO 8601)
dateEndquerystring (date-time)End of date range filter (ISO 8601)

Responses

CodeDescription
200Campaign statistics returned successfully
401Invalid credentials

Example 200

{
  "Campaigns": [
    {
      "IdCampaign": 8001,
      "DateCampaign": "2026-04-20T09:00:01",
      "TitleCampaign": "Spring Newsletter April 2026",
      "NumberEmailSend": 4521,
      "NumberEmailOpen": 1823,
      "NumberEmailClick": 342,
      "BadMail": 12,
      "NumberUnsubscribers": 8,
      "NumberComplaints": 1,
      "SpamTrap": 0,
      "DeliverabilityScore": 98
    }
  ]
}

GET /stats/byHour.{format}

Statistics by hour

Returns the hourly open and click breakdown for a specific campaign. Useful for understanding the optimal send time.

OAuth scope : email:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
idCampaignqueryintegeryesID of the campaign (from /stats/list)

Responses

CodeDescription
200Hourly statistics returned successfully
401Invalid credentials

Example 200

{
  "ByHour": [
    {
      "Hour": 9,
      "Opens": 823,
      "Clicks": 142
    },
    {
      "Hour": 10,
      "Opens": 512,
      "Clicks": 98
    }
  ]
}

GET /stats/byDay.{format}

Statistics by day

Returns the daily open and click breakdown for a specific campaign.

OAuth scope : email:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
idCampaignqueryintegeryesID of the campaign (from /stats/list)

Responses

CodeDescription
200Daily statistics returned successfully
401Invalid credentials

POST /stats/del.{format}

Delete campaign statistics

Permanently deletes all statistics records for a given campaign.

OAuth scope : email:templates:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
IdCampaignintegeryesID of the campaign whose statistics to delete (from /stats/list)

Responses

CodeDescription
200Campaign statistics deleted successfully
400Missing IdCampaign
401Invalid credentials
404Campaign not found

Example 200

{
  "Result": "Statistic Deleted"
}

GET /stats/country.{format}

Geolocation breakdown

Returns the geolocation breakdown of campaign opens — how many opens came from each country.

OAuth scope : email:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
idCampaignqueryintegeryes

Responses

CodeDescription
200Country breakdown returned
401Invalid credentials

GET /stats/open.{format}

Open events log

Returns the per-recipient log of open events for a campaign (one row per open). Use emailFilter to search for a specific recipient (partial, case-insensitive).

OAuth scope : email:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
idCampaignqueryintegeryes
emailFilterquerystring
numberOfRecordsqueryintegerItems per page — 5,000 by default and at most (see Pagination).
pageNumberqueryintegerPage number (1-based). Default is 1.

Responses

CodeDescription
200Open events returned
401Invalid credentials

GET /stats/click.{format}

Click events log

Returns the per-recipient log of click events for a campaign — including which link was clicked and when.

OAuth scope : email:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
idCampaignqueryintegeryes
emailFilterquerystring
numberOfRecordsqueryintegerItems per page — 5,000 by default and at most (see Pagination).
pageNumberqueryintegerPage number (1-based). Default is 1.

Responses

CodeDescription
200Click events returned
401Invalid credentials

GET /stats/system.{format}

Operating system breakdown

Returns the breakdown of opens by operating system (Windows, macOS, iOS, Android, Linux, etc.) for a given campaign.

OAuth scope : email:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
idCampaignqueryintegeryes

Responses

CodeDescription
200OS breakdown returned
401Invalid credentials

GET /stats/emailSoftware.{format}

Email client breakdown

Returns the breakdown of opens by email client (Gmail, Outlook, Apple Mail, Thunderbird, etc.) for a given campaign.

OAuth scope : email:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
idCampaignqueryintegeryes

Responses

CodeDescription
200Email client breakdown returned
401Invalid credentials

GET /stats/bounce.{format}

Bounce log

Returns the list of bounced recipients (hard bounces, soft bounces, spam complaints) for a campaign, with the failure reason for each address.

OAuth scope : email:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
idCampaignqueryintegeryes
emailFilterquerystring
numberOfRecordsqueryintegerItems per page — 5,000 by default and at most (see Pagination).
pageNumberqueryintegerPage number (1-based). Default is 1.

Responses

CodeDescription
200Bounce events returned
401Invalid credentials

Example 200

{
  "ListBadMail": [
    {
      "Email": "[email protected]",
      "Date": "2026-04-20T09:05:12",
      "Error": "550 5.1.1 recipient rejected",
      "AddressbookId": 1813318
    }
  ]
}

History

Browse the complete log of all past email sends.

GET /history/list.{format}

List sending history

Returns a paginated log of all past email sends. Filter by campaign ID or by subject. Set bodyHTML=true to include the HTML body (automatically limits to 10 per page).

OAuth scope : email:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
subjectMessageFilterquerystringFilter by campaign subject (partial, case-insensitive match)
idCampaignqueryintegerReturn history for a specific campaign only
numberOfRecordsqueryintegerItems per page — 5,000 by default and at most (see Pagination).
pageNumberqueryintegerPage number (1-based). Default is 1.
bodyHTMLquerybooleanThe MessageBody field is returned by default. Set to false to omit it — useful to cut the response size when you only need the metrics.

Responses

CodeDescription
200History returned successfully
401Invalid credentials

Example 200

{
  "HistoricList": [
    {
      "IDHistoric": 9001,
      "SubjectMessage": "Spring Newsletter — April 2026",
      "DateSend": "2026-04-20T09:00:01",
      "NumberSend": 4521,
      "NumberOpen": 1823,
      "NumberClick": 342
    }
  ]
}

Import

Upload a CSV or XLSX file to import email contacts into a list in bulk. The file is queued for asynchronous processing — track completion via the ImportJobId returned or via the optional WebhookUrl callback.

POST /import/upload.{format}

Upload a contacts file (CSV / XLSX) for bulk import

Upload a CSV or XLSX file to import email contacts into a list. The file is stored and queued for asynchronous processing — the endpoint returns an ImportJobId immediately.

Request type: multipart/form-data

Required form fields:

Optional:

File format:

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (multipart/form-data)

NameTypeRequiredDescription
ListIdintegeryesID of the destination list. Legacy alias AddressBookId is also accepted.
filestring (binary)yesCSV or XLSX file (UTF-8, max 50 MB)
WebhookUrlstring (uri)Callback URL invoked when processing finishes

Responses

CodeDescription
200File accepted and queued
400No file uploaded or missing ListId
401Invalid credentials
500Server or storage error

Example 200

{
  "Result": "OK",
  "ImportJobId": 13,
  "InternalName": "230575-20260420140221-contacts.csv"
}

Webhooks

Subscribe your server to real-time event notifications. Mailpro POSTs an HMAC-signed JSON payload to your URL as soon as the subscribed event happens. See the "Webhooks" section in the introduction for the full event catalog, payload envelope, signature verification snippets, and retry policy.

GET /webhooks

List webhooks

Returns every webhook configured on the authenticated account.

Responses

CodeDescription
200Webhooks of the account
401Invalid credentials

Example 200

{
  "items": [
    {
      "id": 14,
      "url": "https://example.com/hooks/mailpro",
      "description": "Slack integration",
      "is_active": true,
      "created_at": "2026-07-01T21:09:52.43",
      "updated_at": "2026-07-21T18:56:08.02",
      "last_delivery_at": "2026-07-21T18:56:07.653",
      "last_delivery_status": 200,
      "consecutive_failures": 0,
      "events": [
        "email.delivered",
        "email.bounced"
      ]
    }
  ],
  "total": 1
}

POST /webhooks

Create a webhook

Creates a new webhook subscription. The response contains a secret that is shown only once — store it immediately, server-side. Use it to verify the HMAC signature of every incoming payload.

Limits: max 20 webhooks per account, max 50 events per webhook.

Request body (application/json)

NameTypeRequiredDescription
urlstring (uri)yesHTTPS endpoint (HTTP refused in production)
eventsarray of stringyesList of event types to subscribe to (see catalog above). Use ["*"] to subscribe to all events of this API.
descriptionstring

Example

{
  "url": "https://example.com/hooks/mailpro",
  "events": [
    "email.delivered",
    "email.bounced"
  ],
  "description": "Slack integration"
}

Responses

CodeDescription
201Webhook created — secret is returned only here
400Invalid URL, events list, or limit exceeded
401Invalid credentials

Example 201

{
  "id": 42,
  "url": "https://example.com/hooks/mailpro",
  "events": [
    "email.delivered",
    "email.bounced"
  ],
  "description": "Slack integration",
  "isActive": true,
  "consecutiveFailures": 0,
  "autoDisabledAt": null,
  "autoDisabledReason": null,
  "createdAt": "2026-05-25T10:00:00Z",
  "updatedAt": "2026-05-25T10:00:00Z",
  "secret": "whsec_AbcD1234EfgH5678IjkL9012MnoP3456QrsT"
}

GET /webhooks/{id}

Get a webhook

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Responses

CodeDescription
200The webhook
404Webhook not found

PUT /webhooks/{id}

Update a webhook

Partial update — only the sent fields are modified.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Request body (application/json)

NameTypeRequiredDescription
urlstring (uri)
eventsarray of string
descriptionstring
isActivebooleanSet true to re-enable an auto-disabled webhook once your endpoint is fixed.

Responses

CodeDescription
200Updated webhook
400Invalid payload
404Webhook not found

DELETE /webhooks/{id}

Delete a webhook

Permanently deletes the webhook and its full delivery history.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Responses

CodeDescription
204Deleted
404Webhook not found

POST /webhooks/{id}/test

Send a test event

Queues an immediate ping event (or any other event type via the eventType body field) to your endpoint. Useful to verify reachability and signature verification before relying on real events.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Request body (application/json)

NameTypeRequiredDescription
eventTypestring

Example

{
  "eventType": "ping"
}

Responses

CodeDescription
202Event queued for dispatch
404Webhook not found

Example 202

{
  "deliveryId": 1234,
  "eventId": "evt_8f3a9b2c4d1e5f6789abcdef01234567",
  "eventType": "ping"
}

POST /webhooks/{id}/rotate-secret

Rotate the HMAC secret

Generates a new HMAC secret and invalidates the old one immediately. The new secret is returned once in the response — store it. Every delivery from this point on will be signed with the new secret.

Use this if you suspect your secret has been compromised.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Responses

CodeDescription
200New secret (visible once)
404Webhook not found

Example 200

{
  "secret": "whsec_XyzA9876BcdE5432FghI1098JklM7654NopQ"
}

GET /webhooks/{id}/deliveries

List delivery attempts

Returns the history of dispatch attempts for this webhook (30-day retention). Useful for debugging — which events were sent, status codes received, retry counts, error messages.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes
limitqueryinteger
cursorqueryinteger (int64)Pagination cursor (= last delivery ID seen). Returns deliveries with id < cursor.

Responses

CodeDescription
200Array of delivery attempts (newest first)
404Webhook not found

POST /webhooks/{id}/deliveries/{deliveryId}/replay

Replay a delivery

Queues a fresh delivery attempt with the same payload as the original. Useful to re-trigger a failed delivery after fixing your endpoint. A NEW Webhook_Delivery row is created (the original is preserved for audit). The replay goes through the standard retry policy (same backoff schedule, same 10-failure auto-disable rule).

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes
deliveryIdpathinteger (int64)yes

Responses

CodeDescription
202Replay queued
404Webhook or delivery not found

Suppression

Bounces and unsubscribers of the account: list, add, remove.

GET /suppression/bounces.{format}

List the bounces of the account

Most recent first. The total is in the X-Total-Count header.

OAuth scope : email:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
typequerystring: hard, softhard or soft
sourcequerystringWhere the bounce comes from: list, contact, api or smtp
emailquerystringExact address
dateFromquerystring (date-time)From this date
dateToquerystring (date-time)Up to this date
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200Bounces returned
400type must be hard or soft
401Invalid credentials

Example 200

{
  "bounces": [
    {
      "Id": 5521,
      "Source": "contact",
      "BounceType": "hard",
      "Email": "[email protected]",
      "Date": "2026-10-12T08:30:00",
      "DiagCode": "550 5.1.1 User unknown",
      "IdList": 101
    }
  ]
}

POST /suppression/bounces/{Id}/del.{format}

Remove a bounce

Source and Type are required, as returned by /suppression/bounces: an Id alone is not unique.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
Idpathinteger (int64)yesId of the bounce

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
Sourcestringyeslist, contact, api or smtp
Typestring: hard, softyeshard or soft

Responses

CodeDescription
200Bounce removed
400Source or Type missing
401Invalid credentials
404Bounce not found

Example 200

{
  "Result": "OK",
  "Id": 5521,
  "Restored": false
}

POST /suppression/bounces/{Id}/restore.{format}

Put a contact with a hard bounce back in its lists

Only for a hard bounce with the source contact.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
Idpathinteger (int64)yesId of the bounce

Responses

CodeDescription
200Contact put back
401Invalid credentials
404Hard bounce of a contact not found, or address no longer active

Example 200

{
  "Result": "OK",
  "Id": 5521,
  "Restored": true
}

GET /suppression/unsubscribers.{format}

List the unsubscribers of the account

Most recent first. The total is in the X-Total-Count header.

OAuth scope : contacts:read

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
sourcequerystringWhere the unsubscribe comes from: contact, list, smtp, api or api3
emailquerystringExact address
dateFromquerystring (date-time)From this date
dateToquerystring (date-time)Up to this date
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200Unsubscribers returned
401Invalid credentials

Example 200

{
  "unsubscribers": [
    {
      "Id": 9012,
      "Source": "api",
      "Email": "[email protected]",
      "Date": "2026-10-20T16:05:00",
      "ListName": null,
      "Comment": "Asked by phone"
    }
  ]
}

POST /suppression/unsubscribers/add.{format}

Add an unsubscriber

Without ListId, the address is unsubscribed for the whole account. With it, from that unsubscribe list only.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
EmailstringyesAddress to unsubscribe
CommentstringFree comment
ListIdinteger (int64)Unsubscribe list of the account (optional)

Responses

CodeDescription
200Done. AlreadyUnsubscribed is true when the address already was.
400A valid email is required
401Invalid credentials
404ListId is not an unsubscribe list of this account

Example 200

{
  "Result": "OK",
  "AlreadyUnsubscribed": false
}

POST /suppression/unsubscribers/{Id}/del.{format}

Remove an unsubscriber

Source is required, as returned by /suppression/unsubscribers: an Id alone is not unique.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
formatpathstring: json, xmlyesResponse format
Idpathinteger (int64)yesId of the unsubscriber

Request body (application/x-www-form-urlencoded)

NameTypeRequiredDescription
Sourcestringyescontact, list, smtp, api or api3

Responses

CodeDescription
200Unsubscriber removed
400Source is required
401Invalid credentials
404Unsubscriber not found

Example 200

{
  "Result": "OK",
  "Removed": 9012
}