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

Mailpro™ CRM API v3 : Reference

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

The Mailpro™ CRM API v3 lets you programmatically manage contacts, lists, tags, custom fields, email templates, and campaigns — and send both bulk marketing emails and individual transactional emails, plus SMS.

Authentication

All endpoints (except POST /token and the OAuth endpoints) accept three authentication methods. Use whichever fits your stack.

> Legacy Bearer JWT and IdClient+ApiKey are not scope-gated — they keep > full access to every endpoint they could already reach. Only OAuth 2.0 > access tokens are subject to the per-endpoint scope checks shown on each > operation below (a token missing the scope gets 403 insufficient_scope).

Method 1 — OAuth 2.0 (recommended for integrations)

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

EndpointURL
AuthorizeGET https://api.mailpro.com/oauth/authorize
Token / RefreshPOST https://api.mailpro.com/oauth/token
JWKS (verify signatures)GET https://api.mailpro.com/oauth/.well-known/jwks
DiscoveryGET https://api.mailpro.com/oauth/.well-known/openid-configuration

Issuer (the iss claim on every token): https://api.mailpro.com/oauth/.

Supported grants

Token lifetimes

TokenTTL
Access token (RS256 JWT)20 minutes
Refresh token (sliding, single-use)30 days
Authorization code60 seconds

Authorization Code + PKCE — step by step

   GET https://api.mailpro.com/oauth/authorize
     ?response_type=code
     &client_id=YOUR_CLIENT_ID
     &redirect_uri=https://yourapp.example.com/callback
     &scope=contacts:read contacts:write
     &state=RANDOM_CSRF_TOKEN
     &code_challenge=CODE_CHALLENGE
     &code_challenge_method=S256
   POST https://api.mailpro.com/oauth/token
   Content-Type: application/x-www-form-urlencoded

   grant_type=authorization_code&code=AUTH_CODE
   &redirect_uri=https://yourapp.example.com/callback
   &client_id=YOUR_CLIENT_ID&code_verifier=CODE_VERIFIER
   { "access_token": "eyJ…", "token_type": "Bearer",
     "expires_in": 1200, "refresh_token": "…" }

client_credentials

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

grant_type=client_credentials&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET&scope=contacts:read email:send

The same OAuth access token is accepted by the Email v2 (api.mailpro.com/v2) and SMS (api.mailpro.com/sms) APIs too — authenticate once, call any API. Full scope catalog: see OAuth 2.0 scopes below.

Test OAuth 2.0 in minutes — quickstart

The fastest way to try OAuth is the client_credentials grant (server-to-server, no browser, no user). You need a client_id / client_secret pair (ask your Mailpro contact, or create one in Settings → API access). Replace the placeholders below — never commit a real secret.

1. Get an access token

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=contacts:read"
{ "access_token": "eyJhbGciOiJSUzI1Ni…", "token_type": "Bearer",
  "expires_in": 1200 }

2. Call a resource endpoint with the token

curl https://api.mailpro.com/v3/List \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1Ni…"

The same token also works on Email v2 (…/v2/…) and SMS (…/sms/…) — as long as it carries the scope each endpoint requires. A token missing the required scope is rejected with 403 insufficient_scope.

Interactive "Connect Mailpro" (authorization_code + PKCE) — when you act on a *user's* behalf (Zapier/Make/n8n, partner apps): redirect the user to GET https://api.mailpro.com/oauth/authorize with response_type=code, client_id, redirect_uri, scope, state, code_challenge and code_challenge_method=S256; the user consents; you exchange the returned code at POST https://api.mailpro.com/oauth/token with the matching code_verifier. Full walk-through above; scopes in OAuth 2.0 scopes below.

> Tokens are RS256 JWTs — access tokens live 20 minutes, refresh > tokens 30 days (sliding, single-use: each refresh rotates the token). > Verify signatures against the JWKS at > https://api.mailpro.com/oauth/.well-known/jwks and validate the scope of > each request against the scope matrix below.

Method 2 — Bearer JWT (legacy)

Authorization: Bearer <access_token>

Get a token via POST /token (see below). Valid for 30 days by default; use grant_type=refresh_token to renew without re-authenticating. Simpler to set up, but tied to a single account credential and cannot be scoped or handed to a third party — prefer OAuth 2.0 for any installable integration.

Method 3 — IdClient + ApiKey (simpler)

Send your client ID and API key as HTTP headers (preferred) or as query parameters:

IdClient: 12345
ApiKey:   a1b2c3d4-e5f6-7890-abcd-ef1234567890

Same credentials as the v2 / SMS APIs — copy-paste from Settings → API access. No token exchange step. Header form is recommended because URLs are logged by IIS / Cloudflare / proxies.

Token endpoint (password grant)

POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=YOUR_USERNAME&password=YOUR_API_KEY

client_credentials

POST /v3/token does not accept grant_type=client_credentials. For server-to-server access use the OAuth 2.0 endpoint POST https://api.mailpro.com/oauth/token with your client_id and client_secret (see the OAuth quickstart above).

> The token issued by POST /v3/token is also valid on the Email v2 API > (api.mailpro.com/v2) and the SMS API (api.mailpro.com/sms). Authenticate > once, call any API.

OAuth 2.0 scopes

When your app requests an OAuth access token it declares a set of scopes — permissions that define exactly what the token may do. The user sees a plain-language description of each requested scope on the consent screen. Scopes are space-delimited in the scope parameter (scope=contacts:read email:send). Request only what you need.

Scopes follow <resource>:<action> and are hierarchically independent — contacts:write does not imply contacts:read; request both if needed.

ScopeAllows
contacts:readView your contacts, lists, tags, segments and custom fields.
contacts:writeCreate, edit and delete your contacts, lists, tags and custom fields.
email:sendSend transactional and campaign emails on your behalf.
email:readView delivery stats, opens, clicks and bounce history.
email:templates:readView your email templates (subject, HTML, plain-text body).
email:templates:writeCreate, edit and delete your email templates.
sms:sendSend transactional and campaign SMS messages on your behalf.
sms:readView SMS credit balance and message history.
sms:contacts:readView your SMS phone book.
sms:contacts:writeCreate, edit and delete entries in your SMS phone book.
webhooks:manageSubscribe to, edit and remove webhook endpoints for your account.
account:readView your account credits, limits and subscription status.

Scope enforcement per endpoint

Scope checks are live and enforced per endpoint for OAuth-issued tokens (legacy auth is exempt). Each operation in this reference lists its required scope under Authorization. Summary for the CRM v3 surface:

