API Reference

SparkPost-compatible REST API for sending email, tracking delivery, and receiving real-time webhook events.

Base URL https://proiton.com/api/v1

Authentication

All API requests require an API key passed in the Authorization header. Create API keys from your dashboard.

HTTP Header
Authorization: mu_live_<40-hex-characters>
HeaderValue
AuthorizationYour API key (e.g. mu_live_a1b2c3d4...)
Content-Typeapplication/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:

Error Response
{
  "errors": [
    {
      "message": "Domain 'example.com' is not registered to your account."
    }
  ]
}
CodeMeaning
200Success
202Accepted (transmission queued)
204No Content (successful delete)
400Bad request or validation error
401Invalid or missing API key
404Resource not found
422Unprocessable entity
429Rate 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.

SettingValue
Hostsmtp.proiton.com
Port2525
EncryptionSTARTTLS
UsernameSMTP_Injection (any value accepted)
PasswordYour API key
Max recipients/message50
Max message size25 MB

Metadata via X-MSYS-API Header

Pass campaign IDs, metadata, and options using a JSON header in your email:

Custom Header
X-MSYS-API: {"campaign_id": "summer2024", "metadata": {"user_id": "123"}, "options": {"transactional": true}}

Differences from REST API

FeatureREST APISMTP
Template substitutionYes ({{var}} syntax)No (send pre-rendered email)
Max recipients2,000 per transmission50 per message
Per-recipient dataYes (substitution_data, metadata)No
AttachmentsBase64 in JSONStandard MIME

SMTP Code Examples

PHP (PHPMailer)

Send via SMTP
$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

Send via SMTP
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)

Send via SMTP
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

POST /api/v1/transmissions

Queue emails for delivery. Returns 202 Accepted.

Request Body
{
  "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.

FieldTypeRequiredDescription
address.emailstringYesRecipient email address
address.namestringNoDisplay name (max 255)
substitution_dataobjectNoPer-recipient template variables
metadataobjectNoPer-recipient metadata (included in webhooks)

content required

FieldTypeRequiredDescription
from.emailstringYesSender email (domain must be verified)
from.namestringNoSender display name (max 255)
subjectstringYesSubject line (max 998, supports templates)
htmlstringYes*HTML body (required if text is absent)
textstringYes*Plain text body (required if html is absent)
reply_tostringNoReply-to email address
attachmentsarrayNoFile attachments (25 MB total limit)
inline_imagesarrayNoInline images (CID-referenced)

* At least one of html or text is required.

Attachments & Inline Images

FieldTypeRequiredDescription
namestringYesFilename or CID name
typestringYesMIME type (e.g. application/pdf)
datastringYesBase64-encoded file content

Top-level fields

FieldTypeRequiredDescription
substitution_dataobjectNoGlobal template variables
metadataobjectNoGlobal metadata (included in webhooks)
descriptionstringNoTransmission label (max 255)
options.transactionalbooleanNoIf true, only non-transactional suppressions apply
Response — 202 Accepted
{
  "results": {
    "total_rejected_recipients": 0,
    "total_accepted_recipients": 1,
    "id": "t-abc12345"
  }
}
FieldDescription
idTransmission ID (format: t-<8-chars>)
total_accepted_recipientsEmails queued for delivery
total_rejected_recipientsEmails rejected (suppressed recipients)

Get Transmission

GET /api/v1/transmissions/{id}

Returns the status of a transmission.

Response — 200 OK
{
  "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.

SyntaxDescriptionExample
{{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):

  1. Per-recipient — from each recipient's substitution_data
  2. Global — from top-level substitution_data

These built-in variables are always available per recipient:

Built-in Variables
{
  "address": {
    "name": "Recipient Name",
    "email": "[email protected]"
  }
}

Query Events

GET /api/v1/message-events

Search and filter email delivery events.

ParameterDescription
eventsComma-separated event types (e.g. delivery,bounce,open)
recipientsComma-separated email addresses
transmission_idFilter by transmission ID
fromStart time (ISO 8601 or Unix timestamp)
toEnd time (ISO 8601 or Unix timestamp)
per_pageResults per page (default 100, max 1000)
Response — 200 OK
{
  "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

EventDescription
queuedEmail accepted and queued for delivery
deliveredSuccessfully delivered to recipient
bouncedPermanent failure (hard bounce, 5xx)
failedDelivery failed (soft bounce expiry or rejection)
deferredTemporary deferral, retry pending
suppressedRecipient on suppression list
openedEmail opened (if tracking enabled)
clickedLink clicked (if tracking enabled)
unsubscribedRecipient unsubscribed

Create Webhook

POST /api/v1/webhooks

Register an endpoint to receive real-time event notifications.

Request Body
{
  "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"
}
FieldTypeRequiredDescription
namestringYesWebhook label (max 255)
targetstringYesHTTPS URL to POST events to
eventsarrayYesEvent types to subscribe to (see below)
auth_typestringNonone (default), token, or basic
auth_tokenstringNoToken value or base64 user:pass
sending_domainstringNoScope to one domain (account-wide if omitted)

Valid event types

injection delivery bounce policy_rejection delay open click list_unsubscribe

Response — 200 OK
{
  "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

GET /api/v1/webhooks

Returns all webhooks for your account.

Update Webhook

PUT /api/v1/webhooks/{id}

Partial updates are supported — send only the fields you want to change.

Request Body (all fields optional)
{
  "name": "Updated Name",
  "target": "https://api.example.com/webhooks/new-url",
  "events": ["delivery", "bounce", "open"],
  "active": false
}

Delete Webhook

DELETE /api/v1/webhooks/{id}

Returns 204 No Content.

Test Webhook

POST /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
[
  {
    "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
[
  {
    "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 typeContainerWhen it fires
injectionmessage_eventEmail queued for delivery
deliverymessage_eventSuccessfully delivered
bouncemessage_eventHard bounce (5xx) or soft bounce expiry
policy_rejectionmessage_eventSuppressed or rejected
delaymessage_eventTemporary deferral (retry pending)
opentrack_eventEmail opened
clicktrack_eventLink clicked
list_unsubscribetrack_eventRecipient unsubscribed

Bounce Classification Codes

bounce_classNameCategory
1UndeterminedHard Bounce
10Invalid RecipientHard Bounce
25Admin FailureHard Bounce
30Generic Bounce: No RCPTHard Bounce
20Generic Soft BounceSoft Bounce
22Mailbox FullSoft Bounce
24TimeoutSoft Bounce
50ReputationSoft Bounce
52Rate LimitedSoft Bounce

Webhook Delivery & Retry

SettingValue
DeliveryNear-instant via async worker
SuccessAny HTTP 2xx response
Retry backoff1m, 2m, 4m, 8m, 16m, 32m, 64m, 120m
Max attempts8
Auto-disableAfter 50 consecutive failures

Authentication headers

auth_typeHeader sent
noneNo auth header
tokenX-MessageSystems-Webhook-Token: <your-token>
basicAuthorization: Basic <base64(user:pass)>

List Suppressions

GET /api/v1/suppression-list

Returns suppressed recipients for your account.

ParameterDescription
fromStart date (ISO 8601)
toEnd date (ISO 8601)
typesComma-separated: transactional, non_transactional
sourcesComma-separated: hard_bounce, manual, list_unsubscribe
limitMax results (default 100, max 1000)
Response — 200 OK
{
  "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

GET /api/v1/suppression-list/{email}

Check if a specific email address is on your suppression list. Returns 404 if not suppressed.

Response — 200 OK
{
  "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

PUT /api/v1/suppression-list

Bulk add or update suppressions. If a recipient already exists, it is updated.

Request Body
{
  "recipients": [
    {
      "recipient": "[email protected]",
      "type": "non_transactional",
      "description": "Opted out of marketing"
    },
    {
      "recipient": "[email protected]",
      "type": "transactional",
      "description": "Do not contact"
    }
  ]
}
FieldTypeRequiredDescription
recipientstringYesEmail address to suppress
typestringNotransactional or non_transactional (default)
descriptionstringNoReason for suppression (max 255)
Response — 200 OK
{
  "results": {
    "message": "2 suppression(s) updated."
  }
}

Remove Suppression

DELETE /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

POST /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.

Request Body
{
  "domain": "yourdomain.com",
  "tracking_domain": "track.yourdomain.com"
}
FieldTypeRequiredDescription
domainstringYesFully qualified domain name
tracking_domainstringNoCustom domain for open/click tracking
generate_dkimbooleanNoGenerate DKIM keys (default: true)
dkim_key_lengthintegerNo1024 or 2048 (default: 2048)
Response — 200 OK
{
  "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

GET /api/v1/sending-domains

Returns all sending domains for your account. Use query parameters to filter by verification status.

ParameterValuesDescription
ownership_verifiedtrue, falseFilter by ownership verification
dkim_statusvalid, invalid, unverifiedFilter by DKIM status
spf_statusvalid, invalid, unverifiedFilter by SPF status
cname_statusvalid, invalid, unverifiedFilter by bounce CNAME status
Response — 200 OK
{
  "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

GET /api/v1/sending-domains/{domain}

Returns details for a single sending domain, including current verification status and DKIM configuration.

Response — 200 OK
{
  "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

FieldValuesDescription
ownership_verifiedtrue / falseWhether the ownership TXT record was found
spf_statusvalid, invalid, unverifiedSPF record verification status
dkim_statusvalid, invalid, unverifiedDKIM record verification status
cname_statusvalid, invalid, unverifiedBounce CNAME verification status
compliance_statusvalidCompliance review status
verification_tokenstringToken for the ownership TXT record

Update Sending Domain

PUT /api/v1/sending-domains/{domain}

Update domain settings. Send only the fields you want to change.

Request Body (all fields optional)
{
  "tracking_domain": "track.yourdomain.com",
  "open_tracking": true,
  "click_tracking": true
}
FieldTypeDescription
tracking_domainstringCustom tracking domain (set to null to remove)
open_trackingbooleanEnable/disable open tracking pixel
click_trackingbooleanEnable/disable click tracking
Response — 200 OK
{
  "results": {
    "message": "Successfully Updated Domain.",
    "domain": "yourdomain.com"
  }
}

Verify Sending Domain

POST /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.

Request Body (optional)
{
  "dkim_verify": true,
  "spf_verify": true
}
FieldTypeDescription
dkim_verifybooleanRequest DKIM verification (default: all checks run)
spf_verifybooleanRequest SPF verification (default: all checks run)
Response — 200 OK
{
  "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

DELETE /api/v1/sending-domains/{domain}

Remove a sending domain from your account. Returns 204 No Content.

Example: cURL

Send an email
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

Send with Guzzle
$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

Send with fetch
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