API reference
The mxsin.com HTTP API sends email, reports what happened to it and manages templates, suppressions and contact lists. It is a JSON API over HTTPS. This page lists every route the API accepts.
Authentication
Base URL: https://mxsin.com/api/v1. Every request carries an API key as a bearer token:
Authorization: Bearer mx_…
Create keys in the panel under API keys & SMTP. A key is shown once, when it is created; we store only a hash of it. Keep keys on your server, never in browser or mobile code. Request bodies are JSON (Content-Type: application/json), and responses are JSON.
Limits
- Requests: 600 a minute per account, plus a per-address limit at our web server. Over the limit you get
429. - Daily quota: every recipient counts. New accounts start in a sandbox (200 a day, own addresses and verified domains only) and move to production automatically once their domain is authenticated and early sending is clean. Production quotas then grow step by step with good bounce and complaint rates.
GET /accountreturns your current numbers. - Sending rate: messages over your per-second rate wait briefly in the queue; they are not refused.
- Size: up to 50 recipients for
/send, 500 for/send/bulk; message size depends on your plan (10–40 MB).
Errors
Errors return a JSON body such as {"error": "Need 1-50 recipients"}.
| Status | Meaning |
|---|---|
400 | The request is malformed or a field is invalid. The error message names the field. |
401 | Missing or unknown API key. |
403 | Not allowed: unverified sender domain, suspended or paused account, or a protected suppression. |
404 | No such route or object. |
413 | The message is larger than your plan allows. |
422 | Content rejected, for example by the virus scanner. |
429 | A limit was reached: requests per minute, or your daily quota. Wait and retry. |
500 | Something failed on our side. Retry with backoff. |
503 | A dependency is briefly unavailable. Retry shortly. |
Retry 429, 500 and 503 with exponential backoff. Do not retry other 4xx errors unchanged.
Client libraries
Small single-file libraries wrap every route below, raise errors with the HTTP status, and include a signature check for webhooks and exports. They have no dependencies beyond the language’s standard library.
Webhook and export signatures
Webhooks and scheduled exports are signed. The X-Mxsin-Signature header reads t=<unix time>,v1=<hex>, where the hex value is HMAC-SHA256 of <t>.<raw body> using the endpoint’s signing secret. Compare in constant time and reject timestamps older than five minutes. Failed webhook deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours.
POST /send
Send a message. Queues one message to up to 50 recipients (to, cc and bcc together). The From domain must be added to your account and verified. Each recipient becomes its own message, so bounces and suppressions apply per recipient; the response maps each recipient to its message id and status (queued or suppressed).
| Field | Type | Required | Description |
|---|---|---|---|
from | string | Yes | "Name <addr@yourdomain>" or a bare address on a verified domain |
to | string | array | Yes | One or more recipients |
cc, bcc | array | No | Extra recipients |
subject | string | Yes | Required unless the template has one |
text, html | string | Yes | At least one, unless a template provides it |
reply_to | string | No | Reply-To address |
headers | object | No | Custom X- headers |
tag | string | No | 1–64 characters: letters, digits, _ . : - |
metadata | object | No | Up to 2 KB, returned in lookups and webhooks |
stream | string | No | Message stream name |
template | string | No | Template name |
variables | object | No | Values for {{placeholders}}; HTML-escaped in the HTML part |
curl -X POST 'https://mxsin.com/api/v1/send' \
-H 'Authorization: Bearer mx_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{
"from": "Your App <hello@example.com>",
"to": [
"customer@example.org"
],
"subject": "Your receipt",
"text": "Thanks for your order.",
"tag": "receipt",
"stream": "transactional"
}'require 'mxsin.php';
$mx = new Mxsin\Client(getenv('MXSIN_API_KEY'));
$res = $mx->send([
'from' => 'Your App <hello@example.com>',
'to' => [
'customer@example.org'
],
'subject' => 'Your receipt',
'text' => 'Thanks for your order.',
'tag' => 'receipt',
'stream' => 'transactional'
]);const { Mxsin } = require('./mxsin.js');
const mx = new Mxsin(process.env.MXSIN_API_KEY);
const res = await mx.call('POST', '/send', {
"from": "Your App <hello@example.com>",
"to": [
"customer@example.org"
],
"subject": "Your receipt",
"text": "Thanks for your order.",
"tag": "receipt",
"stream": "transactional"
});import os
from mxsin import Mxsin
mx = Mxsin(os.environ['MXSIN_API_KEY'])
res = mx.call('POST', '/send', {
"from": "Your App <hello@example.com>",
"to": [
"customer@example.org"
],
"subject": "Your receipt",
"text": "Thanks for your order.",
"tag": "receipt",
"stream": "transactional"
})Example response
{
"message_ids": {
"customer@example.org": 1234
},
"status": {
"customer@example.org": "queued"
}
}POST /send/bulk
Bulk send with a template. Sends one template to up to 500 recipients in a single call, each with their own variables, or to the subscribed contacts of a list (optionally one topic). Every recipient counts against your daily quota, and the whole call is refused if it would go over. Messages are queued and paced at your account’s sending rate.
| Field | Type | Required | Description |
|---|---|---|---|
from | string | Yes | Sender on a verified domain |
template | string | Yes | Template name |
recipients | array | No | Up to 500 objects: {"to": "...", "variables": {...}} |
list_id | integer | No | Instead of recipients: send to this list’s subscribed contacts (500 at most); contact fields become variables |
topic | string | No | With list_id: only contacts opted in to this topic |
default_variables | object | No | Variables shared by every recipient (per-recipient values win) |
subject | string | No | Overrides the template subject |
stream | string | No | Message stream name |
tag | string | No | Statistics tag |
curl -X POST 'https://mxsin.com/api/v1/send/bulk' \
-H 'Authorization: Bearer mx_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{
"from": "news@example.com",
"template": "monthly-update",
"stream": "newsletter",
"default_variables": {
"month": "November"
},
"recipients": [
{
"to": "alex@example.org",
"variables": {
"name": "Alex"
}
},
{
"to": "sam@example.org",
"variables": {
"name": "Sam"
}
}
]
}'require 'mxsin.php';
$mx = new Mxsin\Client(getenv('MXSIN_API_KEY'));
$res = $mx->sendBulk([
'from' => 'news@example.com',
'template' => 'monthly-update',
'stream' => 'newsletter',
'default_variables' => [
'month' => 'November'
],
'recipients' => [
[
'to' => 'alex@example.org',
'variables' => [
'name' => 'Alex'
]
],
[
'to' => 'sam@example.org',
'variables' => [
'name' => 'Sam'
]
]
]
]);const { Mxsin } = require('./mxsin.js');
const mx = new Mxsin(process.env.MXSIN_API_KEY);
const res = await mx.call('POST', '/send/bulk', {
"from": "news@example.com",
"template": "monthly-update",
"stream": "newsletter",
"default_variables": {
"month": "November"
},
"recipients": [
{
"to": "alex@example.org",
"variables": {
"name": "Alex"
}
},
{
"to": "sam@example.org",
"variables": {
"name": "Sam"
}
}
]
});import os
from mxsin import Mxsin
mx = Mxsin(os.environ['MXSIN_API_KEY'])
res = mx.call('POST', '/send/bulk', {
"from": "news@example.com",
"template": "monthly-update",
"stream": "newsletter",
"default_variables": {
"month": "November"
},
"recipients": [
{
"to": "alex@example.org",
"variables": {
"name": "Alex"
}
},
{
"to": "sam@example.org",
"variables": {
"name": "Sam"
}
}
]
})Example response
{
"accepted": 2,
"results": [
{
"to": "alex@example.org",
"message_id": 1235,
"status": "queued"
},
{
"to": "sam@example.org",
"message_id": 1236,
"status": "queued"
}
]
}GET /messages/{id}
Look up a message. Status, SMTP response, opens, clicks, metadata and the full event timeline of one message.
curl -X GET 'https://mxsin.com/api/v1/messages/1234' \ -H 'Authorization: Bearer mx_YOUR_KEY'
require 'mxsin.php';
$mx = new Mxsin\Client(getenv('MXSIN_API_KEY'));
$res = $mx->call('GET', '/messages/1234');const { Mxsin } = require('./mxsin.js');
const mx = new Mxsin(process.env.MXSIN_API_KEY);
const res = await mx.call('GET', '/messages/1234');import os
from mxsin import Mxsin
mx = Mxsin(os.environ['MXSIN_API_KEY'])
res = mx.call('GET', '/messages/1234')Example response
{
"id": 1234,
"rcpt": "customer@example.org",
"status": "delivered",
"events": [
{
"type": "sent",
"at": 1760000000
},
{
"type": "delivered",
"at": 1760000003
}
]
}GET /events
List events. Delivery events for your messages, oldest first. Page with after= set to the last id you received.
| Field | Type | Required | Description |
|---|---|---|---|
after | integer | No | Return events with an id greater than this |
type | string | No | sent, delivered, deferred, bounced, complained, opened, clicked or unsubscribed |
limit | integer | No | 1–500, default 100 |
curl -X GET 'https://mxsin.com/api/v1/events' \ -H 'Authorization: Bearer mx_YOUR_KEY'
require 'mxsin.php';
$mx = new Mxsin\Client(getenv('MXSIN_API_KEY'));
$res = $mx->call('GET', '/events');const { Mxsin } = require('./mxsin.js');
const mx = new Mxsin(process.env.MXSIN_API_KEY);
const res = await mx.call('GET', '/events');import os
from mxsin import Mxsin
mx = Mxsin(os.environ['MXSIN_API_KEY'])
res = mx.call('GET', '/events')Example response
{
"events": [
{
"id": 9001,
"type": "delivered",
"at": 1760000003,
"message_id": 1234,
"to": "customer@example.org",
"detail": "250 2.0.0 OK"
}
],
"next_after": 9001
}GET /templates
List templates. Names, subjects and the placeholders each template uses.
curl -X GET 'https://mxsin.com/api/v1/templates' \ -H 'Authorization: Bearer mx_YOUR_KEY'
require 'mxsin.php';
$mx = new Mxsin\Client(getenv('MXSIN_API_KEY'));
$res = $mx->call('GET', '/templates');const { Mxsin } = require('./mxsin.js');
const mx = new Mxsin(process.env.MXSIN_API_KEY);
const res = await mx.call('GET', '/templates');import os
from mxsin import Mxsin
mx = Mxsin(os.environ['MXSIN_API_KEY'])
res = mx.call('GET', '/templates')Example response
{
"templates": [
{
"name": "welcome",
"subject": "Welcome, {{name}}",
"variables": [
"name",
"link"
],
"updated_at": 1760000000
}
]
}GET /templates/{name}
Get a template. One template with its HTML and text parts.
curl -X GET 'https://mxsin.com/api/v1/templates/welcome' \ -H 'Authorization: Bearer mx_YOUR_KEY'
require 'mxsin.php';
$mx = new Mxsin\Client(getenv('MXSIN_API_KEY'));
$res = $mx->call('GET', '/templates/welcome');const { Mxsin } = require('./mxsin.js');
const mx = new Mxsin(process.env.MXSIN_API_KEY);
const res = await mx.call('GET', '/templates/welcome');import os
from mxsin import Mxsin
mx = Mxsin(os.environ['MXSIN_API_KEY'])
res = mx.call('GET', '/templates/welcome')Example response
{
"name": "welcome",
"subject": "Welcome, {{name}}",
"html": "<p>Hi {{name}}</p>",
"text": "Hi {{name}}",
"variables": [
"name"
]
}GET /suppressions
List suppressions. Addresses that will not be mailed, newest first.
| Field | Type | Required | Description |
|---|---|---|---|
after | integer | No | Return entries with an id lower than this (paging) |
limit | integer | No | 1–500, default 100 |
curl -X GET 'https://mxsin.com/api/v1/suppressions' \ -H 'Authorization: Bearer mx_YOUR_KEY'
require 'mxsin.php';
$mx = new Mxsin\Client(getenv('MXSIN_API_KEY'));
$res = $mx->call('GET', '/suppressions');const { Mxsin } = require('./mxsin.js');
const mx = new Mxsin(process.env.MXSIN_API_KEY);
const res = await mx.call('GET', '/suppressions');import os
from mxsin import Mxsin
mx = Mxsin(os.environ['MXSIN_API_KEY'])
res = mx.call('GET', '/suppressions')Example response
{
"suppressions": [
{
"id": 77,
"email": "gone@example.org",
"reason": "bounce",
"created_at": 1760000000
}
],
"next_after": 77
}POST /suppressions
Add a suppression. Stops all future mail to an address from your account.
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Address to suppress |
detail | string | No | Note, up to 200 characters |
curl -X POST 'https://mxsin.com/api/v1/suppressions' \
-H 'Authorization: Bearer mx_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{
"email": "no-more@example.org",
"detail": "Asked by phone"
}'require 'mxsin.php';
$mx = new Mxsin\Client(getenv('MXSIN_API_KEY'));
$res = $mx->call('POST', '/suppressions', [
'email' => 'no-more@example.org',
'detail' => 'Asked by phone'
]);const { Mxsin } = require('./mxsin.js');
const mx = new Mxsin(process.env.MXSIN_API_KEY);
const res = await mx.call('POST', '/suppressions', {
"email": "no-more@example.org",
"detail": "Asked by phone"
});import os
from mxsin import Mxsin
mx = Mxsin(os.environ['MXSIN_API_KEY'])
res = mx.call('POST', '/suppressions', {
"email": "no-more@example.org",
"detail": "Asked by phone"
})Example response
{
"email": "no-more@example.org",
"reason": "manual"
}DELETE /suppressions/{email}
Remove a suppression. Removes a manual or bounce suppression. Complaints and unsubscribes can only be removed by the recipient (through the preference page) or by our support team.
curl -X DELETE 'https://mxsin.com/api/v1/suppressions/alex%40example.org' \ -H 'Authorization: Bearer mx_YOUR_KEY'
require 'mxsin.php';
$mx = new Mxsin\Client(getenv('MXSIN_API_KEY'));
$res = $mx->call('DELETE', '/suppressions/alex%40example.org');const { Mxsin } = require('./mxsin.js');
const mx = new Mxsin(process.env.MXSIN_API_KEY);
const res = await mx.call('DELETE', '/suppressions/alex%40example.org');import os
from mxsin import Mxsin
mx = Mxsin(os.environ['MXSIN_API_KEY'])
res = mx.call('DELETE', '/suppressions/alex%40example.org')Example response
{
"deleted": true
}GET /lists
List contact lists. Your lists with their topics and contact counts.
curl -X GET 'https://mxsin.com/api/v1/lists' \ -H 'Authorization: Bearer mx_YOUR_KEY'
require 'mxsin.php';
$mx = new Mxsin\Client(getenv('MXSIN_API_KEY'));
$res = $mx->call('GET', '/lists');const { Mxsin } = require('./mxsin.js');
const mx = new Mxsin(process.env.MXSIN_API_KEY);
const res = await mx.call('GET', '/lists');import os
from mxsin import Mxsin
mx = Mxsin(os.environ['MXSIN_API_KEY'])
res = mx.call('GET', '/lists')Example response
{
"lists": [
{
"id": 3,
"name": "Newsletter",
"topics": [
{
"name": "product-news",
"label": "Product news",
"default_on": true
}
],
"contacts": 1200,
"subscribed": 1150
}
]
}POST /lists
Create a list. Creates a list, optionally with subscription topics.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Up to 80 characters |
topics | array | No | Topic labels, for example ["Product news", "Events"] |
curl -X POST 'https://mxsin.com/api/v1/lists' \
-H 'Authorization: Bearer mx_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{
"name": "Newsletter",
"topics": [
"Product news",
"Events"
]
}'require 'mxsin.php';
$mx = new Mxsin\Client(getenv('MXSIN_API_KEY'));
$res = $mx->call('POST', '/lists', [
'name' => 'Newsletter',
'topics' => [
'Product news',
'Events'
]
]);const { Mxsin } = require('./mxsin.js');
const mx = new Mxsin(process.env.MXSIN_API_KEY);
const res = await mx.call('POST', '/lists', {
"name": "Newsletter",
"topics": [
"Product news",
"Events"
]
});import os
from mxsin import Mxsin
mx = Mxsin(os.environ['MXSIN_API_KEY'])
res = mx.call('POST', '/lists', {
"name": "Newsletter",
"topics": [
"Product news",
"Events"
]
})Example response
{
"id": 3,
"name": "Newsletter"
}GET /lists/{id}/contacts
List contacts. Contacts on a list with their status and topic choices. Status is subscribed, unsubscribed or suppressed.
| Field | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number, 100 per page |
curl -X GET 'https://mxsin.com/api/v1/lists/3/contacts' \ -H 'Authorization: Bearer mx_YOUR_KEY'
require 'mxsin.php';
$mx = new Mxsin\Client(getenv('MXSIN_API_KEY'));
$res = $mx->call('GET', '/lists/3/contacts');const { Mxsin } = require('./mxsin.js');
const mx = new Mxsin(process.env.MXSIN_API_KEY);
const res = await mx.call('GET', '/lists/3/contacts');import os
from mxsin import Mxsin
mx = Mxsin(os.environ['MXSIN_API_KEY'])
res = mx.call('GET', '/lists/3/contacts')Example response
{
"contacts": [
{
"email": "alex@example.org",
"status": "subscribed",
"fields": {
"name": "Alex"
},
"topics": {
"product-news": true
},
"preferences_url": "https://mxsin.com/p/41.abc"
}
],
"page": 1
}POST /lists/{id}/contacts
Add or update a contact. Adds a contact or updates an existing one. Addresses on your suppression list are stored but are never mailed. Only add people who asked to receive your mail.
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Contact address |
fields | object | No | Up to 30 values used as template variables |
topics | object | No | {"topic-name": true|false} |
curl -X POST 'https://mxsin.com/api/v1/lists/3/contacts' \
-H 'Authorization: Bearer mx_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{
"email": "alex@example.org",
"fields": {
"name": "Alex"
},
"topics": {
"product-news": true
}
}'require 'mxsin.php';
$mx = new Mxsin\Client(getenv('MXSIN_API_KEY'));
$res = $mx->call('POST', '/lists/3/contacts', [
'email' => 'alex@example.org',
'fields' => [
'name' => 'Alex'
],
'topics' => [
'product-news' => true
]
]);const { Mxsin } = require('./mxsin.js');
const mx = new Mxsin(process.env.MXSIN_API_KEY);
const res = await mx.call('POST', '/lists/3/contacts', {
"email": "alex@example.org",
"fields": {
"name": "Alex"
},
"topics": {
"product-news": true
}
});import os
from mxsin import Mxsin
mx = Mxsin(os.environ['MXSIN_API_KEY'])
res = mx.call('POST', '/lists/3/contacts', {
"email": "alex@example.org",
"fields": {
"name": "Alex"
},
"topics": {
"product-news": True
}
})Example response
{
"email": "alex@example.org",
"status": "subscribed"
}DELETE /lists/{id}/contacts/{email}
Remove a contact. Deletes the contact from the list. It does not add a suppression.
curl -X DELETE 'https://mxsin.com/api/v1/lists/3/contacts/alex%40example.org' \ -H 'Authorization: Bearer mx_YOUR_KEY'
require 'mxsin.php';
$mx = new Mxsin\Client(getenv('MXSIN_API_KEY'));
$res = $mx->call('DELETE', '/lists/3/contacts/alex%40example.org');const { Mxsin } = require('./mxsin.js');
const mx = new Mxsin(process.env.MXSIN_API_KEY);
const res = await mx.call('DELETE', '/lists/3/contacts/alex%40example.org');import os
from mxsin import Mxsin
mx = Mxsin(os.environ['MXSIN_API_KEY'])
res = mx.call('DELETE', '/lists/3/contacts/alex%40example.org')Example response
{
"deleted": true
}GET /account
Account limits and usage. Your current sending stage, quota, rate and usage in the last 24 hours.
curl -X GET 'https://mxsin.com/api/v1/account' \ -H 'Authorization: Bearer mx_YOUR_KEY'
require 'mxsin.php';
$mx = new Mxsin\Client(getenv('MXSIN_API_KEY'));
$res = $mx->call('GET', '/account');const { Mxsin } = require('./mxsin.js');
const mx = new Mxsin(process.env.MXSIN_API_KEY);
const res = await mx.call('GET', '/account');import os
from mxsin import Mxsin
mx = Mxsin(os.environ['MXSIN_API_KEY'])
res = mx.call('GET', '/account')Example response
{
"stage": "production",
"daily_quota": 1000,
"per_second": 2,
"sent_24h": 120,
"bounce_rate_7d": 0.4,
"complaint_rate_7d": 0
}Signed in? The panel’s API docs page fills in your own domain in the examples. Questions: contact us.