EndpointsRequired scope
GET /Contact/{id}, GET /List, GET /List/{id}, GET /List/{id}/Contact, GET /List/{listId}/Unsubscribers, GET /Tag, GET /Tag/{id}, GET /Tag/{id}/Contact, GET /Segment, segment membership checkcontacts:read
POST /Contact, POST /Contact/Multiple, PUT/DELETE /Contact/{id}, add/remove tag or list on a contact, POST/PUT/DELETE /List, POST/PUT/DELETE /Tagcontacts:write

> Endpoints in this spec that are not listed above (e.g. GET /Field, > campaigns, transactional send, SMS send, message templates, account info) > are reachable by any valid token today — they are not yet scope-gated on > the CRM v3 surface. The Email v2 and SMS APIs additionally gate their own > send / template / contacts / account endpoints (see those specs).

Scope rejection returns 403 with { "error": "insufficient_scope", "required_scope": "…" } (RFC 6750 §3.1).

Data format

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.

Business error codes

Business rule violations return HTTP 406 with a JSON body:

{ "Message": "Contact cannot be created. Email cannot be empty." }

Common 406 reasons:

Status codes returned

CodeMeaning
400Malformed request (bad JSON, message over the SMS part limit)
401Missing, invalid or expired credentials
402Not enough credits to perform the send
403Allowed by your credentials, but refused — account blocked for SMS sending
404The referenced object does not exist (contact, list, tag, field, campaign)
406Contact business rule violation (see the list above)
422Input rejected by validation, or an account limit reached

> Changed: some of these used to return 404. Insufficient credits now > return 402, validation errors and account limits return 422, and an > account blocked for SMS returns 403. 404 is now used only for "this > object does not exist", consistently with the SMS API. If your integration > branches on 404 for those cases, update it to the codes above.

Phone number format

Phone numbers are normalized automatically. You can send them in many formats — spaces, dashes, dots, slashes, parentheses, underscores and non-breaking spaces are stripped before validation. Any other character (letters, apostrophes, =, ;, etc.) makes the number invalid.

ValidInvalid
+41794849980 — E.164 international0612345678 — national format (leading 0)
0041 79 484 99 80 — 00-prefix + spaces41'794'849'80 — apostrophes (not stripped)
+33 6-12-34-56-78 — + with dashestel=0041794849980 — extra characters
41794849980 — bare digits1234567 — too short (< 8 digits)

Rules:

v3 Contact specifics: the phone_number field on a Contact is currently limited to 12 digits after normalization (vs 15 for the standalone SMS endpoints). Numbers from countries like Germany (13), Brazil (13) or Italy mobile (12–13) may be rejected on /v3/Contact with Length exceeded for Phone Number (12). This legacy constraint may be relaxed in a future release.

Limits

ResourceLimit
Tags per account1,000
Lists per account1,000
Custom fields per account50
Monthly API requests per account50,000 (5× subscription email credits)

Delivery tracking (transactional emails)

Every single email you send (POST /Send/Mail, the Email API v2, 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 (date_planned).no
sentHanded over by our servers, no answer from the recipient server yet.no
deliveredAccepted by the recipient server (diag_code = its SMTP reply).yes
bouncedRejected. bounce_type: 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 (a contact is created, an email is delivered, a campaign finishes, 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":       "contact.created",
  "created":    1748194800,
  "apiVersion": "v3",
  "account":    { "id": 12345, "name": "ACME Corp" },
  "data":       { "id": "...", "email": "[email protected]", "...": "..." }
}
FieldFormatDescription
idevt_ + 32 hexUnique event ID — use as an idempotency key on your side.
type<domain>.<verb>Canonical event type (e.g. email.delivered, contact.created).
createdUnix seconds UTCIncluded in the HMAC signature.
apiVersion"v3"Which Mailpro API published the event.
account.idintYour Mailpro client ID.
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 /Webhook/{id}/test{ message }
contact.createdPOST /Contact{ id, email, firstName, lastName, createdAt }
contact.updatedPUT /Contact/{id}{ id, email, firstName, lastName, updatedAt, changes }
contact.deletedDELETE /Contact/{id}{ id, email, deletedAt }
list.contact_addedContact added to list{ listId, listName, contactId, contactEmail, addedAt }
list.contact_removedContact removed from list{ listId, listName, contactId, contactEmail, removedAt }
tag.appliedTag applied to contact{ tag, contactId, contactEmail, appliedAt }
tag.removedTag removed{ tag, contactId, contactEmail, removedAt }
segment.matchedContact enters a segment{ segmentId, segmentName, contactId, contactEmail, matchedAt }
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 }
import.startedImport job started{ importId, fileName, totalRows, startedAt }
import.progressAt 25/50/75%{ importId, fileName, processedRows, totalRows, progressPct }
import.completedImport OK{ importId, fileName, totalRows, addedCount, updatedCount, errorCount, completedAt }
import.failedFatal error during import{ importId, fileName, errorMessage, failedAt }

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: contact.created
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 /Webhook/{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 token endpoint accepts multiple grant types: password, refresh_token, and third-party integrations (zapier, automateio, microsoft-flow). For client_credentials use /oauth/token.

POST /token

Get access token

Obtains a Bearer JWT token using client credentials. Pass the returned access_token as Authorization: Bearer <token> in all subsequent requests.

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
zapier / automateio / microsoft-flowThird-party platform integrations
Token lifetime

Use grant_type=refresh_token to obtain a fresh access token without re-authenticating — your SDK should do this automatically when the access token gets close to expiry (typical strategy: refresh when less than 24 h remaining).

Storing tokens securely

Even with a 30-day lifetime, anyone who obtains an access_token can impersonate your account until it expires. Store tokens in a server-side secret store (Vault, AWS Secrets Manager, Azure Key Vault, environment variables on a hardened host). Never embed a token in:

If a token is leaked, revoke it via your Mailpro account settings and request a new one.

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

NameTypeRequiredDescription
grant_typestring: password, refresh_token, client_credentials, zapier, automateio, microsoft-flowyesAuthentication grant type
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
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

Retrieve account profile, credit balance, and subscription details.

GET /Account/Me

Get account details

Returns the profile details and settings of the authenticated Mailpro account.

Responses

CodeDescription
200Account details returned successfully
401Invalid or expired token

GET /Account/Credits

Get credit balance

Returns the remaining email and SMS credit balance for the account.

Responses

CodeDescription
200Credit balance returned successfully
401Invalid or expired token

Example 200

{
  "email_credits": 48500,
  "sms_credits": 1200
}

GET /Account/SubscriptionEndDate

Get subscription end date

Returns the expiry date of the current Mailpro subscription.

Responses

CodeDescription
200Subscription end date returned successfully
401Invalid or expired token

GET /Account/Limits

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. The exact mapping is set by the billing job at subscription signup and reset on the subscription anniversary date (not the 1st of the calendar month).

What consumes credits:

What is FREE (does not consume credits):

Responses

CodeDescription
200Quota snapshot
401Invalid or expired token
404No quota record found for this account (newly created account that has never made an API call; the row will be created on the first authenticated request).

Contact

Full CRUD for CRM contacts. Contacts store an email address, optional phone number, custom field values, and belong to one or more Lists. They can also be labelled with Tags. Contact IDs are UUIDs.

POST /Contact

Create a contact

Creates a new CRM contact with optional custom fields, list assignments, and tags.

Required:

Phone numbers accept +41…, 0041… or 41… with or without separators (max 12 digits on Contact — see Phone number format in the API description).

Custom fields are identified by their id (from GET /Field). Values are validated against the field's type.

OAuth scope : contacts:write

Request body (application/json)

NameTypeRequiredDescription
emailstringyesContact's email address (required, must be unique)
phone_numberstringPhone number — accepts +41…, 0041… or 41… with or without separators. Must start with a valid country code. Max 12 digits on Contact (see Phone number format in the API description for full rules).
listsarray of ListRefyesLists to assign this contact to. At least one list is required.
tagsarray of TagRefTags to assign to this contact (optional)
custom_fieldsarray of FieldValueCustom field values for this contact (optional)

Example

{
  "email": "[email protected]",
  "phone_number": "+41791234567",
  "lists": [
    {
      "id": 101
    }
  ],
  "tags": [
    {
      "id": 5
    }
  ],
  "custom_fields": [
    {
      "id": 1,
      "value": "Alice"
    },
    {
      "id": 2,
      "value": "Dupont"
    }
  ]
}

Responses

CodeDescription
200Contact created successfully
400Invalid email format
401Invalid or expired token
406Business rule violation (see description for full list)

Example 406

{
  "Message": "Contact cannot be created. A contact already exist with the same Email"
}

POST /Contact/Multiple

Import contacts (bulk)

Creates up to 1,000 contacts in a single request for efficient bulk import. The response includes a list of successfully created contacts and a list of errors for contacts that could not be created (e.g. duplicate email, invalid data).

Note: lists and tags used in bulk imports must not be linked to active automation workflows. If they are, the entire import is rejected with HTTP 406.

OAuth scope : contacts:write

Request body (application/json)

Example

[
  {
    "email": "[email protected]",
    "lists": [
      {
        "id": 101
      }
    ],
    "custom_fields": [
      {
        "id": 1,
        "value": "Alice"
      }
    ]
  },
  {
    "email": "[email protected]",
    "lists": [
      {
        "id": 101
      }
    ],
    "custom_fields": [
      {
        "id": 1,
        "value": "Bob"
      }
    ]
  }
]

Responses

CodeDescription
200Bulk import completed (check errors array for partial failures)
400Invalid input
401Invalid or expired token
406Business rule violation (e.g. automation conflict, list limit reached)

Example 200

{
  "Added": [
    {
      "Email": "[email protected]",
      "Id": "550e8400-e29b-41d4-a716-446655440000"
    },
    {
      "Email": "[email protected]",
      "Id": "660e8400-e29b-41d4-a716-446655440001"
    }
  ],
  "Errors": []
}

GET /Contact/{id}

Get a contact

Returns the details of a specific contact. Use includeTags=true to also return the tags assigned to the contact. Use includeLists=true to also return the lists the contact belongs to.

OAuth scope : contacts:read

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesContact UUID (from POST /Contact or GET /List/{id}/Contact)
includeTagsquerybooleanSet to true to include the contact's tag assignments in the response
includeListsquerybooleanSet to true to include the contact's list memberships in the response

Responses

CodeDescription
200Contact returned successfully
401Invalid or expired token
404Contact not found

Example 200

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "email": "[email protected]",
  "phone_number": "41791234567",
  "custom_fields": [
    {
      "id": 1,
      "value": "Alice"
    },
    {
      "id": 2,
      "value": "Dupont"
    }
  ],
  "tags": [
    {
      "id": 5
    }
  ],
  "lists": [
    {
      "id": 101
    }
  ]
}

