Authentication

All API requests require authentication using an API key. Include your API key in the X-API-Key header:

X-API-Key: your-api-key-here

Obtaining an API Key

API keys can be generated through the admin dashboard or via the admin API endpoints (requires Sanctum authentication).

API Key Properties

Property Description
name A descriptive name for the key
permissions Array of allowed operations (or ["*"] for full access)
expires_at Optional expiration date
rate_limit Custom rate limit per minute (default: 60)

Example Request

curl -X GET "https://mail.example.com/api/v1/subscribers" \
  -H "X-API-Key: your-api-key-here" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json"

Rate Limiting

API requests are rate-limited to prevent abuse. The default limit is 60 requests per minute per API key.

Rate Limit Headers

Every response includes rate limit information:

Header Description
X-RateLimit-Limit Maximum requests per window
X-RateLimit-Remaining Remaining requests in current window
X-RateLimit-Reset Unix timestamp when the window resets

Rate Limit Exceeded

When the rate limit is exceeded, you'll receive a 429 Too Many Requests response:

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests. Please slow down and try again later.",
    "status": 429,
    "rate_limit": 60,
    "retry_after": 45
  }
}

The Retry-After header indicates how many seconds to wait before making another request.


Error Handling

All API responses follow a consistent format:

Success Response

{
  "success": true,
  "data": { ... },
  "message": "Operation completed successfully."
}

Error Response

{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error message.",
    "status": 400,
    "details": { ... }
  }
}

Error Codes

Code HTTP Status Description
API_KEY_MISSING 401 No API key provided in request
INVALID_API_KEY 401 API key is invalid or revoked
API_KEY_EXPIRED 403 API key has expired
UNAUTHORIZED 401 Authentication required
FORBIDDEN 403 Permission denied for this operation
NOT_FOUND 404 Resource not found
VALIDATION_ERROR 422 Request validation failed
CONFLICT 409 Resource conflict (e.g., duplicate)
RATE_LIMIT_EXCEEDED 429 Too many requests
SERVER_ERROR 500 Internal server error

Validation Error Details

When validation fails, the details field contains field-specific errors:

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The given data was invalid.",
    "status": 422,
    "details": {
      "email": ["The email field is required."],
      "name": ["The name must be at least 2 characters."]
    }
  }
}

Endpoints

Subscribers

Manage email subscribers in your mailing lists.

Create Subscriber

POST /api/v1/subscribers

Request Body:

{
  "email": "john@example.com",
  "first_name": "John",
  "last_name": "Doe",
  "lists": ["01HN...ULID1", "01HN...ULID2"],
  "tags": ["01HN...ULID3"],
  "custom_fields": {
    "company": "Acme Inc",
    "role": "Developer"
  }
}

Response (201 Created):

{
  "success": true,
  "data": {
    "id": "01HN...ULID",
    "email": "john@example.com",
    "first_name": "John",
    "last_name": "Doe",
    "status": "active",
    "confirmed_at": null,
    "lists": [...],
    "tags": [...],
    "custom_fields": {...},
    "created_at": "2024-01-15T10:30:00Z"
  }
}

Get Subscriber

GET /api/v1/subscribers/{email_or_ulid}

Query Parameters:

Parameter Type Description
include string Comma-separated relations: lists,tags

Response:

{
  "data": {
    "id": "01HN...ULID",
    "email": "john@example.com",
    "first_name": "John",
    "last_name": "Doe",
    "status": "active",
    "confirmed_at": "2024-01-15T11:00:00Z",
    "lists": [
      {"id": "01HN...ULID", "name": "Newsletter"}
    ],
    "tags": [
      {"id": "01HN...ULID", "name": "VIP"}
    ],
    "created_at": "2024-01-15T10:30:00Z"
  }
}

Update Subscriber

PUT /api/v1/subscribers/{email_or_ulid}

Request Body:

{
  "first_name": "Jane",
  "custom_fields": {
    "company": "New Company"
  }
}

Delete Subscriber

DELETE /api/v1/subscribers/{email_or_ulid}

Marks the subscriber as unsubscribed. Does not permanently delete the record.

Add Tags to Subscriber

POST /api/v1/subscribers/{email_or_ulid}/tags

Request Body:

{
  "tags": ["01HN...ULID1", "01HN...ULID2"]
}

Response:

{
  "success": true,
  "message": "Tags processed successfully.",
  "data": {
    "subscriber": "01HN...ULID",
    "attached": ["01HN...ULID1"],
    "skipped": ["01HN...ULID2"]
  }
}

