Interactive documentation · Developer portal · OpenAPI specification · EN · FR · DE · IT · PT · ES
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.
All endpoints (except POST /token and the OAuth endpoints) accept three authentication methods. Use whichever fits your stack.
POST /token. Looks like OAuth but is tied to a single account credential and is not scoped.> 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).
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).
| Endpoint | URL |
|---|---|
| Authorize | GET https://api.mailpro.com/oauth/authorize |
| Token / Refresh | POST https://api.mailpro.com/oauth/token |
| JWKS (verify signatures) | GET https://api.mailpro.com/oauth/.well-known/jwks |
| Discovery | GET https://api.mailpro.com/oauth/.well-known/openid-configuration |
Issuer (the iss claim on every token): https://api.mailpro.com/oauth/.
Supported grants
authorization_code + PKCE (code_challenge_method=S256 is mandatory) — the "Connect Mailpro" browser consent flow. Returns an access token and a rotating refresh token.client_credentials — machine-to-machine, no user. Returns an access token only.refresh_token — exchange a refresh token for a fresh access/refresh pair. Refresh tokens rotate: each use invalidates the old one.Token lifetimes
| Token | TTL |
|---|---|
| Access token (RS256 JWT) | 20 minutes |
| Refresh token (sliding, single-use) | 30 days |
| Authorization code | 60 seconds |
Authorization Code + PKCE — step by step
code_verifier (43–128 random chars) and code_challenge = base64url(sha256(code_verifier)). 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
?code=…&state=…. Verify state (CSRF protection). 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": "…" }
Authorization: Bearer ACCESS_TOKEN.grant_type=refresh_token and store the new refresh token (the old one is now dead).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.
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.
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.
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.
POST /token
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=YOUR_USERNAME&password=YOUR_API_KEY
username — your Mailpro username (email)password — your API key (GUID), found in Settings → API accessPOST /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.
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.
| Scope | Allows |
|---|---|
contacts:read | View your contacts, lists, tags, segments and custom fields. |
contacts:write | Create, edit and delete your contacts, lists, tags and custom fields. |
email:send | Send transactional and campaign emails on your behalf. |
email:read | View delivery stats, opens, clicks and bounce history. |
email:templates:read | View your email templates (subject, HTML, plain-text body). |
email:templates:write | Create, edit and delete your email templates. |
sms:send | Send transactional and campaign SMS messages on your behalf. |
sms:read | View SMS credit balance and message history. |
sms:contacts:read | View your SMS phone book. |
sms:contacts:write | Create, edit and delete entries in your SMS phone book. |
webhooks:manage | Subscribe to, edit and remove webhook endpoints for your account. |
account:read | View your account credits, limits and subscription status. |
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:
| Endpoints | Required 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 check | contacts: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 /Tag | contacts: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).
Same rule on the three Mailpro APIs (Email v2, CRM v3, SMS): 5,000 items per page, by default and at most.
numberOfRecords — items per page. Omitted, 0, or above 5,000 = 5,000.pageNumber — 1-based page number (default 1).pageNumber until a page comes back with fewer items.includeBodyHTML=true) return 10 per page by default.200 with an empty list []. 404 means that the resource itself (list, tag, segment, campaign…) does not exist.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 rule violations return HTTP 406 with a JSON body:
{ "Message": "Contact cannot be created. Email cannot be empty." }
Common 406 reasons:
Email cannot be empty — email is requiredEmail has an invalid format — not a valid email addressA contact already exist with the same Email — email uniqueness violationThe email address was unsubscribed permanently — address blacklistedThe email address has identified has a hard bounce — address bouncedThe email address has identified has a spam trap — spam trap detectedAt least one List is necessary — contacts must belong to at least one listOne or more lists have reached the contact limit — 1M contacts per list maxInvalid Phone Number — phone number format errorInvalid country code for Phone Number — unsupported country prefixLength exceeded for Phone Number (12) — phone number too long| Code | Meaning |
|---|---|
400 | Malformed request (bad JSON, message over the SMS part limit) |
401 | Missing, invalid or expired credentials |
402 | Not enough credits to perform the send |
403 | Allowed by your credentials, but refused — account blocked for SMS sending |
404 | The referenced object does not exist (contact, list, tag, field, campaign) |
406 | Contact business rule violation (see the list above) |
422 | Input 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 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.
| Valid | Invalid |
|---|---|
+41794849980 — E.164 international | 0612345678 — national format (leading 0) |
0041 79 484 99 80 — 00-prefix + spaces | 41'794'849'80 — apostrophes (not stripped) |
+33 6-12-34-56-78 — + with dashes | tel=0041794849980 — extra characters |
41794849980 — bare digits | 1234567 — too short (< 8 digits) |
Rules:
+, 00, or no prefix0)-, dot ., slash /, parentheses ( ), underscore _, NBSP=, ;, + inside the number, etc.) → rejected41794849980)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.
| Resource | Limit |
|---|---|
| Tags per account | 1,000 |
| Lists per account | 1,000 |
| Custom fields per account | 50 |
| Monthly API requests per account | 50,000 (5× subscription email credits) |
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.
POST /Send/Mail contains id_single_send.email.delivered and email.bounced webhook events push the final status to your server as soon as it is known, with no polling at all.GET /Send/{id}/Status returns the current status (table below).status | Meaning | Final? |
|---|---|---|
queued | Accepted, waiting to be sent. | no |
scheduled | Planned for later (date_planned). | no |
sent | Handed over by our servers, no answer from the recipient server yet. | no |
delivered | Accepted by the recipient server (diag_code = its SMTP reply). | yes |
bounced | Rejected. bounce_type: hard = invalid address or permanent refusal; soft = refused after retries (mailbox full, server unreachable…). | yes |
not_found | Unknown id, or an id that does not belong to your account. | yes |
Polling without looping
delivered appears about 5 minutes after the handover.Retry-After header (the number of seconds to wait), and the call does not count against your quota.scheduled: no need to poll before date_planned.Statuses are kept for 365 days.
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.
POST /Webhook. The response contains a secret shown once (Stripe / GitHub style) — store it.secret (snippets below).> ⏱ 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.
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]", "...": "..." }
}
| Field | Format | Description |
|---|---|---|
id | evt_ + 32 hex | Unique event ID — use as an idempotency key on your side. |
type | <domain>.<verb> | Canonical event type (e.g. email.delivered, contact.created). |
created | Unix seconds UTC | Included in the HMAC signature. |
apiVersion | "v3" | Which Mailpro API published the event. |
account.id | int | Your Mailpro client ID. |
account.name | string | Your account name (handy for client-side debug). |
data | object | Event payload — shape depends on type (see catalog below). |
| Event | Trigger | data shape |
|---|---|---|
ping | Test via POST /Webhook/{id}/test | { message } |
contact.created | POST /Contact | { id, email, firstName, lastName, createdAt } |
contact.updated | PUT /Contact/{id} | { id, email, firstName, lastName, updatedAt, changes } |
contact.deleted | DELETE /Contact/{id} | { id, email, deletedAt } |
list.contact_added | Contact added to list | { listId, listName, contactId, contactEmail, addedAt } |
list.contact_removed | Contact removed from list | { listId, listName, contactId, contactEmail, removedAt } |
tag.applied | Tag applied to contact | { tag, contactId, contactEmail, appliedAt } |
tag.removed | Tag removed | { tag, contactId, contactEmail, removedAt } |
segment.matched | Contact enters a segment | { segmentId, segmentName, contactId, contactEmail, matchedAt } |
email.processed | Send accepted by pipeline | { messageId, email, campaignId, processedAt } |
email.delivered | A 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.opened | Tracking pixel hit | { messageId, email, campaignId, openedAt, ip, userAgent } |
email.clicked | Click on tracked link | { messageId, email, campaignId, clickedAt, url, ip, userAgent } |
email.bounced | Hard or soft bounce | { messageId, email, campaignId, bouncedAt, bounceType, reason } |
email.spam_report | FBL complaint | { messageId, email, campaignId, reportedAt } |
email.unsubscribed | Opt-out link clicked | { messageId, email, campaignId, unsubscribedAt } |
email.dropped | Refused before send | { messageId, email, campaignId, droppedAt, reason } |
campaign.started | Dispatch begins | { campaignId, name, totalRecipients, startedAt } |
campaign.finished | All recipients processed | { campaignId, name, totalSent, totalFailed, finishedAt } |
import.started | Import job started | { importId, fileName, totalRows, startedAt } |
import.progress | At 25/50/75% | { importId, fileName, processedRows, totalRows, progressPct } |
import.completed | Import OK | { importId, fileName, totalRows, addedCount, updatedCount, errorCount, completedAt } |
import.failed | Fatal error during import | { importId, fileName, errorMessage, failedAt } |
Subscribe to all events of this API by passing ["*"] in the events array.
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:
t and v1 from the header.|now - t| > 300 (5-minute replay protection window).expected = hex(HMAC_SHA256(secret, t + "." + raw_body)).v1 with expected (crypto.timingSafeEqual, hash_equals, CryptographicOperations.FixedTimeEquals) — never ==.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'));
}
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']);
}
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'])
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"]));
}
| Attempt | Delay before next try |
|---|---|
| 1 → 2 | 10 seconds |
| 2 → 3 | 1 minute |
| 3 → 4 | 5 minutes |
| 4 → 5 | 30 minutes |
| 5 → 6 | 2 hours |
| 6 → 7 | 6 hours |
| 7 | abandoned (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.
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.
| Limit | Value |
|---|---|
| Webhooks per account | 20 |
| Subscribed events per webhook | 50 |
| Max payload size | 256 KB |
| HTTP timeout per attempt | 10 seconds |
| Delivery history retention | 30 days |
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.
/tokenGet access token
Obtains a Bearer JWT token using client credentials. Pass the returned access_token as Authorization: Bearer <token> in all subsequent requests.
| Grant type | Use case |
|---|---|
password | Standard integration — username + API key |
refresh_token | Renew an expiring access token without re-authenticating |
client_credentials | Server-to-server with client ID only |
zapier / automateio / microsoft-flow | Third-party platform integrations |
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).
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)
| Name | Type | Required | Description |
|---|---|---|---|
grant_type | string: password, refresh_token, client_credentials, zapier, automateio, microsoft-flow | yes | Authentication grant type |
username | string | Your Mailpro username (email). Required for password grant. | |
password | string | Your Mailpro API key (GUID format). Required for password grant. | |
refresh_token | string | Previous refresh token. Required for refresh_token grant. | |
client_id | string | Your Mailpro client ID (integer). Required for client_credentials grant. |
Responses
| Code | Description |
|---|---|
200 | Token issued successfully |
400 | Invalid 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."
}
Retrieve account profile, credit balance, and subscription details.
/Account/MeGet account details
Returns the profile details and settings of the authenticated Mailpro account.
Responses
| Code | Description |
|---|---|
200 | Account details returned successfully |
401 | Invalid or expired token |
/Account/CreditsGet credit balance
Returns the remaining email and SMS credit balance for the account.
Responses
| Code | Description |
|---|---|
200 | Credit balance returned successfully |
401 | Invalid or expired token |
Example 200
{
"email_credits": 48500,
"sms_credits": 1200
}
/Account/SubscriptionEndDateGet subscription end date
Returns the expiry date of the current Mailpro subscription.
Responses
| Code | Description |
|---|---|
200 | Subscription end date returned successfully |
401 | Invalid or expired token |
/Account/LimitsGet 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:
v2, v3 or sms endpoints (each call = 1 credit, regardless of the result — 2xx and most 4xx business errors both count).contact.created fired to your subscribed webhook = 1 credit on top of the API call that triggered it).What is FREE (does not consume credits):
/health endpoints (monitoring).401 (auth fail), 403 (forbidden) or 429 (rate-limited).Responses
| Code | Description |
|---|---|
200 | Quota snapshot |
401 | Invalid or expired token |
404 | No 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). |
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.
/ContactCreate a contact
Creates a new CRM contact with optional custom fields, list assignments, and tags.
Required:
email — must be unique across the accountlists — at least one list assignment is mandatoryPhone 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)
| Name | Type | Required | Description |
|---|---|---|---|
email | string | yes | Contact's email address (required, must be unique) |
phone_number | string | Phone 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).
| |
lists | array of ListRef | yes | Lists to assign this contact to. At least one list is required. |
tags | array of TagRef | Tags to assign to this contact (optional) | |
custom_fields | array of FieldValue | Custom 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
| Code | Description |
|---|---|
200 | Contact created successfully |
400 | Invalid email format |
401 | Invalid or expired token |
406 | Business rule violation (see description for full list) |
Example 406
{
"Message": "Contact cannot be created. A contact already exist with the same Email"
}
/Contact/MultipleImport 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
| Code | Description |
|---|---|
200 | Bulk import completed (check errors array for partial failures) |
400 | Invalid input |
401 | Invalid or expired token |
406 | Business 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": []
}
/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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Contact UUID (from POST /Contact or GET /List/{id}/Contact) |
includeTags | query | boolean | Set to true to include the contact's tag assignments in the response | |
includeLists | query | boolean | Set to true to include the contact's list memberships in the response |
Responses
| Code | Description |
|---|---|
200 | Contact returned successfully |
401 | Invalid or expired token |
404 | Contact 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
}
]
}
/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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Contact UUID |
Request body (application/json)
| Name | Type | Required | Description |
|---|---|---|---|
email | string | yes | Contact's email address (required, must be unique) |
phone_number | string | Phone 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).
| |
lists | array of ListRef | yes | Lists to assign this contact to. At least one list is required. |
tags | array of TagRef | Tags to assign to this contact (optional) | |
custom_fields | array of FieldValue | Custom field values for this contact (optional) |
Responses
| Code | Description |
|---|---|
200 | Contact updated successfully |
401 | Invalid or expired token |
404 | Contact not found |
422 | Validation error (email format, phone number, field value type mismatch) |
Example 200
{
"Result": "Successful operation"
}
/Contact/{id}Delete a contact
Permanently deletes a contact from the CRM.
OAuth scope : contacts:write
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Contact UUID |
Responses
| Code | Description |
|---|---|
200 | Contact deleted successfully |
401 | Invalid or expired token |
404 | Contact not found |
/Contact/{id}/ActivityActivity 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Contact GUID |
Responses
| Code | Description |
|---|---|
200 | Activity returned |
401 | Invalid or expired token |
404 | The 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"
}
]
}
/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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Contact UUID |
tagid | path | integer (int64) | yes | Tag ID (from GET /Tag) |
Responses
| Code | Description |
|---|---|
200 | Tag assigned successfully |
401 | Invalid or expired token |
404 | Contact or tag not found |
/Contact/{id}/Tag/{tagid}Remove tag from contact
Removes a tag assignment from a contact.
OAuth scope : contacts:write
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Contact UUID |
tagid | path | integer (int64) | yes | Tag ID |
Responses
| Code | Description |
|---|---|
200 | Tag removed successfully |
401 | Invalid or expired token |
404 | Contact or tag not found |
/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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Contact UUID |
listid | path | integer (int64) | yes | List ID (from GET /List) |
Responses
| Code | Description |
|---|---|
200 | Contact added to list successfully |
401 | Invalid or expired token |
404 | Contact, list not found, or contact was unsubscribed from this list |
/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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Contact UUID |
listid | path | integer (int64) | yes | List ID |
Responses
| Code | Description |
|---|---|
200 | Contact removed from list successfully |
401 | Invalid or expired token |
404 | Contact or list not found |
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.
/ListList 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | query | string | Filter by list name (partial match) | |
dateCreationFrom | query | string (date-time) | Filter by creation date (from, ISO 8601) | |
dateCreationTo | query | string (date-time) | Filter by creation date (to, ISO 8601) | |
numberOfRecords | query | integer | Items per page — 5,000 by default and at most | |
pageNumber | query | integer | 1-based page number | |
sortByName | query | boolean | Set to true to sort alphabetically by name | |
includeTotalContacts | query | boolean | Set to true to include total_contacts count in each result |
Responses
| Code | Description |
|---|---|
200 | Lists returned successfully |
401 | Invalid or expired token |
Example 200
[
{
"id": 101,
"name": "Newsletter subscribers",
"total_contacts": 4521,
"date_creation": "2025-01-15T10:30:00"
}
]
/ListCreate a contact list
Creates a new contact list. Maximum 1,000 lists per account.
OAuth scope : contacts:write
Request body (application/json)
| Name | Type | Required | Description |
|---|---|---|---|
name | string | yes | Display name for the list |
Example
{
"name": "Spring Campaign 2026"
}
Responses
| Code | Description |
|---|---|
200 | List created successfully |
401 | Invalid or expired token |
406 | List limit (1,000) reached |
Example 200
{
"id": 102,
"name": "Spring Campaign 2026",
"date_creation": "2026-04-14T12:00:00"
}
/List/{id}Get a contact list
Returns the details of a specific contact list.
OAuth scope : contacts:read
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | List ID |
Responses
| Code | Description |
|---|---|
200 | List returned successfully |
401 | Invalid or expired token |
404 | List not found |
/List/{id}Update a contact list
Renames an existing contact list.
OAuth scope : contacts:write
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | List ID |
Request body (application/json)
| Name | Type | Required | Description |
|---|---|---|---|
name | string | yes | Display name for the list |
Example
{
"name": "Newsletter subscribers — Europe"
}
Responses
| Code | Description |
|---|---|
200 | List updated successfully |
401 | Invalid or expired token |
404 | List not found |
/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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | List ID |
Responses
| Code | Description |
|---|---|
200 | List deleted successfully |
401 | Invalid or expired token |
404 | List not found |
406 | List is used by an active automation workflow |
/List/{id}/ContactList 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | List ID |
email | query | string | Filter by email (partial match) | |
numberOfRecords | query | integer | Items per page — 5,000 by default and at most | |
pageNumber | query | integer | ||
sortByEmail | query | boolean | Sort alphabetically by email | |
includeTags | query | boolean | Include tag assignments in results | |
includeLists | query | boolean | Include list memberships in results |
Responses
| Code | Description |
|---|---|
200 | Contacts returned successfully |
401 | Invalid or expired token |
404 | List not found |
/List/{listId}/UnsubscribersList unsubscribers
Returns the contacts who have unsubscribed from a specific list.
OAuth scope : contacts:read
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
listId | path | integer (int64) | yes | List ID |
Responses
| Code | Description |
|---|---|
200 | Unsubscribers returned successfully |
401 | Invalid or expired token |
404 | List not found |
Manage tags for contact labelling and segmentation. Tags are flat labels (e.g. "VIP", "Churned", "B2B"). Maximum 1,000 tags per account.
/TagList tags
Returns all tags for the account.
OAuth scope : contacts:read
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | query | string | Filter by name (partial match) | |
numberOfRecords | query | integer | ||
pageNumber | query | integer | ||
sortByName | query | boolean |
Responses
| Code | Description |
|---|---|
200 | Tags returned successfully |
401 | Invalid or expired token |
Example 200
[
{
"id": 5,
"name": "VIP customers"
},
{
"id": 6,
"name": "Newsletter openers"
}
]
/TagCreate a tag
Creates a new tag. Maximum 1,000 tags per account.
OAuth scope : contacts:write
Request body (application/json)
| Name | Type | Required | Description |
|---|---|---|---|
name | string | yes | Display name for the tag |
Example
{
"name": "VIP customers"
}
Responses
| Code | Description |
|---|---|
200 | Tag created successfully |
401 | Invalid or expired token |
406 | Tag limit (1,000) reached |
/Tag/{id}Get a tag
Returns a specific tag by its ID.
OAuth scope : contacts:read
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | Tag ID |
Responses
| Code | Description |
|---|---|
200 | Tag returned successfully |
401 | Invalid or expired token |
404 | Tag not found |
/Tag/{id}Update a tag
Renames an existing tag.
OAuth scope : contacts:write
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Request body (application/json)
| Name | Type | Required | Description |
|---|---|---|---|
name | string | yes | Display name for the tag |
Example
{
"name": "VIP customers — updated"
}
Responses
| Code | Description |
|---|---|
200 | Tag updated successfully |
401 | Invalid or expired token |
404 | Tag not found |
/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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Responses
| Code | Description |
|---|---|
200 | Tag deleted successfully |
401 | Invalid or expired token |
404 | Tag not found |
406 | Tag is used by an active automation workflow |
/Tag/{id}/ContactList contacts with a tag
Returns the contacts that have been assigned a specific tag.
OAuth scope : contacts:read
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | Tag ID |
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | Contacts returned successfully |
401 | Invalid or expired token |
404 | Tag not found |
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.
/FieldList custom fields
Returns all custom contact fields defined for the account. Use the returned id values when creating or updating contacts.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | query | string | Filter by field name | |
idFieldType | query | integer | Filter by field type ID | |
numberOfRecords | query | integer | ||
pageNumber | query | integer | ||
sortByName | query | boolean |
Responses
| Code | Description |
|---|---|
200 | Custom fields returned successfully |
401 | Invalid 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"
}
]
/FieldCreate 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)
| Name | Type | Required | Description |
|---|---|---|---|
name | string | yes | Display name of the field |
id_field_type | integer | yes | Field type ID (get available types from GET /Field/Types):
- 1 = text
- 2 = integer
- 3 = date and time
- 4 = yes / no
- 12 = decimal
|
personalize_key | string | yes | Template variable key, used to insert the field in a message |
sort | integer | Display 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
| Code | Description |
|---|---|
200 | Field created successfully |
401 | Invalid or expired token |
406 | Field limit (50) reached |
/Field/TypesList field types
Returns the available field type identifiers to use when creating a custom field. Common types: 1 = text, 2 = number, 3 = date.
Responses
| Code | Description |
|---|---|
200 | Field types returned successfully |
401 | Invalid or expired token |
/Field/{id}Get a custom field
Returns the definition and metadata of a specific custom field.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | Field ID |
Responses
| Code | Description |
|---|---|
200 | Field returned successfully |
401 | Invalid or expired token |
404 | Field not found |
/Field/{id}Update a custom field
Updates the name or personalisation key of a custom field.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Request body (application/json)
| Name | Type | Required | Description |
|---|---|---|---|
name | string | yes | Display name of the field |
id_field_type | integer | yes | Field type ID (get available types from GET /Field/Types):
- 1 = text
- 2 = integer
- 3 = date and time
- 4 = yes / no
- 12 = decimal
|
personalize_key | string | yes | Template variable key, used to insert the field in a message |
sort | integer | Display order (0 to 255). Required when updating a field (PUT /Field/{id}). |
Responses
| Code | Description |
|---|---|
200 | Field updated successfully |
401 | Invalid or expired token |
404 | Field not found |
/Field/{id}Delete a custom field
Permanently deletes a custom field and removes its value from all contacts. This action cannot be undone.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Responses
| Code | Description |
|---|---|
200 | Field deleted successfully |
401 | Invalid or expired token |
404 | Field not found |
View campaign statistics and manage scheduled campaigns. Campaigns can be edited or deleted until 5 minutes before the scheduled send time.
/CampaignList 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | Campaigns returned successfully |
401 | Invalid 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
}
]
/Campaign/{id}Get a campaign
Returns the configuration and statistics summary of a specific campaign.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Responses
| Code | Description |
|---|---|
200 | Campaign returned successfully |
401 | Invalid or expired token |
404 | Campaign not found |
/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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Request body (application/json)
| Name | Type | Required | Description |
|---|---|---|---|
campaign_title | string | Display name for the campaign | |
date_planned | string (date-time) | Scheduled send time (ISO 8601). Omit to send immediately. |
Responses
| Code | Description |
|---|---|
200 | Campaign updated successfully |
401 | Invalid or expired token |
404 | Campaign not found |
/Campaign/{id}Delete a campaign
Cancels and permanently deletes a campaign by its identifier.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Responses
| Code | Description |
|---|---|
200 | Campaign deleted successfully |
401 | Invalid or expired token |
404 | Campaign not found |
/Campaign/ScheduledList my scheduled campaigns
Returns campaigns with date_planned in the future that have not yet been dispatched. Sorted by date_planned ascending.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | List of scheduled campaigns |
401 | Invalid 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
}
]
/Campaign/{id}/CancelCancel 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Responses
| Code | Description |
|---|---|
200 | Campaign cancelled |
401 | Invalid or expired token |
409 | Already dispatched or not found |
Example 200
{
"result": "ok",
"cancelled": 5001
}
/Campaign/{id}/RescheduleReschedule 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Request body (application/json)
| Name | Type | Required | Description |
|---|---|---|---|
date_planned | string (date-time) | Primary field name. | |
PlanDate | string (date-time) | Legacy alias accepted for backward compatibility. | |
scheduled_time | string (date-time) | Legacy alias accepted for backward compatibility. |
Responses
| Code | Description |
|---|---|
200 | Campaign rescheduled |
400 | Missing date — provide one of date_planned / PlanDate / scheduled_time |
401 | Invalid or expired token |
409 | Already dispatched |
Example 200
{
"result": "ok",
"id": 5001,
"date_planned": "2026-12-31T14:30:00"
}
/Campaign/{id}/ByHourStatistics by hour
Returns the hourly open and click breakdown for the campaign.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Responses
| Code | Description |
|---|---|
200 | Hourly statistics returned successfully |
401 | Invalid or expired token |
/Campaign/{id}/ByDayStatistics by day
Returns the daily open and click breakdown for the campaign.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Responses
| Code | Description |
|---|---|
200 | Daily statistics returned successfully |
401 | Invalid or expired token |
/Campaign/{id}/OpeningsList email opens
Returns a paginated list of contacts who opened the campaign email.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | |
email | query | string | Filter by recipient email | |
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | Open list returned successfully |
401 | Invalid or expired token |
/Campaign/{id}/ClicksList link clicks
Returns a paginated list of contacts who clicked a link in the campaign.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | |
email | query | string | ||
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | Click list returned successfully |
401 | Invalid or expired token |
/Campaign/{id}/HardBouncesList hard bounces
Returns the email addresses that hard-bounced during this campaign.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | |
email | query | string | ||
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | Hard bounce list returned successfully |
401 | Invalid or expired token |
/Campaign/{id}/LinksList tracked links
Returns click performance data for each tracked link in the campaign.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | |
link | query | string | Filter by link URL (partial match) | |
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | Link statistics returned successfully |
401 | Invalid or expired token |
/Campaign/{id}/OSStatistics by OS / device
Returns the open and click breakdown by operating system and device type.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Responses
| Code | Description |
|---|---|
200 | OS statistics returned successfully |
401 | Invalid or expired token |
/Campaign/{id}/EmailClientsStatistics by email client
Returns the open breakdown by email client (Gmail, Outlook, Apple Mail…).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Responses
| Code | Description |
|---|---|
200 | Email client statistics returned successfully |
401 | Invalid or expired token |
/Campaign/{id}/CountriesStatistics by country
Returns the geographic distribution of opens and clicks.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Responses
| Code | Description |
|---|---|
200 | Country statistics returned successfully |
401 | Invalid or expired token |
Send bulk email campaigns (to contact lists) and individual transactional emails. Campaigns can be scheduled or sent immediately.
/Send/MailSend 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)
| Name | Type | Required | Description |
|---|---|---|---|
id_sender_email | integer | ID of the sender email address (from GET /SenderEmail). Required. | |
email | string | Recipient 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_contact | string (uuid) | Recipient CRM contact ID. Provide this OR email.
Using id_contact enables personalization with the contact's custom field values.
| |
subject | string | Email subject line (max 255 characters). Required. | |
body_html | string | Inline HTML content. Provide this OR id_message.
If both are given, id_message takes precedence.
| |
id_message | integer | ID of a saved message template (from GET /Message).
Provide this OR body_html.
| |
reply_to | string | Reply-to address (defaults to the sender address) | |
fields | array of FieldValue | Custom personalization fields (only used with inline email, not with id_contact) | |
date_planned | string (date-time) | Schedule the send for a future time. Omit to send immediately. | |
attachments | array of EmailAttachment | Optional 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_files | boolean | false (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
| Code | Description |
|---|---|
200 | Email sent or scheduled successfully |
400 | Invalid email format or missing required field |
401 | Invalid or expired token |
404 | Sender, message, or contact not found; or recipient is unsubscribed |
422 | Validation 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"
}
/Send/{id}/StatusGet 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | The id_single_send returned by POST /Send/Mail. |
Responses
| Code | Description |
|---|---|
200 | Current delivery status |
401 | Invalid or expired token |
404 | No 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
}
/Send/CampaignSend email campaign
Sends a bulk email campaign to one or more contact lists.
Required:
id_message — the template to send (from GET /Message)id_sender_email — the sender address (from GET /SenderEmail)lists — at least one target contact listcampaign_title — label for statistics trackingSegment: 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)
| Name | Type | Required | Description |
|---|---|---|---|
id_message | integer | yes | ID of the message template to use (from GET /Message) |
id_sender_email | integer | yes | ID of the sender address (from GET /SenderEmail) |
id_email_reply_to | integer | ID 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. | |
lists | array of ListRef | yes | Target contact lists (at least one required) |
id_segment | integer | Filter recipients by a saved segment (optional) | |
campaign_title | string | yes | Display name for this campaign (max 150 characters) |
date_planned | string (date-time) | Scheduled send time (ISO 8601). Omit to send immediately. | |
id_automation | integer | Link 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
| Code | Description |
|---|---|
200 | Campaign scheduled or sent successfully |
401 | Invalid or expired token |
402 | Insufficient credits |
404 | Message, sender, list, or segment not found |
422 | Campaign 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
}
/SendList scheduled sends
Returns the list of email campaigns waiting to be sent (scheduled, not yet processed).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
subjectMessage | query | string | Filter by subject (partial match) | |
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | Waiting sends returned successfully |
401 | Invalid or expired token |
/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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | Send ID (from GET /Send) |
Responses
| Code | Description |
|---|---|
200 | Scheduled send cancelled successfully |
401 | Invalid or expired token |
404 | Scheduled send not found |
/Send/Mail/ScheduledList 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
| Code | Description |
|---|---|
200 | Scheduled emails returned |
401 | Invalid or expired token |
Example 200
[
{
"id_single_send": 880123,
"email": "[email protected]",
"subject": "Your invoice",
"date_planned": "2026-12-01T09:00:00"
}
]
/Send/Mail/{id}/CancelCancel a scheduled single email
Cancels the email as long as it has not left.
OAuth scope : email:send
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | id_single_send returned by POST /Send/Mail |
Responses
| Code | Description |
|---|---|
200 | Email cancelled |
401 | Invalid or expired token |
409 | Send not found, or already dispatched |
Example 200
{
"result": "ok",
"cancelled": 880123
}
/Send/Mail/{id}/RescheduleReschedule 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | id_single_send returned by POST /Send/Mail |
Request body (application/json)
| Name | Type | Required | Description |
|---|---|---|---|
date_planned | string (date-time) | yes | New send date (ISO 8601) |
Responses
| Code | Description |
|---|---|
200 | Email rescheduled |
400 | Missing date |
401 | Invalid or expired token |
409 | Send not found, already dispatched, or date out of range |
Example 200
{
"result": "ok",
"id": 880123,
"date_planned": "2026-12-15T09:00:00"
}
Manage validated sender email addresses. Only verified addresses can be used as From.
/SenderEmailList sender addresses
Returns the validated sender email addresses configured for the account.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | Sender addresses returned successfully |
401 | Invalid or expired token |
Example 200
[
{
"id": 201,
"email": "[email protected]",
"sender_name": "Your Company Newsletter",
"is_validated": true
}
]
/SenderEmailAdd 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)
| Name | Type | Required | Description |
|---|---|---|---|
email | string | yes | Sender address |
sender_name | string | yes | Name shown to recipients |
Responses
| Code | Description |
|---|---|
200 | Address added. domain_has_mx is false when the domain has no MX record. |
400 | email or sender_name missing |
401 | Invalid or expired token |
422 | Address refused: invalid, already among the senders of the account, public mail domain, forbidden name, or limit of 25 reached. The message gives the reason. |
503 | Temporarily unavailable, please try again |
Example 200
{
"result": "ok",
"confirmation_sent": true,
"domain_has_mx": true
}
/SenderEmail/{id}/ResendValidationSend the validation email again
For an address still waiting for validation.
OAuth scope : email:templates:write
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | yes | id_sender_email from GET /SenderEmail |
Responses
| Code | Description |
|---|---|
200 | Email sent again |
401 | Invalid or expired token |
404 | Sender not found |
409 | This sender is already validated |
503 | Temporarily unavailable, please try again |
Example 200
{
"result": "ok",
"confirmation_sent": true
}
/SenderEmail/{id}/AuthenticationCheck 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | yes | id_sender_email from GET /SenderEmail |
Responses
| Code | Description |
|---|---|
200 | Status of the domain. spf.recommended_record is built on your current record: publish it only when spf.change_needed is true. |
401 | Invalid or expired token |
404 | Sender not found |
503 | Temporarily 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"
}
Browse the log of past email and SMS sends.
/History/EmailList email sending history
Returns the log of past email sends for the account.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
subjectMessage | query | string | Filter by subject (partial match) | |
idCampaign | query | integer | Filter by campaign ID | |
includeBodyHTML | query | boolean | Include the full HTML body in the response (slower) | |
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | Email history returned successfully |
401 | Invalid or expired token |
/History/SMSList SMS sending history
Returns the log of past SMS sends for the account.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
dateSend | query | string (date-time) | Filter from this date (ISO 8601) | |
dateSendEnd | query | string (date-time) | Filter up to this date (ISO 8601) | |
phoneNumber | query | string | Filter by recipient phone (partial) | |
type | query | integer | SMS type filter | |
statusCode | query | integer | DLR status code filter | |
sortByStatus | query | boolean | ||
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | SMS history returned successfully |
401 | Invalid or expired token |
Manage dynamic segments — saved filter rules that automatically select contacts based on field values, behavior, or list membership.
/SegmentList segments
Returns all saved dynamic segments for the account.
OAuth scope : contacts:read
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | Segments returned successfully |
401 | Invalid or expired token |
/Segment/{id}/ContactList 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | Segment ID |
id_list | query | integer (int64) | Limit to this list (optional) | |
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | Contacts returned |
401 | Invalid or expired token |
404 | Segment or list not found |
/Segment/{id}/CountCount 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | Segment ID |
id_list | query | integer (int64) | Limit to this list (optional) |
Responses
| Code | Description |
|---|---|
200 | Number of contacts |
401 | Invalid or expired token |
404 | Segment or list not found |
Example 200
{
"total": 1250
}
/Segment/{id}/contact/{contactId}/checkCheck 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | Segment ID |
contactId | path | string (uuid) | yes | Contact GUID |
Responses
| Code | Description |
|---|---|
200 | Returns match result |
401 | Invalid or expired token |
404 | Segment or contact not found |
Send SMS messages to individual contacts or to entire contact lists. Uses each contact's phone_number field.
/Sms/SingleSMSSend 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)
| Name | Type | Required | Description |
|---|---|---|---|
phone_number | string | yes | Recipient phone number — accepts +41…, 0041… or 41… with or without separators. See Phone number format in the API description. |
message | string | yes | SMS message text (max 160 chars for standard encoding) |
date_planned | string (date-time) | Schedule the send. Omit to send immediately. |
Example
{
"phone_number": "+41791234567",
"message": "Your verification code is 9473. Valid for 10 minutes."
}
Responses
| Code | Description |
|---|---|
200 | SMS sent or scheduled successfully |
401 | Invalid or expired token |
422 | Invalid phone number format |
/Sms/CampaignSend 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)
| Name | Type | Required | Description |
|---|---|---|---|
lists | array of ListRef | yes | Target contact lists (uses each contact's phone_number field) |
message | string | yes | SMS message text |
date_planned | string (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
| Code | Description |
|---|---|
200 | SMS campaign sent or scheduled successfully |
401 | Invalid or expired token |
/SmsList SMS history
Returns the log of SMS sends (both single and campaign) for the account.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | SMS history returned successfully |
401 | Invalid or expired token |
/Sms/{id}Cancel a scheduled SMS
Cancels a pending scheduled SMS send.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | SMS send ID |
Responses
| Code | Description |
|---|---|
200 | Scheduled SMS cancelled successfully |
401 | Invalid or expired token |
404 | SMS send not found |
/Sms/ScheduledList scheduled SMS sends
Returns SMS sends with a future date_planned (not yet dispatched), ordered by date ascending.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | Scheduled SMS sends returned successfully |
401 | Invalid or expired token |
/Sms/{id}/CancelCancel a scheduled SMS
Cancels a scheduled SMS send only if it has not yet been dispatched.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | yes | SMS send ID |
Responses
| Code | Description |
|---|---|
200 | Scheduled SMS cancelled successfully |
401 | Invalid or expired token |
409 | SMS send not found, not owned by client, or already dispatched |
/Sms/{id}/RescheduleReschedule 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | yes | SMS send ID |
Request body (application/json)
| Name | Type | Required | Description |
|---|---|---|---|
date_planned | string (date-time) | Primary field name. | |
PlanDate | string (date-time) | Legacy alias accepted for backward compatibility. | |
scheduled_time | string (date-time) | Legacy alias accepted for backward compatibility. |
Responses
| Code | Description |
|---|---|
200 | Scheduled SMS rescheduled successfully |
400 | Missing date — provide one of date_planned / PlanDate / scheduled_time |
401 | Invalid or expired token |
409 | Reschedule failed (not found, already dispatched, or date out of range) |
Organize message templates into folders.
/FolderListList list folders
Returns the folders used to organize contact lists.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | query | string | Filter by folder name (partial) | |
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | List folders returned successfully |
401 | Invalid or expired token |
/FolderList/{id}Get a single list folder
Returns the details of one list folder by its ID.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer | yes | Folder ID |
Responses
| Code | Description |
|---|---|
200 | Folder returned successfully |
401 | Invalid or expired token |
404 | Folder not found |
/FolderMessageList message folders
Returns the folders used to organize email message templates.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | query | string | Filter by folder name | |
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | Message folders returned successfully |
401 | Invalid or expired token |
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.
/Import/UploadUpload 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:
id_list (integer) — target list IDfile (binary) — the CSV or XLSX file to uploadjson_match_fields (string, JSON) — which column of the file goes to which contact field. email_position = 0-based column holding the email (required), phone_position = column holding the mobile number or -1, fields = ids of the other fields (see GET /Field), fields_position = column of each of them, update_fields = update existing contacts. A missing or invalid mapping returns 400 and no import job is created.Optional:
webhook_url (string) — HTTPS URL called when processing finishes. Receives a JSON payload with the final status (success / failed) and counts of imported / skipped / invalid rows.File format:
json_match_fieldsRequest body (multipart/form-data)
| Name | Type | Required | Description |
|---|---|---|---|
id_list | integer (int64) | yes | ID of the destination list |
file | string (binary) | yes | CSV or XLSX file (UTF-8, max 50 MB) |
json_match_fields | string | yes | JSON mapping of the file columns to contact fields |
webhook_url | string (uri) | Callback URL invoked when processing finishes |
Responses
| Code | Description |
|---|---|
200 | File accepted and queued |
400 | No file uploaded, missing id_list, or missing / invalid json_match_fields |
401 | Invalid or expired token |
500 | Server or storage error |
Example 200
{
"result": "OK",
"import_job_id": 14,
"internal_name": "230575-20260420142615-contacts.csv"
}
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.
/WebhookList webhooks
Returns every webhook configured on the authenticated account.
Responses
| Code | Description |
|---|---|
200 | Array of webhooks |
401 | Invalid or expired token |
/WebhookCreate 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)
| Name | Type | Required | Description |
|---|---|---|---|
url | string (uri) | yes | HTTPS endpoint (HTTP refused in production) |
events | array of string | yes | List of event types to subscribe to (see catalog above). Use ["*"] to subscribe to all events of this API. |
description | string | Free-form description for your reference |
Example
{
"url": "https://example.com/hooks/mailpro",
"events": [
"email.delivered",
"email.bounced",
"contact.created"
],
"description": "Slack integration"
}
Responses
| Code | Description |
|---|---|
201 | Webhook created — secret is returned only here |
400 | Invalid URL, events list, or limit exceeded |
401 | Invalid 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"
}
/Webhook/{id}Get a webhook
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Responses
| Code | Description |
|---|---|
200 | The webhook |
404 | Webhook not found |
/Webhook/{id}Update a webhook
Partial update — only the sent fields are modified.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Request body (application/json)
| Name | Type | Required | Description |
|---|---|---|---|
url | string (uri) | ||
events | array of string | ||
description | string | ||
isActive | boolean | Set true to re-enable an auto-disabled webhook once your endpoint is fixed. |
Responses
| Code | Description |
|---|---|
200 | Updated webhook |
400 | Invalid payload |
404 | Webhook not found |
/Webhook/{id}Delete a webhook
Permanently deletes the webhook and its full delivery history.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Responses
| Code | Description |
|---|---|
204 | Deleted |
404 | Webhook not found |
/Webhook/{id}/testSend 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Request body (application/json)
| Name | Type | Required | Description |
|---|---|---|---|
eventType | string | Override the event type (must be subscribed by this webhook). ping always works. |
Example
{
"eventType": "ping"
}
Responses
| Code | Description |
|---|---|
202 | Event queued for dispatch |
404 | Webhook not found |
Example 202
{
"deliveryId": 1234,
"eventId": "evt_8f3a9b2c4d1e5f6789abcdef01234567",
"eventType": "ping"
}
/Webhook/{id}/rotate-secretRotate 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes |
Responses
| Code | Description |
|---|---|
200 | New secret (visible once) |
404 | Webhook not found |
Example 200
{
"secret": "whsec_XyzA9876BcdE5432FghI1098JklM7654NopQ"
}
/Webhook/{id}/deliveriesList 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | |
limit | query | integer | ||
cursor | query | integer (int64) | Pagination cursor (= last delivery ID seen). Returns deliveries with id < cursor. |
Responses
| Code | Description |
|---|---|
200 | Array of delivery attempts (newest first) |
404 | Webhook not found |
/Webhook/{id}/deliveries/{deliveryId}/replayReplay 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | |
deliveryId | path | integer (int64) | yes |
Responses
| Code | Description |
|---|---|
202 | Replay queued |
404 | Webhook or delivery not found |
Bounces and unsubscribers of the account: list, add, remove.
/Suppression/BouncesList the bounces of the account
Most recent first. The total is in the X-Total-Count header.
OAuth scope : email:read
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
type | query | string: hard, soft | hard or soft | |
source | query | string | Where the bounce comes from: list, contact, api or smtp | |
email | query | string | Exact address | |
dateFrom | query | string (date-time) | From this date | |
dateTo | query | string (date-time) | Up to this date | |
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | Bounces returned |
400 | type must be hard or soft |
401 | Invalid 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
}
]
/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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | id of the bounce |
source | query | string | yes | list, contact, api or smtp |
type | query | string: hard, soft | yes | hard or soft |
Responses
| Code | Description |
|---|---|
200 | Bounce removed |
400 | source or type missing |
401 | Invalid or expired token |
404 | Bounce not found |
Example 200
{
"result": "ok",
"id": 5521,
"restored": false
}
/Suppression/Bounces/{id}/RestorePut a contact with a hard bounce back in its lists
Only for a hard bounce with the source contact.
OAuth scope : contacts:write
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | id of the bounce |
Responses
| Code | Description |
|---|---|
200 | Contact put back |
401 | Invalid or expired token |
404 | Hard bounce of a contact not found, or address no longer active |
Example 200
{
"result": "ok",
"id": 5521,
"restored": true
}
/Suppression/UnsubscribersList the unsubscribers of the account
Most recent first. The total is in the X-Total-Count header.
OAuth scope : contacts:read
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
source | query | string | Where the unsubscribe comes from: contact, list, smtp, api or api3 | |
email | query | string | Exact address | |
dateFrom | query | string (date-time) | From this date | |
dateTo | query | string (date-time) | Up to this date | |
numberOfRecords | query | integer | ||
pageNumber | query | integer |
Responses
| Code | Description |
|---|---|
200 | Unsubscribers returned |
401 | Invalid 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"
}
]
/Suppression/UnsubscribersAdd 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)
| Name | Type | Required | Description |
|---|---|---|---|
email | string | yes | Address to unsubscribe |
comment | string | Free comment | |
id_list | integer (int64) | Unsubscribe list of the account (optional) |
Responses
| Code | Description |
|---|---|
200 | Done. already_unsubscribed is true when the address already was. |
400 | A valid email is required |
401 | Invalid or expired token |
404 | id_list is not an unsubscribe list of this account |
Example 200
{
"result": "ok",
"already_unsubscribed": false
}
/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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | integer (int64) | yes | id of the unsubscriber |
source | query | string | yes | contact, list, smtp, api or api3 |
Responses
| Code | Description |
|---|---|
200 | Unsubscriber removed |
400 | source is required |
401 | Invalid or expired token |
404 | Unsubscriber not found |
Example 200
{
"result": "ok",
"removed": 9012
}