PUT /Contact/{id}

Update a contact

Updates a contact's email, phone number, custom fields, list assignments, and tags. The request body replaces the contact's current data — include all lists and tags you want the contact to remain assigned to.

Required in body: email and at least one lists entry.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesContact UUID

Request body (application/json)

NameTypeRequiredDescription
emailstringyesContact's email address (required, must be unique)
phone_numberstringPhone number — accepts +41…, 0041… or 41… with or without separators. Must start with a valid country code. Max 12 digits on Contact (see Phone number format in the API description for full rules).
listsarray of ListRefyesLists to assign this contact to. At least one list is required.
tagsarray of TagRefTags to assign to this contact (optional)
custom_fieldsarray of FieldValueCustom field values for this contact (optional)

Responses

CodeDescription
200Contact updated successfully
401Invalid or expired token
404Contact not found
422Validation error (email format, phone number, field value type mismatch)

Example 200

{
  "Result": "Successful operation"
}

DELETE /Contact/{id}

Delete a contact

Permanently deletes a contact from the CRM.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesContact UUID

Responses

CodeDescription
200Contact deleted successfully
401Invalid or expired token
404Contact not found

GET /Contact/{id}/Activity

Activity of a contact over 90 days

Emails received, opened and clicked, favorite email client and latest campaigns of the contact.

OAuth scope : contacts:read

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesContact GUID

Responses

CodeDescription
200Activity returned
401Invalid or expired token
404The requested contact does not exist

Example 200

{
  "period_days": 90,
  "email_received": 12,
  "email_opened": 7,
  "email_clicked": 2,
  "favorite_email_client": "Gmail",
  "last_campaigns": [
    {
      "id": 5001,
      "name": "Spring Sale 2026",
      "date": "2026-10-01T09:00:00"
    }
  ]
}

PUT /Contact/{id}/Tag/{tagid}

Add tag to contact

Assigns a tag to a contact. If the tag is already assigned, the operation is a no-op.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesContact UUID
tagidpathinteger (int64)yesTag ID (from GET /Tag)

Responses

CodeDescription
200Tag assigned successfully
401Invalid or expired token
404Contact or tag not found

DELETE /Contact/{id}/Tag/{tagid}

Remove tag from contact

Removes a tag assignment from a contact.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesContact UUID
tagidpathinteger (int64)yesTag ID

Responses

CodeDescription
200Tag removed successfully
401Invalid or expired token
404Contact or tag not found

PUT /Contact/{id}/List/{listid}

Add contact to list

Adds a contact to a specific list. Returns HTTP 404 if the contact was previously unsubscribed from this list.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesContact UUID
listidpathinteger (int64)yesList ID (from GET /List)

Responses

CodeDescription
200Contact added to list successfully
401Invalid or expired token
404Contact, list not found, or contact was unsubscribed from this list

DELETE /Contact/{id}/List/{listid}

