API Reference
SparkPost-compatible REST API for sending email, tracking delivery, and receiving real-time webhook events.
https://proiton.com/api/v1
Authentication
All API requests require an API key passed in the Authorization header. Create API keys from your dashboard.
Authorization: mu_live_<40-hex-characters>
| Header | Value |
|---|---|
Authorization | Your API key (e.g. mu_live_a1b2c3d4...) |
Content-Type | application/json |
Rate Limiting
API requests are limited to 60 requests per minute per API key. Exceeding this limit returns 429 Too Many Requests.
Error Handling
All errors return a JSON object with an errors array:
{
"errors": [
{
"message": "Domain 'example.com' is not registered to your account."
}
]
}
| Code | Meaning |
|---|---|
200 | Success |
202 | Accepted (transmission queued) |
204 | No Content (successful delete) |
400 | Bad request or validation error |
401 | Invalid or missing API key |
404 | Resource not found |
422 | Unprocessable entity |
429 | Rate limit exceeded |
SMTP Injection
Send emails via standard SMTP as an alternative to the REST API. Use your API key as the SMTP password. All features (tracking, webhooks, IP pools, suppressions) work identically.
| Setting | Value |
|---|---|
| Host | smtp.proiton.com |
| Port | 2525 |
| Encryption | STARTTLS |
| Username | SMTP_Injection (any value accepted) |
| Password | Your API key |
| Max recipients/message | 50 |
| Max message size | 25 MB |
Metadata via X-MSYS-API Header
Pass campaign IDs, metadata, and options using a JSON header in your email:
X-MSYS-API: {"campaign_id": "summer2024", "metadata": {"user_id": "123"}, "options": {"transactional": true}}
Differences from REST API
| Feature | REST API | SMTP |
|---|---|---|
| Template substitution | Yes ({{var}} syntax) | No (send pre-rendered email) |
| Max recipients | 2,000 per transmission | 50 per message |
| Per-recipient data | Yes (substitution_data, metadata) | No |
| Attachments | Base64 in JSON | Standard MIME |
SMTP Code Examples
PHP (PHPMailer)
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
$mail->isSMTP();
$mail->Host = 'smtp.proiton.com';
$mail->Port = 2525;
$mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS;
$mail->SMTPAuth = true;
$mail->Username = 'SMTP_Injection';
$mail->Password = 'mu_live_YOUR_API_KEY';
$mail->setFrom('[email protected]', 'My App');
$mail->addAddress('[email protected]', 'John');
$mail->Subject = 'Welcome!';
$mail->Body = '<h1>Hello!</h1>';
$mail->AltBody = 'Hello!';
$mail->send();
Python
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
msg = MIMEMultipart('alternative')
msg['From'] = '[email protected]'
msg['To'] = '[email protected]'
msg['Subject'] = 'Welcome!'
msg.attach(MIMEText('Hello!', 'plain'))
msg.attach(MIMEText('<h1>Hello!</h1>', 'html'))
with smtplib.SMTP('smtp.proiton.com', 2525) as server:
server.starttls()
server.login('SMTP_Injection', 'mu_live_YOUR_API_KEY')
server.send_message(msg)
Node.js (Nodemailer)
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
host: 'smtp.proiton.com',
port: 2525,
secure: false,
auth: { user: 'SMTP_Injection', pass: 'mu_live_YOUR_API_KEY' },
});
await transporter.sendMail({
from: '[email protected]',
to: '[email protected]',
subject: 'Welcome!',
text: 'Hello!',
html: '<h1>Hello!</h1>',
});
Send Email
/api/v1/transmissions
Queue emails for delivery. Returns 202 Accepted.
{
"recipients": [
{
"address": {
"email": "[email protected]",
"name": "Recipient Name"
},
"substitution_data": {
"custom_key": "custom_value"
},
"metadata": {
"user_id": "123"
}
}
],
"content": {
"from": {
"email": "[email protected]",
"name": "Sender Name"
},
"subject": "Hello {{address.name or 'there'}}",
"html": "<p>Welcome {{first_name or 'Guest'}}!</p>",
"text": "Welcome {{first_name or 'Guest'}}!",
"reply_to": "[email protected]",
"attachments": [
{
"name": "document.pdf",
"type": "application/pdf",
"data": "<base64-encoded-content>"
}
],
"inline_images": [
{
"name": "logo",
"type": "image/png",
"data": "<base64-encoded-content>"
}
]
},
"substitution_data": {
"global_key": "global_value"
},
"metadata": {
"campaign_id": "summer2024"
},
"description": "Summer campaign batch 1",
"options": {
"transactional": false
}
}
recipients required
Array of 1 to 2,000 recipient objects.
| Field | Type | Required | Description |
|---|---|---|---|
address.email | string | Yes | Recipient email address |
address.name | string | No | Display name (max 255) |
substitution_data | object | No | Per-recipient template variables |
metadata | object | No | Per-recipient metadata (included in webhooks) |
content required
| Field | Type | Required | Description |
|---|---|---|---|
from.email | string | Yes | Sender email (domain must be verified) |
from.name | string | No | Sender display name (max 255) |
subject | string | Yes | Subject line (max 998, supports templates) |
html | string | Yes* | HTML body (required if text is absent) |
text | string | Yes* | Plain text body (required if html is absent) |
reply_to | string | No | Reply-to email address |
attachments | array | No | File attachments (25 MB total limit) |
inline_images | array | No | Inline images (CID-referenced) |
* At least one of html or text is required.
Attachments & Inline Images
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Filename or CID name |
type | string | Yes | MIME type (e.g. application/pdf) |
data | string | Yes | Base64-encoded file content |
Top-level fields
| Field | Type | Required | Description |
|---|---|---|---|
substitution_data | object | No | Global template variables |
metadata | object | No | Global metadata (included in webhooks) |
description | string | No | Transmission label (max 255) |
options.transactional | boolean | No | If true, only non-transactional suppressions apply |
{
"results": {
"total_rejected_recipients": 0,
"total_accepted_recipients": 1,
"id": "t-abc12345"
}
}
| Field | Description |
|---|---|
id | Transmission ID (format: t-<8-chars>) |
total_accepted_recipients | Emails queued for delivery |
total_rejected_recipients | Emails rejected (suppressed recipients) |
Get Transmission
/api/v1/transmissions/{id}
Returns the status of a transmission.
{
"results": {
"transmission_id": "t-abc12345",
"state": "completed",
"description": "Summer campaign batch 1",
"rcpt_to": 100,
"total_accepted_recipients": 100,
"total_rejected_recipients": 0,
"created_at": "2024-06-15T10:30:00+00:00"
}
}
Template Syntax
All string fields (subject, html, text) support lightweight template variables.
| Syntax | Description | Example |
|---|---|---|
{{var}} |
HTML-escaped variable | Hello {{name}} |
{{{var}}} |
Raw / unescaped output | {{{html_content}}} |
{{var or 'default'}} |
Fallback if null or empty | Hi {{name or 'there'}} |
{{a.b.c}} |
Dot notation for nested data | Order #{{order.id}} |
Substitution Data
Variables are resolved in this priority order (highest wins):
- Per-recipient — from each recipient's
substitution_data - Global — from top-level
substitution_data
These built-in variables are always available per recipient:
{
"address": {
"name": "Recipient Name",
"email": "[email protected]"
}
}
Query Events
/api/v1/message-events
Search and filter email delivery events.
| Parameter | Description |
|---|---|
events | Comma-separated event types (e.g. delivery,bounce,open) |
recipients | Comma-separated email addresses |
transmission_id | Filter by transmission ID |
from | Start time (ISO 8601 or Unix timestamp) |
to | End time (ISO 8601 or Unix timestamp) |
per_page | Results per page (default 100, max 1000) |
{
"results": [
{
"type": "delivery",
"message_id": "550e8400-e29b-41d4-a716-446655440000",
"recipient": "[email protected]",
"timestamp": "2024-06-15T10:35:00+00:00",
"transmission_id": "t-abc12345",
"subject": "Summer Campaign",
"detail": null
}
],
"total_count": 1,
"links": {
"next": null,
"previous": null
}
}
Event Types
| Event | Description |
|---|---|
queued | Email accepted and queued for delivery |
delivered | Successfully delivered to recipient |
bounced | Permanent failure (hard bounce, 5xx) |
failed | Delivery failed (soft bounce expiry or rejection) |
deferred | Temporary deferral, retry pending |
suppressed | Recipient on suppression list |
opened | Email opened (if tracking enabled) |
clicked | Link clicked (if tracking enabled) |
unsubscribed | Recipient unsubscribed |
Create Webhook
/api/v1/webhooks
Register an endpoint to receive real-time event notifications.
{
"name": "My Webhook",
"target": "https://api.example.com/webhooks/proiton",
"events": ["injection", "delivery", "bounce", "open", "click"],
"auth_type": "token",
"auth_token": "my-secret-token",
"sending_domain": "yourdomain.com"
}
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Webhook label (max 255) |
target | string | Yes | HTTPS URL to POST events to |
events | array | Yes | Event types to subscribe to (see below) |
auth_type | string | No | none (default), token, or basic |
auth_token | string | No | Token value or base64 user:pass |
sending_domain | string | No | Scope to one domain (account-wide if omitted) |
Valid event types
injection
delivery
bounce
policy_rejection
delay
open
click
list_unsubscribe
{
"results": {
"id": "1",
"name": "My Webhook",
"target": "https://api.example.com/webhooks/proiton",
"sending_domain": "yourdomain.com",
"auth_type": "token",
"events": ["injection", "delivery", "bounce", "open", "click"],
"active": true,
"links": []
}
}
List Webhooks
/api/v1/webhooks
Returns all webhooks for your account.
Update Webhook
/api/v1/webhooks/{id}
Partial updates are supported — send only the fields you want to change.
{
"name": "Updated Name",
"target": "https://api.example.com/webhooks/new-url",
"events": ["delivery", "bounce", "open"],
"active": false
}
Delete Webhook
/api/v1/webhooks/{id}
Returns 204 No Content.
Test Webhook
/api/v1/webhooks/{id}/test
Sends a fake injection event to verify connectivity. Returns 200 on success or 422 on failure.
Webhook Payload Format
Events are POSTed as a JSON array. Each event is wrapped in a container depending on type.
Message Events
Used for: injection, delivery, bounce, policy_rejection, delay
[
{
"msys": {
"message_event": {
"type": "delivery",
"event_id": "123456",
"timestamp": 1718440500,
"message_id": "550e8400-e29b-41d4-a716-446655440000",
"rcpt_to": "[email protected]",
"msg_from": "[email protected]",
"friendly_from": "Sender Name <[email protected]>",
"subject": "Summer Campaign",
"customer_id": "5",
"transmission_id": "t-abc12345",
"rcpt_meta": {"user_id": "123"},
"rcpt_tags": [],
"sending_domain": "domain.com",
"campaign_id": "summer2024",
"raw_reason": "",
"reason": "",
"bounce_class": "",
"classification_name": "",
"classification_category": ""
}
}
}
]
Track Events
Used for: open, click, list_unsubscribe
[
{
"msys": {
"track_event": {
"type": "click",
"event_id": "789012",
"timestamp": 1718440600,
"message_id": "550e8400-e29b-41d4-a716-446655440000",
"rcpt_to": "[email protected]",
"customer_id": "5",
"transmission_id": "t-abc12345",
"rcpt_meta": {},
"rcpt_tags": [],
"campaign_id": "summer2024",
"ip_address": "192.0.2.1",
"user_agent": "Mozilla/5.0...",
"geo_ip": {"country": "US"},
"target_link_url": "https://example.com/promo"
}
}
}
]
Event Type Mapping
Proiton internal events map to SparkPost webhook types:
Webhook type | Container | When it fires |
|---|---|---|
injection | message_event | Email queued for delivery |
delivery | message_event | Successfully delivered |
bounce | message_event | Hard bounce (5xx) or soft bounce expiry |
policy_rejection | message_event | Suppressed or rejected |
delay | message_event | Temporary deferral (retry pending) |
open | track_event | Email opened |
click | track_event | Link clicked |
list_unsubscribe | track_event | Recipient unsubscribed |
Bounce Classification Codes
bounce_class | Name | Category |
|---|---|---|
1 | Undetermined | Hard Bounce |
10 | Invalid Recipient | Hard Bounce |
25 | Admin Failure | Hard Bounce |
30 | Generic Bounce: No RCPT | Hard Bounce |
20 | Generic Soft Bounce | Soft Bounce |
22 | Mailbox Full | Soft Bounce |
24 | Timeout | Soft Bounce |
50 | Reputation | Soft Bounce |
52 | Rate Limited | Soft Bounce |
Webhook Delivery & Retry
| Setting | Value |
|---|---|
| Delivery | Near-instant via async worker |
| Success | Any HTTP 2xx response |
| Retry backoff | 1m, 2m, 4m, 8m, 16m, 32m, 64m, 120m |
| Max attempts | 8 |
| Auto-disable | After 50 consecutive failures |
Authentication headers
auth_type | Header sent |
|---|---|
none | No auth header |
token | X-MessageSystems-Webhook-Token: <your-token> |
basic | Authorization: Basic <base64(user:pass)> |
List Suppressions
/api/v1/suppression-list
Returns suppressed recipients for your account.
| Parameter | Description |
|---|---|
from | Start date (ISO 8601) |
to | End date (ISO 8601) |
types | Comma-separated: transactional, non_transactional |
sources | Comma-separated: hard_bounce, manual, list_unsubscribe |
limit | Max results (default 100, max 1000) |
{
"results": [
{
"recipient": "[email protected]",
"type": ["non_transactional", "transactional"],
"source": "hard_bounce",
"description": "550 5.1.1 User unknown",
"created": "2026-02-13T12:01:05+00:00",
"updated": "2026-02-13T12:01:05+00:00"
}
]
}
Check Recipient
/api/v1/suppression-list/{email}
Check if a specific email address is on your suppression list. Returns 404 if not suppressed.
{
"results": [
{
"recipient": "[email protected]",
"type": ["non_transactional"],
"source": "manual",
"description": "Opted out",
"created": "2026-02-10T08:00:00+00:00",
"updated": "2026-02-10T08:00:00+00:00"
}
]
}
Add / Update Suppressions
/api/v1/suppression-list
Bulk add or update suppressions. If a recipient already exists, it is updated.
{
"recipients": [
{
"recipient": "[email protected]",
"type": "non_transactional",
"description": "Opted out of marketing"
},
{
"recipient": "[email protected]",
"type": "transactional",
"description": "Do not contact"
}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
recipient | string | Yes | Email address to suppress |
type | string | No | transactional or non_transactional (default) |
description | string | No | Reason for suppression (max 255) |
{
"results": {
"message": "2 suppression(s) updated."
}
}
Remove Suppression
/api/v1/suppression-list/{email}
Remove an email address from the suppression list. Returns 204 No Content on success or 404 if not found.
Create Sending Domain
/api/v1/sending-domains
Register a new sending domain. A DKIM keypair is automatically generated. Configure the DNS records shown in the response to verify ownership.
{
"domain": "yourdomain.com",
"tracking_domain": "track.yourdomain.com"
}
| Field | Type | Required | Description |
|---|---|---|---|
domain | string | Yes | Fully qualified domain name |
tracking_domain | string | No | Custom domain for open/click tracking |
generate_dkim | boolean | No | Generate DKIM keys (default: true) |
dkim_key_length | integer | No | 1024 or 2048 (default: 2048) |
{
"results": {
"message": "Successfully Created domain.",
"domain": "yourdomain.com",
"dkim": {
"public": "MIGfMA0GCSqGSIb3DQEB...",
"selector": "default",
"signing_domain": "yourdomain.com",
"headers": "from:to:subject:date"
}
}
}
List Sending Domains
/api/v1/sending-domains
Returns all sending domains for your account. Use query parameters to filter by verification status.
| Parameter | Values | Description |
|---|---|---|
ownership_verified | true, false | Filter by ownership verification |
dkim_status | valid, invalid, unverified | Filter by DKIM status |
spf_status | valid, invalid, unverified | Filter by SPF status |
cname_status | valid, invalid, unverified | Filter by bounce CNAME status |
{
"results": [
{
"domain": "yourdomain.com",
"tracking_domain": "track.yourdomain.com",
"status": {
"ownership_verified": true,
"spf_status": "valid",
"dkim_status": "valid",
"cname_status": "unverified",
"compliance_status": "valid",
"verification_token": "abc123..."
},
"dkim": {
"public": "MIGfMA0GCSqGSIb3DQEB...",
"selector": "default",
"signing_domain": "yourdomain.com",
"headers": "from:to:subject:date"
},
"is_default_bounce_domain": false,
"shared_with_subaccounts": false
}
]
}
Get Sending Domain
/api/v1/sending-domains/{domain}
Returns details for a single sending domain, including current verification status and DKIM configuration.
{
"results": {
"domain": "yourdomain.com",
"tracking_domain": "track.yourdomain.com",
"status": {
"ownership_verified": true,
"spf_status": "valid",
"dkim_status": "valid",
"cname_status": "valid",
"compliance_status": "valid",
"verification_token": "abc123..."
},
"dkim": {
"public": "MIGfMA0GCSqGSIb3DQEB...",
"selector": "default",
"signing_domain": "yourdomain.com",
"headers": "from:to:subject:date"
},
"is_default_bounce_domain": false,
"shared_with_subaccounts": false
}
}
Status fields
| Field | Values | Description |
|---|---|---|
ownership_verified | true / false | Whether the ownership TXT record was found |
spf_status | valid, invalid, unverified | SPF record verification status |
dkim_status | valid, invalid, unverified | DKIM record verification status |
cname_status | valid, invalid, unverified | Bounce CNAME verification status |
compliance_status | valid | Compliance review status |
verification_token | string | Token for the ownership TXT record |
Update Sending Domain
/api/v1/sending-domains/{domain}
Update domain settings. Send only the fields you want to change.
{
"tracking_domain": "track.yourdomain.com",
"open_tracking": true,
"click_tracking": true
}
| Field | Type | Description |
|---|---|---|
tracking_domain | string | Custom tracking domain (set to null to remove) |
open_tracking | boolean | Enable/disable open tracking pixel |
click_tracking | boolean | Enable/disable click tracking |
{
"results": {
"message": "Successfully Updated Domain.",
"domain": "yourdomain.com"
}
}
Verify Sending Domain
/api/v1/sending-domains/{domain}/verify
Trigger DNS verification for a sending domain. Checks ownership TXT, SPF, DKIM, DMARC, bounce CNAME, and tracking CNAME records. Returns current status and any errors to help debug DNS configuration.
{
"dkim_verify": true,
"spf_verify": true
}
| Field | Type | Description |
|---|---|---|
dkim_verify | boolean | Request DKIM verification (default: all checks run) |
spf_verify | boolean | Request SPF verification (default: all checks run) |
{
"results": {
"ownership_verified": true,
"spf_status": "valid",
"dkim_status": "valid",
"cname_status": "unverified",
"compliance_status": "valid",
"dns": {
"dkim_record": "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEB...",
"dkim_error": "",
"spf_record": "v=spf1 include:proitonmail.com ~all",
"spf_error": ""
}
}
}
The dns object shows the expected record values and any error messages when a check fails. Use this to diagnose misconfigured DNS records.
Delete Sending Domain
/api/v1/sending-domains/{domain}
Remove a sending domain from your account. Returns 204 No Content.
Example: cURL
curl -X POST https://proiton.com/api/v1/transmissions \
-H "Authorization: mu_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipients": [
{"address": {"email": "[email protected]", "name": "John"}}
],
"content": {
"from": {"email": "[email protected]", "name": "My App"},
"subject": "Welcome {{address.name}}!",
"html": "<h1>Hello {{address.name}}</h1><p>Welcome aboard!</p>",
"text": "Hello {{address.name}}, welcome aboard!"
}
}'
Example: PHP
$client = new \GuzzleHttp\Client();
$response = $client->post('https://proiton.com/api/v1/transmissions', [
'headers' => [
'Authorization' => 'mu_live_YOUR_API_KEY',
'Content-Type' => 'application/json',
],
'json' => [
'recipients' => [
['address' => ['email' => '[email protected]', 'name' => 'John']],
],
'content' => [
'from' => ['email' => '[email protected]', 'name' => 'My App'],
'subject' => 'Welcome {{address.name}}!',
'html' => '<h1>Hello {{address.name}}</h1><p>Welcome!</p>',
],
],
]);
$result = json_decode($response->getBody(), true);
echo $result['results']['id']; // t-abc12345
Example: JavaScript
const response = await fetch('https://proiton.com/api/v1/transmissions', {
method: 'POST',
headers: {
'Authorization': 'mu_live_YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
recipients: [
{ address: { email: '[email protected]', name: 'John' } },
],
content: {
from: { email: '[email protected]', name: 'My App' },
subject: 'Welcome {{address.name}}!',
html: '<h1>Hello {{address.name}}</h1><p>Welcome!</p>',
},
}),
});
const { results } = await response.json();
console.log(results.id); // t-abc12345