Remove Tag from Subscriber

DELETE /api/v1/subscribers/{email_or_ulid}/tags/{tag_ulid}

Add Subscriber to Lists

POST /api/v1/subscribers/{email_or_ulid}/lists

Request Body:

{
  "lists": ["01HN...ULID1", "01HN...ULID2"]
}

Remove Subscriber from List

DELETE /api/v1/subscribers/{email_or_ulid}/lists/{list_ulid}

Lists

Manage mailing lists.

List All Mailing Lists

GET /api/v1/lists

Query Parameters:

Parameter Type Description
search string Search by name
double_optin boolean Filter by opt-in type
with_count boolean Include subscriber counts
per_page integer Items per page (max: 100)
sort string Sort field: name, created_at
direction string Sort direction: asc, desc

Create List

POST /api/v1/lists

Request Body:

{
  "name": "Weekly Newsletter",
  "description": "Our weekly updates and tips",
  "double_optin": true,
  "welcome_email_template_id": "01HN...ULID"
}

Get List

GET /api/v1/lists/{ulid}

Update List

PUT /api/v1/lists/{ulid}

Delete List

DELETE /api/v1/lists/{ulid}

Get List Subscribers

GET /api/v1/lists/{ulid}/subscribers

Query Parameters:

Parameter Type Description
status string Filter by status: active, unsubscribed, bounced
confirmed boolean Filter by confirmation status
email string Search by email
per_page integer Items per page (max: 100)

Tags

Manage subscriber tags for segmentation.

List All Tags

GET /api/v1/tags

Query Parameters:

Parameter Type Description
search string Search by name or slug
with_count boolean Include subscriber counts
per_page integer Items per page (max: 100)

Create Tag

POST /api/v1/tags

Request Body:

{
  "name": "VIP Customer",
  "color": "#FF5733"
}

Get Tag

GET /api/v1/tags/{ulid}

Update Tag

PUT /api/v1/tags/{ulid}

Delete Tag

DELETE /api/v1/tags/{ulid}

Campaigns

Manage and send email campaigns.

List Campaigns

GET /api/v1/campaigns

Query Parameters:

Parameter Type Description
status string Filter: draft, scheduled, sending, sent, cancelled
list string Filter by list ULID
search string Search by name or subject
from datetime Created after date
to datetime Created before date
per_page integer Items per page (max: 100)

Create Campaign

POST /api/v1/campaigns

Request Body:

{
  "name": "January Newsletter",
  "subject": "New Year, New Features!",
  "from_name": "Your Company",
  "from_email": "news@example.com",
  "reply_to": "support@example.com",
  "list_id": "01HN...ULID",
  "template_id": "01HN...ULID",
  "content": {
    "html": "<html>...</html>",
    "text": "Plain text version..."
  }
}

Get Campaign

GET /api/v1/campaigns/{ulid}

Get Campaign Statistics

GET /api/v1/campaigns/{ulid}/stats

Response:

{
  "data": {
    "campaign_id": "01HN...ULID",
    "sent": 10000,
    "delivered": 9850,
    "bounced": 150,
    "opened": 4500,
    "clicked": 1200,
    "unsubscribed": 25,
    "complained": 2,
    "open_rate": 45.69,
    "click_rate": 12.18,
    "bounce_rate": 1.5,
    "unsubscribe_rate": 0.25
  }
}

Trigger Campaign Send

POST /api/v1/campaigns/{ulid}/send

Immediately queues the campaign for sending. Only works for draft or scheduled campaigns.

Response:

{
  "success": true,
  "message": "Campaign has been queued for sending.",
  "data": {
    "id": "01HN...ULID",
    "status": "sending",
    "sent_at": "2024-01-15T14:30:00Z"
  }
}

Sequences

Manage automated email sequences (drip campaigns).

List Sequences

GET /api/v1/sequences

Query Parameters:

Parameter Type Description
active boolean Filter by active status
trigger_type string Filter by trigger type
list string Filter by list ULID
search string Search by name
with_count boolean Include subscriber/step counts
per_page integer Items per page (max: 100)

Get Sequence

GET /api/v1/sequences/{ulid}

Query Parameters:

Parameter Type Description
include string Include relations: steps,list,subscribers
with_count boolean Include counts

Enroll Subscriber in Sequence

POST /api/v1/sequences/{ulid}/subscribers

Request Body:

{
  "subscriber": "01HN...SUBSCRIBER_ULID"
}

or