Remove contact from list

Removes a contact from a specific list. The contact itself is not deleted.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesContact UUID
listidpathinteger (int64)yesList ID

Responses

CodeDescription
200Contact removed from list successfully
401Invalid or expired token
404Contact or list not found

List

Manage contact lists. Lists group contacts for campaigns and segmentation. Maximum 1,000 lists per account (same limit on the three Mailpro APIs and in the app). Maximum 1,000,000 contacts per list.

GET /List

List contact lists

Returns all contact lists for the account. Filter by name or creation date. Sort alphabetically with sortByName=true. Include the total contact count per list with includeTotalContacts=true.

OAuth scope : contacts:read

Parameters

NameInTypeRequiredDescription
namequerystringFilter by list name (partial match)
dateCreationFromquerystring (date-time)Filter by creation date (from, ISO 8601)
dateCreationToquerystring (date-time)Filter by creation date (to, ISO 8601)
numberOfRecordsqueryintegerItems per page — 5,000 by default and at most
pageNumberqueryinteger1-based page number
sortByNamequerybooleanSet to true to sort alphabetically by name
includeTotalContactsquerybooleanSet to true to include total_contacts count in each result

Responses

CodeDescription
200Lists returned successfully
401Invalid or expired token

Example 200

[
  {
    "id": 101,
    "name": "Newsletter subscribers",
    "total_contacts": 4521,
    "date_creation": "2025-01-15T10:30:00"
  }
]

POST /List

Create a contact list

Creates a new contact list. Maximum 1,000 lists per account.

OAuth scope : contacts:write

Request body (application/json)

NameTypeRequiredDescription
namestringyesDisplay name for the list

Example

{
  "name": "Spring Campaign 2026"
}

Responses

CodeDescription
200List created successfully
401Invalid or expired token
406List limit (1,000) reached

Example 200

{
  "id": 102,
  "name": "Spring Campaign 2026",
  "date_creation": "2026-04-14T12:00:00"
}

GET /List/{id}

Get a contact list

Returns the details of a specific contact list.

OAuth scope : contacts:read

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yesList ID

Responses

CodeDescription
200List returned successfully
401Invalid or expired token
404List not found

PUT /List/{id}

Update a contact list

Renames an existing contact list.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yesList ID

Request body (application/json)

NameTypeRequiredDescription
namestringyesDisplay name for the list

Example

{
  "name": "Newsletter subscribers — Europe"
}

Responses

CodeDescription
200List updated successfully
401Invalid or expired token
404List not found

DELETE /List/{id}

Delete a contact list

Permanently deletes a contact list. The contacts themselves are not deleted — only their membership in this list is removed. Lists used by active automation workflows cannot be deleted.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yesList ID

Responses

CodeDescription
200List deleted successfully
401Invalid or expired token
404List not found
406List is used by an active automation workflow

GET /List/{id}/Contact

List contacts in a list

Returns the contacts belonging to a specific list. Filter by email or date added. Include tags and list memberships with the corresponding flags.

OAuth scope : contacts:read

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yesList ID
emailquerystringFilter by email (partial match)
numberOfRecordsqueryintegerItems per page — 5,000 by default and at most
pageNumberqueryinteger
sortByEmailquerybooleanSort alphabetically by email
includeTagsquerybooleanInclude tag assignments in results
includeListsquerybooleanInclude list memberships in results

Responses

CodeDescription
200Contacts returned successfully
401Invalid or expired token
404List not found

GET /List/{listId}/Unsubscribers

List unsubscribers

Returns the contacts who have unsubscribed from a specific list.

OAuth scope : contacts:read

Parameters

NameInTypeRequiredDescription
listIdpathinteger (int64)yesList ID

Responses

CodeDescription
200Unsubscribers returned successfully
401Invalid or expired token
404List not found

Tag

Manage tags for contact labelling and segmentation. Tags are flat labels (e.g. "VIP", "Churned", "B2B"). Maximum 1,000 tags per account.

GET /Tag

List tags

Returns all tags for the account.

OAuth scope : contacts:read

Parameters

NameInTypeRequiredDescription
namequerystringFilter by name (partial match)
numberOfRecordsqueryinteger
pageNumberqueryinteger
sortByNamequeryboolean

Responses

CodeDescription
200Tags returned successfully
401Invalid or expired token

Example 200

[
  {
    "id": 5,
    "name": "VIP customers"
  },
  {
    "id": 6,
    "name": "Newsletter openers"
  }
]

POST /Tag

Create a tag

Creates a new tag. Maximum 1,000 tags per account.

OAuth scope : contacts:write

Request body (application/json)

NameTypeRequiredDescription
namestringyesDisplay name for the tag

Example

{
  "name": "VIP customers"
}

Responses

CodeDescription
200Tag created successfully
401Invalid or expired token
406Tag limit (1,000) reached

GET /Tag/{id}

Get a tag

Returns a specific tag by its ID.

OAuth scope : contacts:read

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yesTag ID

Responses

CodeDescription
200Tag returned successfully
401Invalid or expired token
404Tag not found

PUT /Tag/{id}

Update a tag

Renames an existing tag.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Request body (application/json)

NameTypeRequiredDescription
namestringyesDisplay name for the tag

Example

{
  "name": "VIP customers — updated"
}

Responses

CodeDescription
200Tag updated successfully
401Invalid or expired token
404Tag not found

DELETE /Tag/{id}

Delete a tag

Permanently deletes a tag. The tag is removed from all contacts. Tags used by active automation workflows cannot be deleted.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Responses

CodeDescription
200Tag deleted successfully
401Invalid or expired token
404Tag not found
406Tag is used by an active automation workflow

GET /Tag/{id}/Contact

List contacts with a tag

Returns the contacts that have been assigned a specific tag.

OAuth scope : contacts:read

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yesTag ID
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200Contacts returned successfully
401Invalid or expired token
404Tag not found

Field

Manage custom contact fields. Fields let you store additional data on each contact (e.g. first name, city, loyalty tier). Maximum 50 fields per account. Field types: text, number, date.

GET /Field

List custom fields

Returns all custom contact fields defined for the account. Use the returned id values when creating or updating contacts.

Parameters

NameInTypeRequiredDescription
namequerystringFilter by field name
idFieldTypequeryintegerFilter by field type ID
numberOfRecordsqueryinteger
pageNumberqueryinteger
sortByNamequeryboolean

Responses

CodeDescription
200Custom fields returned successfully
401Invalid or expired token

Example 200

[
  {
    "id": 1,
    "name": "first_name",
    "personalisation_key": "first_name",
    "field_type": "text"
  },
  {
    "id": 2,
    "name": "last_name",
    "personalisation_key": "last_name",
    "field_type": "text"
  }
]

POST /Field

Create a custom field

Creates a new custom contact field. Maximum 50 fields per account. Use the returned id when assigning values to contacts.

Request body (application/json)

NameTypeRequiredDescription
namestringyesDisplay name of the field
id_field_typeintegeryesField type ID (get available types from GET /Field/Types): - 1 = text - 2 = integer - 3 = date and time - 4 = yes / no - 12 = decimal
personalize_keystringyesTemplate variable key, used to insert the field in a message
sortintegerDisplay order (0 to 255). Required when updating a field (PUT /Field/{id}).

Example

{
  "name": "First name",
  "id_field_type": 1,
  "personalize_key": "first_name"
}

Responses

CodeDescription
200Field created successfully
401Invalid or expired token
406Field limit (50) reached

GET /Field/Types

List field types

Returns the available field type identifiers to use when creating a custom field. Common types: 1 = text, 2 = number, 3 = date.

Responses

CodeDescription
200Field types returned successfully
401Invalid or expired token

GET /Field/{id}

Get a custom field

Returns the definition and metadata of a specific custom field.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yesField ID

Responses

CodeDescription
200Field returned successfully
401Invalid or expired token
404Field not found

PUT /Field/{id}

Update a custom field

Updates the name or personalisation key of a custom field.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Request body (application/json)

NameTypeRequiredDescription
namestringyesDisplay name of the field
id_field_typeintegeryesField type ID (get available types from GET /Field/Types): - 1 = text - 2 = integer - 3 = date and time - 4 = yes / no - 12 = decimal
personalize_keystringyesTemplate variable key, used to insert the field in a message
sortintegerDisplay order (0 to 255). Required when updating a field (PUT /Field/{id}).

Responses

CodeDescription
200Field updated successfully
401Invalid or expired token
404Field not found

DELETE /Field/{id}

Delete a custom field

Permanently deletes a custom field and removes its value from all contacts. This action cannot be undone.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Responses

CodeDescription
200Field deleted successfully
401Invalid or expired token
404Field not found

Campaign

View campaign statistics and manage scheduled campaigns. Campaigns can be edited or deleted until 5 minutes before the scheduled send time.

GET /Campaign

List campaigns

Returns a paginated list of email campaigns with their performance metrics: sent, opens, clicks, unique clickers, bounces (count_badmail), unsubscribes, complaints, spam-trap hits and the deliverability score.

Parameters

NameInTypeRequiredDescription
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200Campaigns returned successfully
401Invalid or expired token

Example 200

[
  {
    "id": 8001,
    "title": "Spring Newsletter April 2026",
    "date_planned": "2026-04-20T09:00:01",
    "count_open": 1823,
    "count_sent": 4521,
    "count_click": 342,
    "count_badmail": 12,
    "count_unsubscription": 8,
    "count_clicking_users": 289,
    "count_complaints": 1,
    "spam_trap": 0,
    "deliverability_score": 98
  }
]

GET /Campaign/{id}

Get a campaign

Returns the configuration and statistics summary of a specific campaign.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Responses

CodeDescription
200Campaign returned successfully
401Invalid or expired token
404Campaign not found

PUT /Campaign/{id}

Update a campaign

Updates a scheduled campaign's title or send date. Campaigns can only be modified until 5 minutes before the scheduled send time.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Request body (application/json)

NameTypeRequiredDescription
campaign_titlestringDisplay name for the campaign
date_plannedstring (date-time)Scheduled send time (ISO 8601). Omit to send immediately.

Responses

CodeDescription
200Campaign updated successfully
401Invalid or expired token
404Campaign not found

DELETE /Campaign/{id}

Delete a campaign

Cancels and permanently deletes a campaign by its identifier.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Responses

CodeDescription
200Campaign deleted successfully
401Invalid or expired token
404Campaign not found

GET /Campaign/Scheduled

List my scheduled campaigns

Returns campaigns with date_planned in the future that have not yet been dispatched. Sorted by date_planned ascending.

Parameters

NameInTypeRequiredDescription
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200List of scheduled campaigns
401Invalid or expired token

Example 200

[
  {
    "id": 5001,
    "id_client": 12345,
    "subject": "Spring Sale 2026",
    "date_planned": "2026-05-01T09:00:00",
    "title": "Spring Sale",
    "id_campaign": 7001,
    "id_list": 101,
    "id_segment": null,
    "number_email": 1250
  }
]

POST /Campaign/{id}/Cancel

Cancel a scheduled campaign

Cancels a campaign scheduled in the future. Returns 409 Conflict if the campaign has already been dispatched or doesn't exist.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Responses

CodeDescription
200Campaign cancelled
401Invalid or expired token
409Already dispatched or not found

Example 200

{
  "result": "ok",
  "cancelled": 5001
}

PATCH /Campaign/{id}/Reschedule

Reschedule a scheduled campaign

Updates the planned send date of a scheduled campaign. The new date must be in the future and within 90 days.

Body field name: the API accepts date_planned (snake_case, primary) as well as the legacy aliases PlanDate and scheduled_time. Provide any one of them — the first non-null is used.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Request body (application/json)

NameTypeRequiredDescription
date_plannedstring (date-time)Primary field name.
PlanDatestring (date-time)Legacy alias accepted for backward compatibility.
scheduled_timestring (date-time)Legacy alias accepted for backward compatibility.

Responses

CodeDescription
200Campaign rescheduled
400Missing date — provide one of date_planned / PlanDate / scheduled_time
401Invalid or expired token
409Already dispatched

Example 200

{
  "result": "ok",
  "id": 5001,
  "date_planned": "2026-12-31T14:30:00"
}

GET /Campaign/{id}/ByHour

Statistics by hour

Returns the hourly open and click breakdown for the campaign.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Responses

CodeDescription
200Hourly statistics returned successfully
401Invalid or expired token

GET /Campaign/{id}/ByDay

Statistics by day

Returns the daily open and click breakdown for the campaign.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Responses

CodeDescription
200Daily statistics returned successfully
401Invalid or expired token

GET /Campaign/{id}/Openings

List email opens

Returns a paginated list of contacts who opened the campaign email.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes
emailquerystringFilter by recipient email
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200Open list returned successfully
401Invalid or expired token

GET /Campaign/{id}/Clicks

List link clicks

Returns a paginated list of contacts who clicked a link in the campaign.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes
emailquerystring
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200Click list returned successfully
401Invalid or expired token

GET /Campaign/{id}/HardBounces

List hard bounces

Returns the email addresses that hard-bounced during this campaign.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes
emailquerystring
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200Hard bounce list returned successfully
401Invalid or expired token

List tracked links

Returns click performance data for each tracked link in the campaign.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes
linkquerystringFilter by link URL (partial match)
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200Link statistics returned successfully
401Invalid or expired token

GET /Campaign/{id}/OS

Statistics by OS / device

Returns the open and click breakdown by operating system and device type.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Responses

CodeDescription
200OS statistics returned successfully
401Invalid or expired token

GET /Campaign/{id}/EmailClients

Statistics by email client

Returns the open breakdown by email client (Gmail, Outlook, Apple Mail…).

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Responses

CodeDescription
200Email client statistics returned successfully
401Invalid or expired token

GET /Campaign/{id}/Countries

Statistics by country

Returns the geographic distribution of opens and clicks.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Responses

CodeDescription
200Country statistics returned successfully
401Invalid or expired token

Send

Send bulk email campaigns (to contact lists) and individual transactional emails. Campaigns can be scheduled or sent immediately.

POST /Send/Mail

Send transactional email

Sends a one-off transactional email to a single recipient.

Recipient: provide email (inline address) OR id_contact (CRM contact) — not both. Using id_contact enables automatic personalization with the contact's custom field values.

Content: provide body_html (inline HTML) OR id_message (saved template) — not both. If both are provided, id_message takes precedence.

Required: id_sender_email, subject, and one of (email or id_contact), and one of (body_html or id_message).

Request body (application/json)

NameTypeRequiredDescription
id_sender_emailintegerID of the sender email address (from GET /SenderEmail). Required.
emailstringRecipient email address (inline). Provide this OR id_contact. If both are given, id_contact takes precedence. Local part (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.
id_contactstring (uuid)Recipient CRM contact ID. Provide this OR email. Using id_contact enables personalization with the contact's custom field values.
subjectstringEmail subject line (max 255 characters). Required.
body_htmlstringInline HTML content. Provide this OR id_message. If both are given, id_message takes precedence.
id_messageintegerID of a saved message template (from GET /Message). Provide this OR body_html.
reply_tostringReply-to address (defaults to the sender address)
fieldsarray of FieldValueCustom personalization fields (only used with inline email, not with id_contact)
date_plannedstring (date-time)Schedule the send for a future time. Omit to send immediately.
attachmentsarray of EmailAttachmentOptional file attachments (max 5). Each item is an object with a filename and Base64-encoded content. Limits: 10 MB per file and 15 MB total, measured after Base64 decoding. 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).
keep_in_my_filesbooleanfalse (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.

Example

{
  "id_sender_email": 201,
  "email": "[email protected]",
  "subject": "Your order #12345 has shipped",
  "body_html": "<p>Hello {{first_name}}, your order is on its way!</p>",
  "reply_to": "[email protected]",
  "fields": [
    {
      "id": 1,
      "value": "Alice"
    }
  ],
  "attachments": [
    {
      "filename": "invoice.pdf",
      "content": "JVBERi0xLjQKJVBERi0xLjQK..."
    }
  ]
}

Responses

CodeDescription
200Email sent or scheduled successfully
400Invalid email format or missing required field
401Invalid or expired token
404Sender, message, or contact not found; or recipient is unsubscribed
422Validation error (subject too long, body and message both empty, etc.)

Example 200

{
  "message": "Successful operation",
  "id_single_send": 8001
}

Example 404

{
  "Message": "Not enough credit available"
}

GET /Send/{id}/Status

Get delivery status of a transactional email

Returns the current delivery status of a transactional email sent with POST /Send/Mail. Pass the id_single_send that call returned.

Possible values: delivered (accepted by the recipient server, see diag_code), bounced (rejected — bounce_type: hard invalid address or permanent refusal, soft refused after retries), sent (handed to the mail server, no answer yet), scheduled (not sent yet, date_planned 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.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yesThe id_single_send returned by POST /Send/Mail.

Responses

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

Example 200

{
  "id_single_send": 8001,
  "email": "[email protected]",
  "status": "delivered",
  "date_planned": "2026-04-15T14:30:00",
  "status_date": "2026-04-15T14:30:07",
  "diag_code": "2.0.0 (success)",
  "bounce_type": null
}

POST /Send/Campaign

Send email campaign

Sends a bulk email campaign to one or more contact lists.

Required:

Segment: optionally filter recipients by a saved segment (id_segment). The segment filters contacts within the specified lists.

Scheduling: set date_planned to a future ISO 8601 date/time. Campaigns can be edited or cancelled until 5 minutes before the scheduled time.

Request body (application/json)

NameTypeRequiredDescription
id_messageintegeryesID of the message template to use (from GET /Message)
id_sender_emailintegeryesID of the sender address (from GET /SenderEmail)
id_email_reply_tointegerID of the reply-to sender address (from GET /SenderEmail). Must be one of your account's sender addresses, otherwise the request is rejected. Defaults to id_sender_email.
listsarray of ListRefyesTarget contact lists (at least one required)
id_segmentintegerFilter recipients by a saved segment (optional)
campaign_titlestringyesDisplay name for this campaign (max 150 characters)
date_plannedstring (date-time)Scheduled send time (ISO 8601). Omit to send immediately.
id_automationintegerLink this send to an automation workflow (optional)

Example

{
  "id_message": 501,
  "id_sender_email": 201,
  "lists": [
    {
      "id": 101
    }
  ],
  "campaign_title": "Spring Newsletter April 2026",
  "date_planned": "2026-04-20T09:00:00"
}

Responses

CodeDescription
200Campaign scheduled or sent successfully
401Invalid or expired token
402Insufficient credits
404Message, sender, list, or segment not found
422Campaign title empty or too long (max 150 chars)

Example 200

{
  "Message": "Successful operation. Campaigns can be edited or deleted until 5 minutes prior to send.",
  "IdCampaign": 8001
}

GET /Send

List scheduled sends

Returns the list of email campaigns waiting to be sent (scheduled, not yet processed).

Parameters

NameInTypeRequiredDescription
subjectMessagequerystringFilter by subject (partial match)
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200Waiting sends returned successfully
401Invalid or expired token

DELETE /Send/{id}

Cancel a scheduled send

Cancels and deletes a scheduled email send that has not yet been processed. Cannot cancel sends that are currently being processed.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yesSend ID (from GET /Send)

Responses

CodeDescription
200Scheduled send cancelled successfully
401Invalid or expired token
404Scheduled send not found

GET /Send/Mail/Scheduled

List scheduled single emails

Returns the single emails sent with POST /Send/Mail and a future date that have not left yet. Campaigns are listed by GET /Campaign/Scheduled.

OAuth scope : email:send

Responses

CodeDescription
200Scheduled emails returned
401Invalid or expired token

Example 200

[
  {
    "id_single_send": 880123,
    "email": "[email protected]",
    "subject": "Your invoice",
    "date_planned": "2026-12-01T09:00:00"
  }
]

POST /Send/Mail/{id}/Cancel

Cancel a scheduled single email

Cancels the email as long as it has not left.

OAuth scope : email:send

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yesid_single_send returned by POST /Send/Mail

Responses

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

Example 200

{
  "result": "ok",
  "cancelled": 880123
}

PATCH /Send/Mail/{id}/Reschedule

Reschedule a scheduled single email

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

OAuth scope : email:send

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yesid_single_send returned by POST /Send/Mail

Request body (application/json)

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

Responses

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

Example 200

{
  "result": "ok",
  "id": 880123,
  "date_planned": "2026-12-15T09:00:00"
}

Sender Addresses

Manage validated sender email addresses. Only verified addresses can be used as From.

GET /SenderEmail

List sender addresses

Returns the validated sender email addresses configured for the account.

Parameters

NameInTypeRequiredDescription
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200Sender addresses returned successfully
401Invalid or expired token

Example 200

[
  {
    "id": 201,
    "email": "[email protected]",
    "sender_name": "Your Company Newsletter",
    "is_validated": true
  }
]

POST /SenderEmail

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

Request body (application/json)

NameTypeRequiredDescription
emailstringyesSender address
sender_namestringyesName shown to recipients

Responses

CodeDescription
200Address added. domain_has_mx is false when the domain has no MX record.
400email or sender_name missing
401Invalid or expired token
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",
  "confirmation_sent": true,
  "domain_has_mx": true
}

POST /SenderEmail/{id}/ResendValidation

Send the validation email again

For an address still waiting for validation.

OAuth scope : email:templates:write

Parameters

NameInTypeRequiredDescription
idpathintegeryesid_sender_email from GET /SenderEmail

Responses

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

Example 200

{
  "result": "ok",
  "confirmation_sent": true
}

GET /SenderEmail/{id}/Authentication

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: dkim_settings_url opens the right screen.

OAuth scope : email:templates:read

Parameters

NameInTypeRequiredDescription
idpathintegeryesid_sender_email from GET /SenderEmail

Responses

CodeDescription
200Status of the domain. spf.recommended_record is built on your current record: publish it only when spf.change_needed is true.
401Invalid or expired token
404Sender not found
503Temporarily unavailable, please try again

Example 200

{
  "domain": "example.com",
  "spf": {
    "valid": true,
    "record": "v=spf1 include:spf.mailpro.com -all",
    "recommended_record": "v=spf1 include:spf.mailpro.com -all",
    "change_needed": 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"
  },
  "dkim_settings_url": "https://app.mailpro.com/dkim/list?d=example.com"
}

History

Browse the log of past email and SMS sends.

GET /History/Email

List email sending history

Returns the log of past email sends for the account.

Parameters

NameInTypeRequiredDescription
subjectMessagequerystringFilter by subject (partial match)
idCampaignqueryintegerFilter by campaign ID
includeBodyHTMLquerybooleanInclude the full HTML body in the response (slower)
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200Email history returned successfully
401Invalid or expired token

GET /History/SMS

List SMS sending history

Returns the log of past SMS sends for the account.

Parameters

NameInTypeRequiredDescription
dateSendquerystring (date-time)Filter from this date (ISO 8601)
dateSendEndquerystring (date-time)Filter up to this date (ISO 8601)
phoneNumberquerystringFilter by recipient phone (partial)
typequeryintegerSMS type filter
statusCodequeryintegerDLR status code filter
sortByStatusqueryboolean
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200SMS history returned successfully
401Invalid or expired token

Segment

Manage dynamic segments — saved filter rules that automatically select contacts based on field values, behavior, or list membership.

GET /Segment

List segments

Returns all saved dynamic segments for the account.

OAuth scope : contacts:read

Parameters

NameInTypeRequiredDescription
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200Segments returned successfully
401Invalid or expired token

GET /Segment/{id}/Contact

List the contacts of a segment

With id_list, returns only the contacts of that list in the segment: those a campaign list + segment would reach. The total is in the X-Total-Count header.

OAuth scope : contacts:read

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yesSegment ID
id_listqueryinteger (int64)Limit to this list (optional)
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200Contacts returned
401Invalid or expired token
404Segment or list not found

GET /Segment/{id}/Count

Count the contacts of a segment

The number only, to know the size of a send beforehand. With id_list, counts only the contacts of that list in the segment.

OAuth scope : contacts:read

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yesSegment ID
id_listqueryinteger (int64)Limit to this list (optional)

Responses

CodeDescription
200Number of contacts
401Invalid or expired token
404Segment or list not found

Example 200

{
  "total": 1250
}

GET /Segment/{id}/contact/{contactId}/check

Check if a contact matches a segment

Verifies whether a specific contact (by GUID) currently matches the rules of a given segment. Useful for triggering automation logic on demand.

OAuth scope : contacts:read

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yesSegment ID
contactIdpathstring (uuid)yesContact GUID

Responses

CodeDescription
200Returns match result
401Invalid or expired token
404Segment or contact not found

SMS

Send SMS messages to individual contacts or to entire contact lists. Uses each contact's phone_number field.

POST /Sms/SingleSMS

Send single SMS

Sends a single SMS to one phone number directly. Use this for transactional messages (OTPs, order confirmations, alerts). The phone number accepts +41…, 0041… or 41… with or without separators — see Phone number format in the API description for accepted / rejected inputs.

Request body (application/json)

NameTypeRequiredDescription
phone_numberstringyesRecipient phone number — accepts +41…, 0041… or 41… with or without separators. See Phone number format in the API description.
messagestringyesSMS message text (max 160 chars for standard encoding)
date_plannedstring (date-time)Schedule the send. Omit to send immediately.

Example

{
  "phone_number": "+41791234567",
  "message": "Your verification code is 9473. Valid for 10 minutes."
}

Responses

CodeDescription
200SMS sent or scheduled successfully
401Invalid or expired token
422Invalid phone number format

POST /Sms/Campaign

Send SMS campaign

Sends an SMS message to the phone_number field of all contacts in one or more lists. Contacts without a phone number are skipped.

Request body (application/json)

NameTypeRequiredDescription
listsarray of ListRefyesTarget contact lists (uses each contact's phone_number field)
messagestringyesSMS message text
date_plannedstring (date-time)Schedule the send. Omit to send immediately.

Example

{
  "lists": [
    {
      "id": 101
    }
  ],
  "message": "Flash sale today — 20% off. Shop at mailpro.com",
  "date_planned": "2026-04-20T10:00:00"
}

Responses

CodeDescription
200SMS campaign sent or scheduled successfully
401Invalid or expired token

GET /Sms

List SMS history

Returns the log of SMS sends (both single and campaign) for the account.

Parameters

NameInTypeRequiredDescription
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200SMS history returned successfully
401Invalid or expired token

DELETE /Sms/{id}

Cancel a scheduled SMS

Cancels a pending scheduled SMS send.

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yesSMS send ID

Responses

CodeDescription
200Scheduled SMS cancelled successfully
401Invalid or expired token
404SMS send not found

GET /Sms/Scheduled

List scheduled SMS sends

Returns SMS sends with a future date_planned (not yet dispatched), ordered by date ascending.

Parameters

NameInTypeRequiredDescription
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200Scheduled SMS sends returned successfully
401Invalid or expired token

POST /Sms/{id}/Cancel

Cancel a scheduled SMS

Cancels a scheduled SMS send only if it has not yet been dispatched.

Parameters

NameInTypeRequiredDescription
idpathintegeryesSMS send ID

Responses

CodeDescription
200Scheduled SMS cancelled successfully
401Invalid or expired token
409SMS send not found, not owned by client, or already dispatched

PATCH /Sms/{id}/Reschedule

Reschedule a scheduled SMS

Updates the planned send date of a scheduled SMS. The new date must be in the future and within 90 days. Fails if the SMS has already been dispatched.

Body field name: the API accepts date_planned (snake_case, primary) as well as the legacy aliases PlanDate and scheduled_time. Provide any one of them.

Parameters

NameInTypeRequiredDescription
idpathintegeryesSMS send ID

Request body (application/json)

NameTypeRequiredDescription
date_plannedstring (date-time)Primary field name.
PlanDatestring (date-time)Legacy alias accepted for backward compatibility.
scheduled_timestring (date-time)Legacy alias accepted for backward compatibility.

Responses

CodeDescription
200Scheduled SMS rescheduled successfully
400Missing date — provide one of date_planned / PlanDate / scheduled_time
401Invalid or expired token
409Reschedule failed (not found, already dispatched, or date out of range)

Folders

Organize message templates into folders.

GET /FolderList

List list folders

Returns the folders used to organize contact lists.

Parameters

NameInTypeRequiredDescription
namequerystringFilter by folder name (partial)
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200List folders returned successfully
401Invalid or expired token

GET /FolderList/{id}

Get a single list folder

Returns the details of one list folder by its ID.

Parameters

NameInTypeRequiredDescription
idpathintegeryesFolder ID

Responses

CodeDescription
200Folder returned successfully
401Invalid or expired token
404Folder not found

GET /FolderMessage

List message folders

Returns the folders used to organize email message templates.

Parameters

NameInTypeRequiredDescription
namequerystringFilter by folder name
numberOfRecordsqueryinteger
pageNumberqueryinteger

Responses

CodeDescription
200Message folders returned successfully
401Invalid or expired token

Import

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

POST /Import/Upload

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

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

Request type: multipart/form-data

Required form fields:

Optional:

File format:

Request body (multipart/form-data)

NameTypeRequiredDescription
id_listinteger (int64)yesID of the destination list
filestring (binary)yesCSV or XLSX file (UTF-8, max 50 MB)
json_match_fieldsstringyesJSON mapping of the file columns to contact fields
webhook_urlstring (uri)Callback URL invoked when processing finishes

Responses

CodeDescription
200File accepted and queued
400No file uploaded, missing id_list, or missing / invalid json_match_fields
401Invalid or expired token
500Server or storage error

Example 200

{
  "result": "OK",
  "import_job_id": 14,
  "internal_name": "230575-20260420142615-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 /Webhook

List webhooks

Returns every webhook configured on the authenticated account.

Responses

CodeDescription
200Array of webhooks
401Invalid or expired token

POST /Webhook

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.
descriptionstringFree-form description for your reference

Example

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

Responses

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

Example 201

{
  "id": 42,
  "url": "https://example.com/hooks/mailpro",
  "events": [
    "email.delivered",
    "email.bounced",
    "contact.created"
  ],
  "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 /Webhook/{id}

Get a webhook

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yes

Responses

CodeDescription
200The webhook
404Webhook not found

PUT /Webhook/{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 /Webhook/{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 /Webhook/{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
eventTypestringOverride the event type (must be subscribed by this webhook). ping always works.

Example

{
  "eventType": "ping"
}

Responses

CodeDescription
202Event queued for dispatch
404Webhook not found

Example 202

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

POST /Webhook/{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 /Webhook/{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 /Webhook/{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 (10s → 1min → 5min → 30min → 2h → 6h → 24h, then abandoned).

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

List the bounces of the account

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

OAuth scope : email:read

Parameters

NameInTypeRequiredDescription
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 or expired token

Example 200

[
  {
    "id": 5521,
    "source": "contact",
    "type": "hard",
    "email": "[email protected]",
    "date": "2026-10-12T08:30:00",
    "diag_code": "550 5.1.1 User unknown",
    "id_list": 101
  }
]

DELETE /Suppression/Bounces/{id}

Remove a bounce

source and type are required, as returned by GET /Suppression/Bounces: an id alone is not unique.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yesid of the bounce
sourcequerystringyeslist, contact, api or smtp
typequerystring: hard, softyeshard or soft

Responses

CodeDescription
200Bounce removed
400source or type missing
401Invalid or expired token
404Bounce not found

Example 200

{
  "result": "ok",
  "id": 5521,
  "restored": false
}

POST /Suppression/Bounces/{id}/Restore

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
idpathinteger (int64)yesid of the bounce

Responses

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

Example 200

{
  "result": "ok",
  "id": 5521,
  "restored": true
}

GET /Suppression/Unsubscribers

List the unsubscribers of the account

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

OAuth scope : contacts:read

Parameters

NameInTypeRequiredDescription
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 or expired token

Example 200

[
  {
    "id": 9012,
    "source": "api3",
    "email": "[email protected]",
    "date": "2026-10-20T16:05:00",
    "list_name": null,
    "comment": "Asked by phone"
  }
]

POST /Suppression/Unsubscribers

Add an unsubscriber

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

OAuth scope : contacts:write

Request body (application/json)

NameTypeRequiredDescription
emailstringyesAddress to unsubscribe
commentstringFree comment
id_listinteger (int64)Unsubscribe list of the account (optional)

Responses

CodeDescription
200Done. already_unsubscribed is true when the address already was.
400A valid email is required
401Invalid or expired token
404id_list is not an unsubscribe list of this account

Example 200

{
  "result": "ok",
  "already_unsubscribed": false
}

DELETE /Suppression/Unsubscribers/{id}

Remove an unsubscriber

source is required, as returned by GET /Suppression/Unsubscribers: an id alone is not unique.

OAuth scope : contacts:write

Parameters

NameInTypeRequiredDescription
idpathinteger (int64)yesid of the unsubscriber
sourcequerystringyescontact, list, smtp, api or api3

Responses

CodeDescription
200Unsubscriber removed
400source is required
401Invalid or expired token
404Unsubscriber not found

Example 200

{
  "result": "ok",
  "removed": 9012
}