Mailzzy Public API
The Mailzzy public API is a JSON REST API authenticated with an API key.
Use it to manage contacts, lists and tags, list campaigns, add contacts to automations, send transactional email, and subscribe to webhooks. This reference covers the API behind the Mailzzy app for Zapier.
Looking for the client-credentials API or the MCP server? See the developer docs.
Base URL
All requests go to this base URL; every path in this reference is relative to it. Requests and responses are JSON, over HTTPS.
https://services.softsages.com/crm/api/v1/mailzzyAuthentication
Create a key in Mailzzy under Settings › Client Credentials › Public API & Zapier keys (admins only) and send it in the X-API-Key header on every request:
curl https://services.softsages.com/crm/api/v1/mailzzy/me \
-H "X-API-Key: sk_live_kid_XXXXXXXXXXXXXXXX.YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"The full key is shown only once, when it is created. A key acts as the user who created it and stops working when it expires, is revoked, or that user is deactivated. Revocation takes effect within about 30 seconds.
Quickstart
1. Check your key. GET /me returns the key and the user it belongs to.
curl https://services.softsages.com/crm/api/v1/mailzzy/me \
-H "X-API-Key: <your API key>"2. Find a list to add contacts to. GET /lists returns your lists (called Groups in the Mailzzy app).
curl https://services.softsages.com/crm/api/v1/mailzzy/lists \
-H "X-API-Key: <your API key>"3. Create or update a contact. POST /contacts creates the contact, or updates the one with that email.
curl --request POST https://services.softsages.com/crm/api/v1/mailzzy/contacts \
-H "X-API-Key: <your API key>" \
-H "Content-Type: application/json" \
--data '{"email":"ada@example.com","first_name":"Ada","last_name":"Lovelace","list_ids":[12],"tags":["vip"]}'4. Get notified. POST /webhooks subscribes an HTTPS URL to an event; Mailzzy posts the event to it.
curl --request POST https://services.softsages.com/crm/api/v1/mailzzy/webhooks \
-H "X-API-Key: <your API key>" \
-H "Content-Type: application/json" \
--data '{"target_url":"https://example.com/mailzzy/webhooks","event_type":"contact.added_to_list","filters":{"list_id":12}}'Errors
Every error has the same body, with the HTTP status repeated in code:
{ "code": 422, "message": "This contact is blacklisted." }| Status | Meaning |
|---|---|
| 400 | The request is malformed (missing field, bad limit, bad since, bad cursor). |
| 401 | The API key is missing, invalid, expired or revoked. |
| 404 | The resource does not exist or belongs to another account. |
| 409 | Conflict: a list with that name exists, or an Idempotency-Key was reused with a different body or while the first request is still running. |
| 422 | The request is well-formed but was refused (blacklisted contact, plan contact limit reached, invalid phone number...). |
| 429 | Rate limit exceeded. |
| 503 | Temporarily unavailable; retry after a short wait. |
Rate limits
Each key may make about 120 requests per minute (counted per API server, so short bursts above that can pass). Above the limit the API answers 429 with a Retry-After: 60 header.
Pagination
List endpoints return { "data": [...], "next_cursor": "..." }. Pass next_cursor back as cursor to get the next page; it is null on the last page. limit is 1–100 (default 50). Cursors are opaque.
Timestamps
Responses use ISO-8601 with an offset, e.g. 2026-10-05T10:00:00-04:00. The since parameter accepts ISO-8601 with an offset or Z, a date-time without offset (read as UTC), or a plain date.
Idempotency
POST /contacts, POST /lists and POST /transactional-emails accept an Idempotency-Key header (up to 255 characters). Repeating a request with the same key and the same body within 24 hours returns the first response (with Idempotent-Replayed: true) instead of running it again. Use it for retries, especially for transactional email.
Subscription status
Creating or updating a contact never changes its subscription status. A contact who unsubscribed stays unsubscribed: creating or updating it again does not resubscribe it.
Webhooks
Subscribe an HTTPS URL to an event type with POST /webhooks and Mailzzy will POST a JSON event to it whenever that happens — from any source: the Mailzzy app, this API, CSV imports, forms, automations and email activity. The events are listed in the event catalog below.
Every delivery has these headers:
| Header | Meaning |
|---|---|
Mailzzy-Event | The event type, e.g. contact.added_to_list |
Mailzzy-Event-Id | Unique per event; the same on retries — use it to ignore duplicates |
Mailzzy-Delivery-Id | Unique per delivery attempt chain |
Mailzzy-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256(secret, t + "." + body)> |
Event catalog
| Event | When it is sent |
|---|---|
contact.created | A contact was added (app, API, form or CSV import). Filter: list_id. |
contact.updated | A contact's profile fields changed. |
contact.unsubscribed | A contact unsubscribed; data.source is api, app or link. Filter: campaign_id. |
contact.added_to_list | A contact was added to a list; data.list says which. Filter: list_id. |
contact.removed_from_list | A contact was removed from a list. Filter: list_id. |
contact.tag_added | A tag was newly added to a contact; data.tag says which. Filter: tag. |
form.submitted | A sign-up form was submitted; data.fields has the values. Filter: form_id. |
campaign.sent | A campaign finished sending. Filter: campaign_id. |
email.opened | A contact opened a campaign email for the first time. Filter: campaign_id. |
email.clicked | A contact clicked a link in a campaign email for the first time; data.url says which. Filter: campaign_id. |
email.bounced | A campaign email hard-bounced. Filter: campaign_id. |
contact.tag_removed | A tag was removed from a contact; data.tag says which. Filter: tag. |
contact.resubscribed | A contact that was unsubscribed (or bounced or marked spam) was re-subscribed. |
contact.deleted | A contact was deleted (archived); data.contact has only id and email. |
automation.completed | A contact completed an automation; data.automation says which. Filter: automation_id. |
Every event has the same envelope; data depends on the event type. An example contact.created body:
{
"id": "evt_3eb39a9eafed4ccc9d91a35506fffeae",
"type": "contact.created",
"api_version": "2026-10-01",
"created_at": "2026-10-05T10:00:00-04:00",
"account": "acme",
"data": {
"contact": {
"id": 123,
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"phone": "+14155550100",
"address": "12 Example Street",
"city": "London",
"state": "England",
"postal_code": "NW1 2DB",
"country": "GB",
"timezone": "Europe/London",
"status": "subscribed",
"tags": [
"vip"
],
"source": "manual",
"created_at": "2026-10-05T10:00:00-04:00",
"updated_at": "2026-10-05T10:00:00-04:00"
}
}
}Verifying signatures
Verify every request: compute HMAC-SHA256 of t + "." + <raw request body> with your webhook secret, compare it in constant time to v1, and reject requests whose t is more than 5 minutes old.
// Node.js (Express: use express.raw({ type: "application/json" }) so you get the raw body)
const crypto = require("crypto");
function verify(secret, header, rawBody) {
const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 || ""));
}# Python
import hmac, hashlib, time
def verify(secret, header, raw_body: bytes):
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
expected = hmac.new(secret.encode(), parts["t"].encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))Test vector: secret whsec_test, t=1700000000, body {"id":"evt_1"} → v1=c89214b5b5da833daed6f0b8c5bb6bd58cea9022bd80ccc78230f3942d632925.
Deliveries and retries
Respond with any 2xx within 10 seconds. Anything else (including timeouts, redirects and HTML pages) is retried after about 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h — 8 attempts in total — and then marked failed. Answer 410 Gone to stop a webhook immediately. After 20 failed deliveries in a row with no success for 24 hours, the webhook is paused and its creator is emailed. With Zapier, turn the Zap off and on again to start a new webhook.
Events are delivered at least once and not necessarily in order. email.opened fires on a contact's first open of a campaign and email.clicked on their first click of each link. A CSV import sends per-contact events for its first 5,000 contacts only.
The API key and the user it belongs to.
Who this key belongs to
Use it to test a key. Zapier uses it as the connection test.
Responses
OK
The key and its owner.
Unauthorized
The API key is missing, invalid, expired or revoked.
Too Many Requests
Rate limit exceeded.
curl --request GET \
--url https://services.softsages.com/crm/api/v1/mailzzy/me \
--header 'X-API-Key: <your API key>'{
"account": "acme",
"app": "mailzzy",
"user": {
"email": "string",
"name": "Newsletter"
},
"key": {
"id": "kid_AbCdEf123456",
"name": "Newsletter",
"rate_limit_per_min": 120,
"expires_on": "2026-10-05T10:00:00-04:00"
},
"capabilities": [
"string"
]
}Create, update, find, unsubscribe and archive contacts; manage their lists, tags, notes and automations.
List contacts, or find one by email
Contacts are returned newest first. Archived (deleted) contacts are never returned. With email, returns at most one contact (an empty data array when there is none) and ignores the other parameters.
Query Parameters
emailstring (email)optionalFind the contact with exactly this email (case-insensitive).
statusContactStatus (enum)optionalOne of: subscribed, unsubscribed, bounced, spam.
list_idinteger (int64)optionalOnly members of this list.
sincestringoptionalOnly contacts created or updated at or after this time.
e.g. 2026-10-05T10:00:00Z
cursorstringoptionalThe next_cursor from the previous page.
limitintegeroptionalDefault 50. 1–100.
Responses
OK
A page of contacts.
Bad Request
The request is malformed.
Unauthorized
The API key is missing, invalid, expired or revoked.
Not Found
Not found, or not visible to this key.
Too Many Requests
Rate limit exceeded.
curl --request GET \
--url https://services.softsages.com/crm/api/v1/mailzzy/contacts \
--header 'X-API-Key: <your API key>'{
"data": [
{
"id": 123,
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"phone": "+14155550100",
"address": "12 Example Street",
"city": "London",
"state": "England",
"postal_code": "NW1 2DB",
"country": "GB",
"timezone": "Europe/London",
"status": "subscribed",
"tags": [
"vip"
],
"source": "manual",
"created_at": "2026-10-05T10:00:00-04:00",
"updated_at": "2026-10-05T10:00:00-04:00"
}
],
"next_cursor": null
}Create or update a contact by email
If no contact has this email, it is created as subscribed and added to list_ids, subject to the same checks as adding a contact in Mailzzy (valid email, not blacklisted, plan contact limit).
If a contact with this email exists, the fields you send are updated (fields you omit are kept), the contact is added to any of list_ids it is not already in, and tags are added. Its subscription status is not changed, and an unsubscribed, bounced or spam contact is not added to any list.
Adding a contact to a list starts any "added to list" automations, as in Mailzzy.
Headers
Idempotency-KeystringoptionalMakes retries safe for 24 hours. X-Idempotency-Key is also accepted. Up to 255 characters.
Request Body · application/json
first_namestringoptionallast_namestringoptionalphonestringoptionaladdressstringoptionalcitystringoptionalstatestringoptionalpostal_codestringoptionalcountrystringoptionaltimezonestringoptionaltagsarray of stringoptionalemailstring (email)requiredlist_idsarray of integer (int64)requiredResponses
OK
An existing contact was updated.
Created
The contact was created.
Bad Request
The request is malformed.
Unauthorized
The API key is missing, invalid, expired or revoked.
Not Found
Not found, or not visible to this key.
Conflict
Conflict.
Unprocessable Entity
The request was refused.
Too Many Requests
Rate limit exceeded.
curl --request POST \
--url https://services.softsages.com/crm/api/v1/mailzzy/contacts \
--header 'X-API-Key: <your API key>' \
--header 'Content-Type: application/json' \
--data '{
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"list_ids": [
12
],
"tags": [
"vip"
]
}'{
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"list_ids": [
12
],
"tags": [
"vip"
]
}{
"id": 123,
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"phone": "+14155550100",
"address": "12 Example Street",
"city": "London",
"state": "England",
"postal_code": "NW1 2DB",
"country": "GB",
"timezone": "Europe/London",
"status": "subscribed",
"tags": [
"vip"
],
"source": "manual",
"created_at": "2026-10-05T10:00:00-04:00",
"updated_at": "2026-10-05T10:00:00-04:00"
}Delete (archive) a contact
Archives the contact, as deleting it in Mailzzy does: it is removed from its lists and tags and no longer returned by the API. Fires contact.deleted. Needs the contact delete permission.
Path Parameters
idintegerrequiredResponses
OK
The contact was archived.
Unauthorized
The API key is missing, invalid, expired or revoked.
Not Found
Not found, or not visible to this key.
Too Many Requests
Rate limit exceeded.
curl --request DELETE \
--url https://services.softsages.com/crm/api/v1/mailzzy/contacts/123 \
--header 'X-API-Key: <your API key>'{
"id": 123,
"status": "archived"
}Add a note to a contact
Path Parameters
idintegerrequiredHeaders
Idempotency-KeystringoptionalMakes retries safe for 24 hours. X-Idempotency-Key is also accepted. Up to 255 characters.
Request Body · application/json
notestringrequiredTrimmed; 1 to 10,000 characters. Up to 10000 characters.
Responses
Created
The note was added.
Bad Request
The request is malformed.
Unauthorized
The API key is missing, invalid, expired or revoked.
Not Found
Not found, or not visible to this key.
Conflict
Conflict.
Unprocessable Entity
The request was refused.
Too Many Requests
Rate limit exceeded.
curl --request POST \
--url https://services.softsages.com/crm/api/v1/mailzzy/contacts/123/notes \
--header 'X-API-Key: <your API key>' \
--header 'Content-Type: application/json' \
--data '{
"note": "Called about the renewal."
}'{
"note": "Called about the renewal."
}{
"id": 123,
"contact_id": 123,
"note": "string",
"created_at": "2026-10-05T10:00:00-04:00"
}Unsubscribe a contact
Has no effect on a contact that is not subscribed. The change is applied asynchronously.
Path Parameters
idintegerrequiredResponses
OK
The contact, shown as unsubscribed.
Unauthorized
The API key is missing, invalid, expired or revoked.
Not Found
Not found, or not visible to this key.
Too Many Requests
Rate limit exceeded.
curl --request POST \
--url https://services.softsages.com/crm/api/v1/mailzzy/contacts/123/unsubscribe \
--header 'X-API-Key: <your API key>'{
"id": 123,
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"phone": "+14155550100",
"address": "12 Example Street",
"city": "London",
"state": "England",
"postal_code": "NW1 2DB",
"country": "GB",
"timezone": "Europe/London",
"status": "subscribed",
"tags": [
"vip"
],
"source": "manual",
"created_at": "2026-10-05T10:00:00-04:00",
"updated_at": "2026-10-05T10:00:00-04:00"
}Add a contact to a list
Does nothing if the contact is already in the list. Starts "added to list" automations. An unsubscribed, bounced or spam contact, or a blacklisted email, is refused with 422.
Path Parameters
idintegerrequiredlistIdinteger (int64)requiredResponses
OK
The contact.
Unauthorized
The API key is missing, invalid, expired or revoked.
Not Found
Not found, or not visible to this key.
Unprocessable Entity
The request was refused.
Too Many Requests
Rate limit exceeded.
curl --request POST \
--url https://services.softsages.com/crm/api/v1/mailzzy/contacts/123/lists/12 \
--header 'X-API-Key: <your API key>'{
"id": 123,
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"phone": "+14155550100",
"address": "12 Example Street",
"city": "London",
"state": "England",
"postal_code": "NW1 2DB",
"country": "GB",
"timezone": "Europe/London",
"status": "subscribed",
"tags": [
"vip"
],
"source": "manual",
"created_at": "2026-10-05T10:00:00-04:00",
"updated_at": "2026-10-05T10:00:00-04:00"
}Remove a contact from a list
Does nothing if the contact is not in the list. Starts "removed from list" automations.
Path Parameters
idintegerrequiredlistIdinteger (int64)requiredResponses
OK
The contact.
Unauthorized
The API key is missing, invalid, expired or revoked.
Not Found
Not found, or not visible to this key.
Too Many Requests
Rate limit exceeded.
curl --request DELETE \
--url https://services.softsages.com/crm/api/v1/mailzzy/contacts/123/lists/12 \
--header 'X-API-Key: <your API key>'{
"id": 123,
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"phone": "+14155550100",
"address": "12 Example Street",
"city": "London",
"state": "England",
"postal_code": "NW1 2DB",
"country": "GB",
"timezone": "Europe/London",
"status": "subscribed",
"tags": [
"vip"
],
"source": "manual",
"created_at": "2026-10-05T10:00:00-04:00",
"updated_at": "2026-10-05T10:00:00-04:00"
}Add tags to a contact
Tags are lower-cased; tags that don't exist yet are created.
Path Parameters
idintegerrequiredRequest Body · application/json
tagsarray of stringrequiredResponses
OK
The contact.
Bad Request
The request is malformed.
Unauthorized
The API key is missing, invalid, expired or revoked.
Not Found
Not found, or not visible to this key.
Too Many Requests
Rate limit exceeded.
curl --request POST \
--url https://services.softsages.com/crm/api/v1/mailzzy/contacts/123/tags \
--header 'X-API-Key: <your API key>' \
--header 'Content-Type: application/json' \
--data '{
"tags": [
"vip",
"webinar-2026"
]
}'{
"tags": [
"vip",
"webinar-2026"
]
}{
"id": 123,
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"phone": "+14155550100",
"address": "12 Example Street",
"city": "London",
"state": "England",
"postal_code": "NW1 2DB",
"country": "GB",
"timezone": "Europe/London",
"status": "subscribed",
"tags": [
"vip"
],
"source": "manual",
"created_at": "2026-10-05T10:00:00-04:00",
"updated_at": "2026-10-05T10:00:00-04:00"
}Remove a tag from a contact
Path Parameters
idintegerrequiredtagstringrequiredResponses
OK
The contact.
Unauthorized
The API key is missing, invalid, expired or revoked.
Not Found
Not found, or not visible to this key.
Too Many Requests
Rate limit exceeded.
curl --request DELETE \
--url https://services.softsages.com/crm/api/v1/mailzzy/contacts/123/tags/vip \
--header 'X-API-Key: <your API key>'{
"id": 123,
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"phone": "+14155550100",
"address": "12 Example Street",
"city": "London",
"state": "England",
"postal_code": "NW1 2DB",
"country": "GB",
"timezone": "Europe/London",
"status": "subscribed",
"tags": [
"vip"
],
"source": "manual",
"created_at": "2026-10-05T10:00:00-04:00",
"updated_at": "2026-10-05T10:00:00-04:00"
}Contact lists (Groups in the Mailzzy app) and tags.
List contact lists
All lists, alphabetical, in one page (next_cursor is always null).
Responses
OK
The lists.
Unauthorized
The API key is missing, invalid, expired or revoked.
Too Many Requests
Rate limit exceeded.
curl --request GET \
--url https://services.softsages.com/crm/api/v1/mailzzy/lists \
--header 'X-API-Key: <your API key>'{
"data": [
{
"id": 123,
"name": "Newsletter",
"member_count": 123,
"created_at": "2026-10-05T10:00:00-04:00",
"updated_at": "2026-10-05T10:00:00-04:00"
}
],
"next_cursor": null
}Create a contact list
Headers
Idempotency-KeystringoptionalMakes retries safe for 24 hours. X-Idempotency-Key is also accepted. Up to 255 characters.
Request Body · application/json
namestringrequiredUp to 100 characters.
Responses
Created
The new list.
Bad Request
The request is malformed.
Unauthorized
The API key is missing, invalid, expired or revoked.
Conflict
Conflict.
Unprocessable Entity
The request was refused.
Too Many Requests
Rate limit exceeded.
curl --request POST \
--url https://services.softsages.com/crm/api/v1/mailzzy/lists \
--header 'X-API-Key: <your API key>' \
--header 'Content-Type: application/json' \
--data '{
"name": "Newsletter"
}'{
"name": "Newsletter"
}{
"id": 123,
"name": "Newsletter",
"member_count": 123,
"created_at": "2026-10-05T10:00:00-04:00",
"updated_at": "2026-10-05T10:00:00-04:00"
}List tags
All tags, alphabetical, in one page.
Responses
OK
The tags.
Unauthorized
The API key is missing, invalid, expired or revoked.
Too Many Requests
Rate limit exceeded.
curl --request GET \
--url https://services.softsages.com/crm/api/v1/mailzzy/tags \
--header 'X-API-Key: <your API key>'{
"data": [
{
"id": 123,
"name": "Newsletter",
"member_count": 123,
"created_at": "2026-10-05T10:00:00-04:00",
"updated_at": "2026-10-05T10:00:00-04:00"
}
],
"next_cursor": null
}List automations and add contacts to them.
Add a contact to an automation
Enrols the contact in an Active automation that uses the Contact added manually trigger (list them with GET /automations?trigger=manually_added). The contact then runs through the automation asynchronously. 422 when the automation is not active or does not use that trigger, or when the contact can't enter it (its re-entry setting, or the automation's entry conditions).
Path Parameters
idintegerrequiredautomationIdintegerrequiredHeaders
Idempotency-KeystringoptionalMakes retries safe for 24 hours. X-Idempotency-Key is also accepted. Up to 255 characters.
Responses
Accepted
The contact was enrolled.
Unauthorized
The API key is missing, invalid, expired or revoked.
Not Found
Not found, or not visible to this key.
Conflict
Conflict.
Unprocessable Entity
The request was refused.
Too Many Requests
Rate limit exceeded.
curl --request POST \
--url https://services.softsages.com/crm/api/v1/mailzzy/contacts/123/automations/7 \
--header 'X-API-Key: <your API key>'{
"status": "enrolled",
"contact_id": 123,
"automation_id": 123
}List active automations
All Active automations, by name. trigger=manually_added returns only those a contact can be added to.
Query Parameters
triggerstringoptionalOne of: manually_added.
Responses
OK
The automations (one page; next_cursor is always null).
Bad Request
The request is malformed.
Unauthorized
The API key is missing, invalid, expired or revoked.
Too Many Requests
Rate limit exceeded.
curl --request GET \
--url https://services.softsages.com/crm/api/v1/mailzzy/automations \
--header 'X-API-Key: <your API key>'{
"data": [
{
"id": 123,
"name": "Newsletter",
"status": "active",
"trigger": null
}
],
"next_cursor": null
}List campaigns.
List campaigns
Query Parameters
statusstringoptionalOnly campaigns in this status. Omit for all. One of: submitted, in_review, in_progress, completed, rejected, failed, draft, ab_test_waiting.
cursorstringoptionalThe next_cursor from the previous page.
limitintegeroptionalDefault 50. 1–100.
Responses
OK
A page of campaigns.
Bad Request
The request is malformed.
Unauthorized
The API key is missing, invalid, expired or revoked.
Too Many Requests
Rate limit exceeded.
curl --request GET \
--url https://services.softsages.com/crm/api/v1/mailzzy/campaigns \
--header 'X-API-Key: <your API key>'{
"data": [
{
"id": 123,
"name": "Newsletter",
"subject": "Welcome to Mailzzy",
"status": null,
"type": null,
"sender_email": null,
"scheduled_at": "2026-10-05T10:00:00-04:00",
"completed_at": "string",
"created_at": "2026-10-05T10:00:00-04:00",
"updated_at": "2026-10-05T10:00:00-04:00",
"metrics": {
"requested": 123,
"sent": 123,
"delivered": 123,
"opened": 123,
"clicked": 123,
"unsubscribed": 123,
"bounced": 123
}
}
],
"next_cursor": null
}Email templates, sign-up forms and sending domains.
List email templates
Most recently updated first.
Query Parameters
cursorstringoptionalThe next_cursor from the previous page.
limitintegeroptionalDefault 50. 1–100.
Responses
OK
A page of templates.
Bad Request
The request is malformed.
Unauthorized
The API key is missing, invalid, expired or revoked.
Too Many Requests
Rate limit exceeded.
curl --request GET \
--url https://services.softsages.com/crm/api/v1/mailzzy/templates \
--header 'X-API-Key: <your API key>'{
"data": [
{
"id": 123,
"name": "Newsletter",
"category": null,
"subject": "Welcome to Mailzzy",
"created_at": "2026-10-05T10:00:00-04:00",
"updated_at": "2026-10-05T10:00:00-04:00"
}
],
"next_cursor": null
}List sign-up forms
Query Parameters
cursorstringoptionalThe next_cursor from the previous page.
limitintegeroptionalDefault 50. 1–100.
Responses
OK
A page of forms.
Bad Request
The request is malformed.
Unauthorized
The API key is missing, invalid, expired or revoked.
Too Many Requests
Rate limit exceeded.
curl --request GET \
--url https://services.softsages.com/crm/api/v1/mailzzy/forms \
--header 'X-API-Key: <your API key>'{
"data": [
{
"id": 123,
"name": "Newsletter",
"status": null,
"type": null,
"opt_in_mode": null,
"public_url": null,
"views": 123,
"submissions": 123,
"created_at": "2026-10-05T10:00:00-04:00",
"updated_at": "2026-10-05T10:00:00-04:00"
}
],
"next_cursor": null
}Send one-off emails to a single recipient.
List verified sending domains
The account's verified, active sending domains. Pass one as domain when sending a transactional email.
Responses
OK
All verified sending domains (not paged).
Unauthorized
The API key is missing, invalid, expired or revoked.
Too Many Requests
Rate limit exceeded.
curl --request GET \
--url https://services.softsages.com/crm/api/v1/mailzzy/domains \
--header 'X-API-Key: <your API key>'{
"data": [
{
"id": "acme.com",
"name": "acme.com"
}
],
"next_cursor": null
}Send a transactional email
Sends a one-off email, such as a receipt or a password reset, to a single recipient. from must be one of your verified senders. The email is queued and sent asynchronously and counts toward your plan's transactional email allowance. Always send an Idempotency-Key so a retry cannot send the email twice.
Headers
Idempotency-KeystringoptionalMakes retries safe for 24 hours. X-Idempotency-Key is also accepted. Up to 255 characters.
Request Body · application/json
tostring (email)requiredThe single recipient.
fromstring (email)requiredA verified sender email of your account.
subjectstringoptionalRequired with html; with template_id, defaults to the template's subject.
htmlstringoptionalThe HTML body. Not allowed together with template_id.
template_idintegeroptionalSend a saved email template (see GET /templates) instead of html.
variablesobjectoptionalValues for {{KEY}} tokens in the subject and HTML ({{ first_name }} and {{FIRST_NAME}} both match the key first_name). Values are HTML-escaped in the body, not in the subject; tokens without a value are left unchanged.
reply_tostring (email)optionaldomainstringoptionalSending domain (an active domain of your account). Defaults to the domain of from.
Responses
Accepted
The email was accepted for delivery.
Bad Request
The request is malformed.
Unauthorized
The API key is missing, invalid, expired or revoked.
Conflict
Conflict.
Unprocessable Entity
The request was refused.
Too Many Requests
Rate limit exceeded.
curl --request POST \
--url https://services.softsages.com/crm/api/v1/mailzzy/transactional-emails \
--header 'X-API-Key: <your API key>' \
--header 'Content-Type: application/json' \
--data '{
"to": "ada@example.com",
"from": "orders@yourcompany.com",
"subject": "Your receipt",
"html": "<p>Thanks for your order!</p>",
"reply_to": "support@example.com"
}'{
"to": "ada@example.com",
"from": "orders@yourcompany.com",
"subject": "Your receipt",
"html": "<p>Thanks for your order!</p>",
"reply_to": "support@example.com"
}{
"status": "accepted",
"message": null
}Subscribe a URL to an event, and remove the subscription.
Create a webhook
Subscribes an HTTPS URL to one event type. The response contains the signing secret — the only time it is shown. Up to 25 webhooks per account. The URL must be public HTTPS (port 443 or 1024+).
Request Body · application/json
target_urlstring (uri)requiredUp to 2048 characters.
event_typeWebhookEventType (enum)requiredOne of: contact.created, contact.updated, contact.unsubscribed, contact.added_to_list, contact.removed_from_list, contact.tag_added, contact.tag_removed, contact.resubscribed, contact.deleted, form.submitted, campaign.sent, email.opened, email.clicked, email.bounced, automation.completed.
filtersWebhookFiltersoptionalOptional; only events with these values are sent. Supported keys depend on the event type (see the event catalog).
Responses
Created
The webhook, with its secret.
Bad Request
The request is malformed.
Unauthorized
The API key is missing, invalid, expired or revoked.
Conflict
Conflict.
Unprocessable Entity
The request was refused.
curl --request POST \
--url https://services.softsages.com/crm/api/v1/mailzzy/webhooks \
--header 'X-API-Key: <your API key>' \
--header 'Content-Type: application/json' \
--data '{
"target_url": "https://example.com/mailzzy/webhooks",
"event_type": "contact.added_to_list",
"filters": {
"list_id": 12
}
}'{
"target_url": "https://example.com/mailzzy/webhooks",
"event_type": "contact.added_to_list",
"filters": {
"list_id": 12
}
}{
"id": "whk_3f2c...",
"event_type": "contact.created",
"target_url": "string",
"filters": {
"list_id": 123,
"form_id": 123,
"campaign_id": 123,
"tag": "vip",
"automation_id": 123
},
"status": "active",
"paused_reason": null,
"consecutive_failures": 123,
"last_success_at": "2026-10-05T10:00:00-04:00",
"last_failure_at": "2026-10-05T10:00:00-04:00",
"created_at": "2026-10-05T10:00:00-04:00",
"secret": "whsec_9f8e...",
"secret_note": "string"
}Delete a webhook
Also removes its delivery history.
Path Parameters
webhookIdstringrequiredResponses
No Content
Deleted.
Unauthorized
The API key is missing, invalid, expired or revoked.
Not Found
Not found, or not visible to this key.
curl --request DELETE \
--url https://services.softsages.com/crm/api/v1/mailzzy/webhooks/whk_123 \
--header 'X-API-Key: <your API key>'// No response body