{
  "email": "john@example.com"
}

Response:

{
  "success": true,
  "message": "Subscriber has been enrolled in the sequence.",
  "data": {
    "sequence": "01HN...ULID",
    "subscriber": "01HN...ULID"
  }
}

Remove Subscriber from Sequence

DELETE /api/v1/sequences/{ulid}/subscribers/{subscriber_email_or_ulid}

Events

Record custom events to trigger automations.

Record Custom Event

POST /api/v1/events

This endpoint allows you to track custom events that can trigger automations, sequences, or be used for segmentation.

Request Body:

{
  "subscriber_email": "john@example.com",
  "event_name": "purchase_completed",
  "properties": {
    "product_id": "SKU-12345",
    "product_name": "Premium Plan",
    "amount": 99.99,
    "currency": "USD"
  }
}

Event Name Format:

  • Must be in snake_case format
  • Only lowercase letters, numbers, and underscores allowed
  • Maximum 100 characters

Response (201 Created):

{
  "success": true,
  "message": "Event recorded successfully.",
  "data": {
    "id": "01HN...ULID",
    "event_name": "purchase_completed",
    "properties": {
      "product_id": "SKU-12345",
      "product_name": "Premium Plan",
      "amount": 99.99,
      "currency": "USD"
    },
    "source": "api",
    "occurred_at": "2024-01-15T14:30:00Z",
    "created_at": "2024-01-15T14:30:00Z"
  }
}

Common Event Examples:

Event Name Description
purchase_completed Customer completed a purchase
trial_started User started a free trial
trial_ended Trial period ended
subscription_upgraded Customer upgraded their plan
subscription_cancelled Customer cancelled subscription
webinar_registered User registered for webinar
webinar_attended User attended webinar
form_submitted User submitted a form
product_viewed User viewed a product
cart_abandoned User abandoned their cart

Webhook Events

The mailing system sends webhooks for various events. Configure webhook endpoints in the admin dashboard.

Webhook Payload Structure

{
  "event": "subscriber.created",
  "timestamp": "2024-01-15T14:30:00Z",
  "data": {
    // Event-specific data
  },
  "signature": "sha256=..."
}

Verifying Webhook Signatures

Webhooks are signed using HMAC-SHA256. Verify the signature by:

  1. Get the X-Webhook-Signature header
  2. Compute HMAC-SHA256 of the raw body using your webhook secret
  3. Compare with the signature header
$signature = hash_hmac('sha256', $rawBody, $webhookSecret);
$isValid = hash_equals($signature, $headerSignature);

Available Webhook Events

Event Description
subscriber.created New subscriber added
subscriber.updated Subscriber data updated
subscriber.unsubscribed Subscriber unsubscribed
subscriber.confirmed Subscriber confirmed (double opt-in)
campaign.sent Campaign finished sending
campaign.opened Email opened
campaign.clicked Link clicked
bounce.received Email bounced
complaint.received Spam complaint received
sequence.completed Subscriber completed sequence
event.recorded Custom event recorded

Webhook Retry Policy

Failed webhook deliveries are retried with exponential backoff:

  • Attempt 1: Immediate
  • Attempt 2: 5 minutes
  • Attempt 3: 30 minutes
  • Attempt 4: 2 hours
  • Attempt 5: 24 hours

After 5 failed attempts, the webhook is marked as failed and no further retries are made.


Pagination

List endpoints support pagination using the following query parameters:

Parameter Description Default
page Page number 1
per_page Items per page 15 (max: 100)

Pagination Response

{
  "data": [...],
  "links": {
    "first": "https://mail.example.com/api/v1/subscribers?page=1",
    "last": "https://mail.example.com/api/v1/subscribers?page=10",
    "prev": null,
    "next": "https://mail.example.com/api/v1/subscribers?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 10,
    "per_page": 15,
    "to": 15,
    "total": 150
  }
}

Filtering and Sorting

Most list endpoints support filtering and sorting:

Common Filter Parameters

Parameter Type Description
status string Filter by status
search string Search by name/email
created_from datetime Created after date
created_to datetime Created before date

Sorting

Parameter Type Description
sort string Field to sort by
direction string asc or desc

Example

GET /api/v1/subscribers?status=active&sort=created_at&direction=desc&per_page=25

SDKs and Libraries

Official SDKs are available for popular programming languages:

  • PHP: composer require expirator/mailing-php
  • JavaScript/Node.js: npm install @expirator/mailing-js
  • Python: pip install expirator-mailing

Support

For API support, contact us at: