openapi: 3.0.3
info:
title: 'ExoSend API Documentation'
description: 'Multi-tenant SMS & Email platform providing reliable message delivery through provider integrations.'
version: 1.0.0
servers:
-
url: 'https://send.exoclass.com'
tags:
-
name: 'SMS Operations'
description: "\nAPIs for sending SMS messages and checking delivery status."
-
name: 'Email Sending'
description: "\nAPIs for sending transactional and marketing emails, checking delivery status,\nand listing sent emails. Credits are deducted per email sent."
-
name: 'Email Domains'
description: "\nAPIs for managing email sending domains. Domains must have proper DNS records (SPF, DKIM, DMARC)\nconfigured and verified before they can be used for sending emails."
-
name: 'Email Templates'
description: "\nAPIs for managing reusable email templates with variable substitution.\nTemplates support MJML, HTML, and plain-text content with merge tags for personalization.\nSystem templates are shared across all organizations and cannot be edited by non-admins."
-
name: 'Email Campaigns'
description: "\nAPIs for creating, managing, and sending email campaigns. Campaigns follow a lifecycle:\ndraft -> sending/scheduled -> completed/cancelled. Only draft campaigns can be edited or deleted."
-
name: Contacts
description: "\nAPIs for managing email contacts. Contacts are individual email recipients that can be\norganized into lists for campaign targeting. Supports CRUD operations and CSV import."
-
name: 'Contact Lists'
description: "\nAPIs for managing contact lists. Lists are collections of contacts used for targeting\nemail campaigns. Contacts can belong to multiple lists simultaneously."
-
name: Analytics
description: "\nAPIs for retrieving email analytics and engagement metrics. Includes organization-level\noverviews, per-campaign statistics, link tracking, device breakdowns, and individual\nemail event timelines."
-
name: 'Suppression List'
description: "\nAPIs for managing your email suppression list. Suppressed email addresses will not receive\nany emails from your organization. Addresses can be suppressed manually, or automatically\nvia bounces, complaints, and unsubscribes."
-
name: 'Account Management'
description: "\nAPIs for managing your ExoSend account balance and credits."
-
name: 'Sender ID Management'
description: "\nAPIs for managing sender IDs (sender names that appear on SMS messages).\nSender IDs must be approved by administrators and verified by providers before use."
-
name: 'SMS Campaigns'
description: "\nAPIs for creating, managing, and sending bulk SMS campaigns.\nCampaigns follow a lifecycle: draft -> sending/scheduled -> sent/completed/cancelled."
components:
securitySchemes:
default:
type: http
scheme: bearer
description: 'You can retrieve your API token by logging into the ExoSend admin panel at /admin and navigating to your profile settings. Click Create New Token to generate an API token.'
security:
-
default: []
paths:
/api/v1/sms/send:
post:
summary: 'Send Single SMS'
operationId: sendSingleSMS
description: "Queue a single SMS message for delivery. The message will be sent asynchronously\nand credits will be deducted immediately."
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
success: true
data:
message_id: 9a7f2e5c-1234-5678-90ab-cdef12345678
status: pending
cost: 0.035
segments: 1
balance_remaining: 99.965
message: 'SMS queued for sending'
properties:
success:
type: boolean
example: true
data:
type: object
properties:
message_id:
type: string
example: 9a7f2e5c-1234-5678-90ab-cdef12345678
status:
type: string
example: pending
cost:
type: number
example: 0.035
segments:
type: integer
example: 1
balance_remaining:
type: number
example: 99.965
message:
type: string
example: 'SMS queued for sending'
404:
description: 'Sender ID Not Found'
content:
application/json:
schema:
type: object
example:
success: false
error:
code: SENDER_NOT_FOUND
message: 'The specified sender ID was not found in your organization'
details:
sender_id: NONEXISTENT
properties:
success:
type: boolean
example: false
error:
type: object
properties:
code:
type: string
example: SENDER_NOT_FOUND
message:
type: string
example: 'The specified sender ID was not found in your organization'
details:
type: object
properties:
sender_id:
type: string
example: NONEXISTENT
422:
description: ''
content:
application/json:
schema:
oneOf:
-
description: 'Insufficient Credits'
type: object
example:
success: false
error:
code: INSUFFICIENT_CREDITS
message: 'Your account does not have enough credits'
details:
required: 0.035
available: 0.0
properties:
success:
type: boolean
example: false
error:
type: object
properties:
code:
type: string
example: INSUFFICIENT_CREDITS
message:
type: string
example: 'Your account does not have enough credits'
details:
type: object
properties:
required:
type: number
example: 0.035
available:
type: number
example: 0.0
-
description: 'Invalid Phone Number'
type: object
example:
success: false
error:
code: INVALID_PHONE_NUMBER
message: 'The recipient phone number is invalid'
details:
recipient: '1234567890'
properties:
success:
type: boolean
example: false
error:
type: object
properties:
code:
type: string
example: INVALID_PHONE_NUMBER
message:
type: string
example: 'The recipient phone number is invalid'
details:
type: object
properties:
recipient:
type: string
example: '1234567890'
-
description: 'Sender ID Not Verified'
type: object
example:
success: false
error:
code: SENDER_NOT_VERIFIED
message: 'The sender ID must be verified before sending messages'
details:
sender_id: EXOCLASS
status: pending
status_label: 'Pending Review'
properties:
success:
type: boolean
example: false
error:
type: object
properties:
code:
type: string
example: SENDER_NOT_VERIFIED
message:
type: string
example: 'The sender ID must be verified before sending messages'
details:
type: object
properties:
sender_id:
type: string
example: EXOCLASS
status:
type: string
example: pending
status_label:
type: string
example: 'Pending Review'
tags:
- 'SMS Operations'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
recipient:
type: string
description: 'Phone number in E.164 format (e.g., +37061234567).'
example: '+37061234567'
message:
type: string
description: 'Message content. Maximum 1600 characters for concatenated messages.'
example: 'Hello from ExoSend! Your verification code is 123456.'
sender_id:
type: string
description: 'UUID or sender ID string of a verified sender ID from your organization. Can be either a UUID (e.g., "9a7f2e5c-1234-5678-90ab-cdef12345678") or the sender ID name (e.g., "EXOCLASS").'
example: EXOCLASS
required:
- recipient
- message
- sender_id
/api/v1/sms/send-bulk:
post:
summary: 'Send Bulk SMS'
operationId: sendBulkSMS
description: "Queue multiple SMS messages to different recipients with the same content.\nInvalid phone numbers will be filtered out, and only valid recipients will receive messages.\nCredits are deducted for all valid recipients before sending."
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
success: true
data:
total_messages: 3
total_cost: 0.105
balance_remaining: 99.895
message_ids:
- 9a7f2e5c-1234-5678-90ab-cdef12345678
- 9a7f2e5c-1234-5678-90ab-cdef12345679
- 9a7f2e5c-1234-5678-90ab-cdef12345680
invalid_recipients: []
message: 'Bulk SMS queued for sending'
properties:
success:
type: boolean
example: true
data:
type: object
properties:
total_messages:
type: integer
example: 3
total_cost:
type: number
example: 0.105
balance_remaining:
type: number
example: 99.895
message_ids:
type: array
example:
- 9a7f2e5c-1234-5678-90ab-cdef12345678
- 9a7f2e5c-1234-5678-90ab-cdef12345679
- 9a7f2e5c-1234-5678-90ab-cdef12345680
items:
type: string
invalid_recipients:
type: array
example: []
message:
type: string
example: 'Bulk SMS queued for sending'
422:
description: ''
content:
application/json:
schema:
oneOf:
-
description: 'Some Invalid Recipients'
type: object
example:
success: true
data:
total_messages: 2
total_cost: 0.07
balance_remaining: 99.93
message_ids:
- 9a7f2e5c-1234-5678-90ab-cdef12345678
- 9a7f2e5c-1234-5678-90ab-cdef12345679
invalid_recipients:
- invalid-number
message: 'Bulk SMS queued for sending'
properties:
success:
type: boolean
example: true
data:
type: object
properties:
total_messages:
type: integer
example: 2
total_cost:
type: number
example: 0.07
balance_remaining:
type: number
example: 99.93
message_ids:
type: array
example:
- 9a7f2e5c-1234-5678-90ab-cdef12345678
- 9a7f2e5c-1234-5678-90ab-cdef12345679
items:
type: string
invalid_recipients:
type: array
example:
- invalid-number
items:
type: string
message:
type: string
example: 'Bulk SMS queued for sending'
-
description: 'All Recipients Invalid'
type: object
example:
success: false
error:
code: NO_VALID_RECIPIENTS
message: 'No valid recipients found'
details:
invalid_recipients:
- invalid1
- invalid2
properties:
success:
type: boolean
example: false
error:
type: object
properties:
code:
type: string
example: NO_VALID_RECIPIENTS
message:
type: string
example: 'No valid recipients found'
details:
type: object
properties:
invalid_recipients:
type: array
example:
- invalid1
- invalid2
items:
type: string
-
description: 'Insufficient Credits'
type: object
example:
success: false
error:
code: INSUFFICIENT_CREDITS
message: 'Your account does not have enough credits'
details:
required: 0.105
available: 0.05
valid_recipients_count: 3
properties:
success:
type: boolean
example: false
error:
type: object
properties:
code:
type: string
example: INSUFFICIENT_CREDITS
message:
type: string
example: 'Your account does not have enough credits'
details:
type: object
properties:
required:
type: number
example: 0.105
available:
type: number
example: 0.05
valid_recipients_count:
type: integer
example: 3
tags:
- 'SMS Operations'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
recipients:
type: array
description: 'Array of phone numbers in E.164 format.'
example:
- '+37061234567'
- '+37061234568'
- '+37061234569'
items:
type: string
message:
type: string
description: 'Message content. Same message sent to all recipients.'
example: 'Bulk notification: System maintenance scheduled for tonight.'
sender_id:
type: string
description: 'UUID or sender ID string of a verified sender ID from your organization. Can be either a UUID or the sender ID name.'
example: EXOCLASS
required:
- recipients
- message
- sender_id
'/api/v1/sms/{id}/status':
get:
summary: 'Get Message Status'
operationId: getMessageStatus
description: "Retrieve the current delivery status of a previously sent SMS message.\nOnly messages belonging to your organization can be accessed."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
oneOf:
-
description: 'Delivered Message'
type: object
example:
success: true
data:
id: 9a7f2e5c-1234-5678-90ab-cdef12345678
status: delivered
recipient: '+37061234567'
cost: 0.035
segments: 1
sent_at: '2026-01-03T14:30:00+00:00'
delivered_at: '2026-01-03T14:30:15+00:00'
error_message: null
error_code: null
properties:
success:
type: boolean
example: true
data:
type: object
properties:
id:
type: string
example: 9a7f2e5c-1234-5678-90ab-cdef12345678
status:
type: string
example: delivered
recipient:
type: string
example: '+37061234567'
cost:
type: number
example: 0.035
segments:
type: integer
example: 1
sent_at:
type: string
example: '2026-01-03T14:30:00+00:00'
delivered_at:
type: string
example: '2026-01-03T14:30:15+00:00'
error_message:
type: string
example: null
error_code:
type: string
example: null
-
description: 'Failed Message'
type: object
example:
success: true
data:
id: 9a7f2e5c-1234-5678-90ab-cdef12345678
status: failed
recipient: '+37061234567'
cost: 0.035
segments: 1
sent_at: null
delivered_at: null
error_message: 'Invalid recipient'
error_code: INVALID_RECIPIENT
properties:
success:
type: boolean
example: true
data:
type: object
properties:
id:
type: string
example: 9a7f2e5c-1234-5678-90ab-cdef12345678
status:
type: string
example: failed
recipient:
type: string
example: '+37061234567'
cost:
type: number
example: 0.035
segments:
type: integer
example: 1
sent_at:
type: string
example: null
delivered_at:
type: string
example: null
error_message:
type: string
example: 'Invalid recipient'
error_code:
type: string
example: INVALID_RECIPIENT
404:
description: 'Message Not Found'
content:
application/json:
schema:
type: object
example:
success: false
error:
code: NOT_FOUND
message: 'Message not found'
properties:
success:
type: boolean
example: false
error:
type: object
properties:
code:
type: string
example: NOT_FOUND
message:
type: string
example: 'Message not found'
tags:
- 'SMS Operations'
parameters:
-
in: path
name: id
description: 'The UUID of the message.'
example: 9a7f2e5c-1234-5678-90ab-cdef12345678
required: true
schema:
type: string
/api/v1/emails/send:
post:
summary: 'Send Single Email'
operationId: sendSingleEmail
description: "Queue a single email for delivery. The email will be sent asynchronously via the\nconfigured email provider. Credits are deducted immediately. If html_content is provided\nwithout text_content, a plain-text version is auto-generated."
parameters: []
responses:
202:
description: ''
content:
application/json:
schema:
type: object
example:
success: true
data:
id: 9a7f2e5c-1234-5678-90ab-cdef12345678
status: pending
to_email: john@example.com
subject: 'Welcome to ExoSend!'
cost: 0.01
created_at: '2026-01-15T10:30:00+00:00'
message: 'Email queued for sending.'
properties:
success:
type: boolean
example: true
data:
type: object
properties:
id:
type: string
example: 9a7f2e5c-1234-5678-90ab-cdef12345678
status:
type: string
example: pending
to_email:
type: string
example: john@example.com
subject:
type: string
example: 'Welcome to ExoSend!'
cost:
type: number
example: 0.01
created_at:
type: string
example: '2026-01-15T10:30:00+00:00'
message:
type: string
example: 'Email queued for sending.'
422:
description: ''
content:
application/json:
schema:
oneOf:
-
description: 'Insufficient Credits'
type: object
example:
success: false
error:
code: INSUFFICIENT_CREDITS
message: 'Your account does not have enough credits'
details:
required: 0.01
available: 0.0
properties:
success:
type: boolean
example: false
error:
type: object
properties:
code:
type: string
example: INSUFFICIENT_CREDITS
message:
type: string
example: 'Your account does not have enough credits'
details:
type: object
properties:
required:
type: number
example: 0.01
available:
type: number
example: 0.0
-
description: 'Domain Not Verified'
type: object
example:
success: false
error:
code: DOMAIN_NOT_VERIFIED
message: 'The sending domain is registered but not yet verified'
details:
domain: exosend.com
status: pending
properties:
success:
type: boolean
example: false
error:
type: object
properties:
code:
type: string
example: DOMAIN_NOT_VERIFIED
message:
type: string
example: 'The sending domain is registered but not yet verified'
details:
type: object
properties:
domain:
type: string
example: exosend.com
status:
type: string
example: pending
-
description: 'Validation Error'
type: object
example:
success: false
error:
code: VALIDATION_ERROR
message: 'The given data was invalid'
details:
to_email:
- 'Recipient email must be a valid email address'
subject:
- 'Email subject is required'
properties:
success:
type: boolean
example: false
error:
type: object
properties:
code:
type: string
example: VALIDATION_ERROR
message:
type: string
example: 'The given data was invalid'
details:
type: object
properties:
to_email:
type: array
example:
- 'Recipient email must be a valid email address'
items:
type: string
subject:
type: array
example:
- 'Email subject is required'
items:
type: string
tags:
- 'Email Sending'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
from_email:
type: string
description: 'Sender email address. Must match a verified domain if one is registered.'
example: hello@exosend.com
from_name:
type: string
description: 'optional Sender display name.'
example: 'ExoSend Team'
nullable: true
to_email:
type: string
description: 'Recipient email address.'
example: john@example.com
to_name:
type: string
description: 'optional Recipient display name.'
example: 'John Doe'
nullable: true
subject:
type: string
description: 'Email subject line. Maximum 255 characters.'
example: 'Welcome to ExoSend!'
html_content:
type: string
description: '(if no text_content) HTML body of the email.'
example: '
Thanks for signing up.
' nullable: true text_content: type: string description: '(if no html_content) Plain-text body of the email.' example: 'Welcome! Thanks for signing up.' nullable: true reply_to: type: string description: 'optional Reply-to email address.' example: support@exosend.com nullable: true template_id: type: string description: 'optional UUID of an email template to use.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 nullable: true variables: type: object description: 'optional Template variables for merge tags.' example: first_name: John company: Acme properties: { } nullable: true metadata: type: object description: 'optional Custom key-value metadata to attach to the email.' example: order_id: '12345' campaign: onboarding properties: { } nullable: true email_domain_id: type: string description: 'optional UUID of a specific email domain to use.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 nullable: true attachments: type: array description: 'Must be a file. Must not be greater than 102400 kilobytes.' items: type: string format: binary required: - from_email - to_email - subject - html_content - text_content /api/v1/emails/send-batch: post: summary: 'Send Batch Emails' operationId: sendBatchEmails description: "Queue multiple emails for delivery in a single request. Each email can have different\nrecipients, subjects, and content. All emails are validated and credits are deducted\nfor the entire batch before sending begins. Maximum 1000 emails per batch." parameters: [] responses: 202: description: '' content: application/json: schema: type: object example: success: true data: total_emails: 3 total_cost: 0.03 balance_remaining: 99.97 email_ids: - 9a7f2e5c-1234-5678-90ab-cdef12345678 - 9a7f2e5c-1234-5678-90ab-cdef12345679 - 9a7f2e5c-1234-5678-90ab-cdef12345680 message: 'Batch emails queued for sending.' properties: success: type: boolean example: true data: type: object properties: total_emails: type: integer example: 3 total_cost: type: number example: 0.03 balance_remaining: type: number example: 99.97 email_ids: type: array example: - 9a7f2e5c-1234-5678-90ab-cdef12345678 - 9a7f2e5c-1234-5678-90ab-cdef12345679 - 9a7f2e5c-1234-5678-90ab-cdef12345680 items: type: string message: type: string example: 'Batch emails queued for sending.' 422: description: '' content: application/json: schema: oneOf: - description: 'Insufficient Credits' type: object example: success: false error: code: INSUFFICIENT_CREDITS message: 'Your account does not have enough credits' details: required: 0.03 available: 0.01 email_count: 3 properties: success: type: boolean example: false error: type: object properties: code: type: string example: INSUFFICIENT_CREDITS message: type: string example: 'Your account does not have enough credits' details: type: object properties: required: type: number example: 0.03 available: type: number example: 0.01 email_count: type: integer example: 3 - description: 'Domain Not Verified' type: object example: success: false error: code: DOMAIN_NOT_VERIFIED message: "The sending domain 'exosend.com' is registered but not yet verified" details: domain: exosend.com status: pending properties: success: type: boolean example: false error: type: object properties: code: type: string example: DOMAIN_NOT_VERIFIED message: type: string example: "The sending domain 'exosend.com' is registered but not yet verified" details: type: object properties: domain: type: string example: exosend.com status: type: string example: pending - description: 'Validation Error' type: object example: success: false error: code: VALIDATION_ERROR message: 'The given data was invalid' details: emails: - 'At least one email is required' properties: success: type: boolean example: false error: type: object properties: code: type: string example: VALIDATION_ERROR message: type: string example: 'The given data was invalid' details: type: object properties: emails: type: array example: - 'At least one email is required' items: type: string tags: - 'Email Sending' requestBody: required: true content: multipart/form-data: schema: type: object properties: emails: type: array description: 'Array of email objects to send (1-1000 items).' example: - architecto items: type: string attachments: type: array description: 'Must be a file. Must not be greater than 102400 kilobytes.' items: type: string format: binary required: - emails '/api/v1/emails/batch/{batchId}': get: summary: 'Get Batch Details' operationId: getBatchDetails description: "Retrieve the summary and paginated email list for a previously created batch.\nOnly batches belonging to your organization can be accessed." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: batch: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 subject: 'Welcome to ExoSend!' from_email: hello@exosend.com from_name: 'ExoSend Team' source: api status: pending total_recipients: 3 total_cost: 0.03 total_sent: 0 total_delivered: 0 total_opened: 0 total_bounced: 0 total_failed: 0 delivery_rate: 0.0 open_rate: 0.0 scheduled_at: null created_at: '2026-01-15T10:30:00+00:00' emails: { } properties: success: type: boolean example: true data: type: object properties: batch: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 subject: type: string example: 'Welcome to ExoSend!' from_email: type: string example: hello@exosend.com from_name: type: string example: 'ExoSend Team' source: type: string example: api status: type: string example: pending total_recipients: type: integer example: 3 total_cost: type: number example: 0.03 total_sent: type: integer example: 0 total_delivered: type: integer example: 0 total_opened: type: integer example: 0 total_bounced: type: integer example: 0 total_failed: type: integer example: 0 delivery_rate: type: number example: 0.0 open_rate: type: number example: 0.0 scheduled_at: type: string example: null created_at: type: string example: '2026-01-15T10:30:00+00:00' emails: type: object properties: { } 404: description: 'Batch Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Batch not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Batch not found' tags: - 'Email Sending' parameters: - in: path name: batchId description: 'The UUID of the email batch.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/emails/{id}/status': get: summary: 'Get Email Status' operationId: getEmailStatus description: "Retrieve the current delivery status and tracking events for a previously sent email.\nOnly emails belonging to your organization can be accessed." parameters: [] responses: 200: description: '' content: application/json: schema: oneOf: - description: 'Delivered Email' type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 status: delivered to_email: john@example.com subject: 'Welcome to ExoSend!' cost: 0.01 sent_at: '2026-01-15T10:30:00+00:00' delivered_at: '2026-01-15T10:30:05+00:00' opened_at: '2026-01-15T11:00:00+00:00' clicked_at: '2026-01-15T11:05:30+00:00' bounced_at: null error_message: null error_code: null properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 status: type: string example: delivered to_email: type: string example: john@example.com subject: type: string example: 'Welcome to ExoSend!' cost: type: number example: 0.01 sent_at: type: string example: '2026-01-15T10:30:00+00:00' delivered_at: type: string example: '2026-01-15T10:30:05+00:00' opened_at: type: string example: '2026-01-15T11:00:00+00:00' clicked_at: type: string example: '2026-01-15T11:05:30+00:00' bounced_at: type: string example: null error_message: type: string example: null error_code: type: string example: null - description: 'Bounced Email' type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 status: bounced to_email: invalid@example.com subject: 'Welcome to ExoSend!' cost: 0.01 sent_at: '2026-01-15T10:30:00+00:00' delivered_at: null opened_at: null clicked_at: null bounced_at: '2026-01-15T10:30:10+00:00' error_message: 'Mailbox not found' error_code: '550' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 status: type: string example: bounced to_email: type: string example: invalid@example.com subject: type: string example: 'Welcome to ExoSend!' cost: type: number example: 0.01 sent_at: type: string example: '2026-01-15T10:30:00+00:00' delivered_at: type: string example: null opened_at: type: string example: null clicked_at: type: string example: null bounced_at: type: string example: '2026-01-15T10:30:10+00:00' error_message: type: string example: 'Mailbox not found' error_code: type: string example: '550' 404: description: 'Email Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Email not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Email not found' tags: - 'Email Sending' parameters: - in: path name: id description: 'The UUID of the email.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string /api/v1/emails: get: summary: 'List Sent Emails' operationId: listSentEmails description: "Retrieve a paginated list of all sent emails for your organization, sorted by newest first.\nOptionally filter by delivery status." parameters: - in: query name: status description: 'Filter by email status. Options: pending, sent, delivered, opened, clicked, bounced, failed, complained.' example: delivered required: false schema: type: string description: 'Filter by email status. Options: pending, sent, delivered, opened, clicked, bounced, failed, complained.' example: delivered - in: query name: per_page description: 'Number of results per page (default 25).' example: 10 required: false schema: type: integer description: 'Number of results per page (default 25).' example: 10 responses: 200: description: '' content: application/json: schema: type: object example: success: true data: emails: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 from_email: hello@exosend.com to_email: john@example.com subject: 'Welcome to ExoSend!' status: delivered cost: 0.01 created_at: '2026-01-15T10:30:00+00:00' pagination: total: 42 per_page: 25 current_page: 1 last_page: 2 properties: success: type: boolean example: true data: type: object properties: emails: type: array example: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 from_email: hello@exosend.com to_email: john@example.com subject: 'Welcome to ExoSend!' status: delivered cost: 0.01 created_at: '2026-01-15T10:30:00+00:00' items: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 from_email: type: string example: hello@exosend.com to_email: type: string example: john@example.com subject: type: string example: 'Welcome to ExoSend!' status: type: string example: delivered cost: type: number example: 0.01 created_at: type: string example: '2026-01-15T10:30:00+00:00' pagination: type: object properties: total: type: integer example: 42 per_page: type: integer example: 25 current_page: type: integer example: 1 last_page: type: integer example: 2 tags: - 'Email Sending' /api/v1/email-domains: get: summary: 'List Domains' operationId: listDomains description: "Retrieve all email domains registered for your organization, including their\nverification status and DNS records." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: exosend.com from_email_default: hello@exosend.com from_name_default: ExoSend status: verified spf_verified: true dkim_verified: true dmarc_verified: true is_default: true dns_records: - type: TXT name: exosend.com value: 'v=spf1 include:mailgun.org ~all' verified_at: '2026-01-10T12:00:00+00:00' created_at: '2026-01-10T10:00:00+00:00' properties: success: type: boolean example: true data: type: array example: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: exosend.com from_email_default: hello@exosend.com from_name_default: ExoSend status: verified spf_verified: true dkim_verified: true dmarc_verified: true is_default: true dns_records: - type: TXT name: exosend.com value: 'v=spf1 include:mailgun.org ~all' verified_at: '2026-01-10T12:00:00+00:00' created_at: '2026-01-10T10:00:00+00:00' items: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: type: string example: exosend.com from_email_default: type: string example: hello@exosend.com from_name_default: type: string example: ExoSend status: type: string example: verified spf_verified: type: boolean example: true dkim_verified: type: boolean example: true dmarc_verified: type: boolean example: true is_default: type: boolean example: true dns_records: type: array example: - type: TXT name: exosend.com value: 'v=spf1 include:mailgun.org ~all' items: type: object properties: type: type: string example: TXT name: type: string example: exosend.com value: type: string example: 'v=spf1 include:mailgun.org ~all' verified_at: type: string example: '2026-01-10T12:00:00+00:00' created_at: type: string example: '2026-01-10T10:00:00+00:00' tags: - 'Email Domains' post: summary: 'Add Domain' operationId: addDomain description: "Register a new email sending domain for your organization. After adding, you must\nconfigure the returned DNS records with your domain registrar, then trigger verification.\nDomain registration with the email provider is done asynchronously." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: exosend.com from_email_default: hello@exosend.com from_name_default: 'ExoSend Team' status: pending spf_verified: false dkim_verified: false dmarc_verified: false is_default: false dns_records: - type: TXT name: exosend.com value: 'v=spf1 include:mailgun.org ~all' - type: TXT name: k1._domainkey.exosend.com value: 'k=rsa; p=MIGf...' - type: TXT name: _dmarc.exosend.com value: 'v=DMARC1; p=none;' verified_at: null created_at: '2026-01-15T10:00:00+00:00' message: 'Domain added. Please add the DNS records shown below, then verify.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: type: string example: exosend.com from_email_default: type: string example: hello@exosend.com from_name_default: type: string example: 'ExoSend Team' status: type: string example: pending spf_verified: type: boolean example: false dkim_verified: type: boolean example: false dmarc_verified: type: boolean example: false is_default: type: boolean example: false dns_records: type: array example: - type: TXT name: exosend.com value: 'v=spf1 include:mailgun.org ~all' - type: TXT name: k1._domainkey.exosend.com value: 'k=rsa; p=MIGf...' - type: TXT name: _dmarc.exosend.com value: 'v=DMARC1; p=none;' items: type: object properties: type: type: string example: TXT name: type: string example: exosend.com value: type: string example: 'v=spf1 include:mailgun.org ~all' verified_at: type: string example: null created_at: type: string example: '2026-01-15T10:00:00+00:00' message: type: string example: 'Domain added. Please add the DNS records shown below, then verify.' 422: description: '' content: application/json: schema: oneOf: - description: 'Domain Already Exists' type: object example: success: false error: code: DOMAIN_EXISTS message: 'This domain is already registered for your organization' details: domain: exosend.com properties: success: type: boolean example: false error: type: object properties: code: type: string example: DOMAIN_EXISTS message: type: string example: 'This domain is already registered for your organization' details: type: object properties: domain: type: string example: exosend.com - description: 'Validation Error' type: object example: success: false error: code: VALIDATION_ERROR message: 'The given data was invalid' details: domain: - 'The domain field is required.' properties: success: type: boolean example: false error: type: object properties: code: type: string example: VALIDATION_ERROR message: type: string example: 'The given data was invalid' details: type: object properties: domain: type: array example: - 'The domain field is required.' items: type: string tags: - 'Email Domains' requestBody: required: true content: application/json: schema: type: object properties: domain: type: string description: 'The domain name to register (e.g., "exosend.com").' example: exosend.com from_email_default: type: string description: 'optional Default sender email for this domain.' example: hello@exosend.com nullable: true from_name_default: type: string description: 'optional Default sender display name for this domain.' example: 'ExoSend Team' nullable: true region: type: string description: 'optional Mailgun region: "us" or "eu". When provided, the domain is assigned to the matching Mailgun provider.' example: eu nullable: true required: - domain '/api/v1/email-domains/{id}': get: summary: 'Get Domain Details' operationId: getDomainDetails description: "Retrieve full details of a specific email domain, including DNS records\nand verification status for SPF, DKIM, and DMARC." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: exosend.com from_email_default: hello@exosend.com from_name_default: 'ExoSend Team' status: verified spf_verified: true dkim_verified: true dmarc_verified: true is_default: true dns_records: - type: TXT name: exosend.com value: 'v=spf1 include:mailgun.org ~all' verified_at: '2026-01-10T12:00:00+00:00' created_at: '2026-01-10T10:00:00+00:00' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: type: string example: exosend.com from_email_default: type: string example: hello@exosend.com from_name_default: type: string example: 'ExoSend Team' status: type: string example: verified spf_verified: type: boolean example: true dkim_verified: type: boolean example: true dmarc_verified: type: boolean example: true is_default: type: boolean example: true dns_records: type: array example: - type: TXT name: exosend.com value: 'v=spf1 include:mailgun.org ~all' items: type: object properties: type: type: string example: TXT name: type: string example: exosend.com value: type: string example: 'v=spf1 include:mailgun.org ~all' verified_at: type: string example: '2026-01-10T12:00:00+00:00' created_at: type: string example: '2026-01-10T10:00:00+00:00' 404: description: 'Domain Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Domain not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Domain not found' tags: - 'Email Domains' delete: summary: 'Delete Domain' operationId: deleteDomain description: "Remove an email domain from your organization. This does not affect emails\nthat have already been sent through this domain." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: null message: 'Domain removed.' properties: success: type: boolean example: true data: type: string example: null message: type: string example: 'Domain removed.' 404: description: 'Domain Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Domain not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Domain not found' tags: - 'Email Domains' parameters: - in: path name: id description: 'The UUID of the domain.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/email-domains/{id}/verify': post: summary: 'Verify Domain DNS' operationId: verifyDomainDNS description: "Trigger DNS verification for a domain. This queues a background job that checks\nwhether the required SPF, DKIM, and DMARC records are properly configured.\nThe domain status will be updated asynchronously after verification completes." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: exosend.com from_email_default: hello@exosend.com from_name_default: 'ExoSend Team' status: pending spf_verified: false dkim_verified: false dmarc_verified: false is_default: false dns_records: [] verified_at: null created_at: '2026-01-15T10:00:00+00:00' message: 'DNS verification has been queued.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: type: string example: exosend.com from_email_default: type: string example: hello@exosend.com from_name_default: type: string example: 'ExoSend Team' status: type: string example: pending spf_verified: type: boolean example: false dkim_verified: type: boolean example: false dmarc_verified: type: boolean example: false is_default: type: boolean example: false dns_records: type: array example: [] verified_at: type: string example: null created_at: type: string example: '2026-01-15T10:00:00+00:00' message: type: string example: 'DNS verification has been queued.' 404: description: 'Domain Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Domain not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Domain not found' tags: - 'Email Domains' parameters: - in: path name: id description: 'The UUID of the domain.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/email-domains/{id}/default': patch: summary: 'Set Default Domain' operationId: setDefaultDomain description: "Set a verified domain as the default sending domain for your organization.\nOnly verified domains can be set as default. The previous default domain\nwill be automatically unset." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: exosend.com from_email_default: hello@exosend.com from_name_default: 'ExoSend Team' status: verified spf_verified: true dkim_verified: true dmarc_verified: true is_default: true dns_records: [] verified_at: '2026-01-10T12:00:00+00:00' created_at: '2026-01-10T10:00:00+00:00' message: 'Domain set as default.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: type: string example: exosend.com from_email_default: type: string example: hello@exosend.com from_name_default: type: string example: 'ExoSend Team' status: type: string example: verified spf_verified: type: boolean example: true dkim_verified: type: boolean example: true dmarc_verified: type: boolean example: true is_default: type: boolean example: true dns_records: type: array example: [] verified_at: type: string example: '2026-01-10T12:00:00+00:00' created_at: type: string example: '2026-01-10T10:00:00+00:00' message: type: string example: 'Domain set as default.' 404: description: 'Domain Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Domain not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Domain not found' 422: description: 'Domain Not Verified' content: application/json: schema: type: object example: success: false error: code: DOMAIN_NOT_VERIFIED message: 'Only verified domains can be set as default' properties: success: type: boolean example: false error: type: object properties: code: type: string example: DOMAIN_NOT_VERIFIED message: type: string example: 'Only verified domains can be set as default' tags: - 'Email Domains' parameters: - in: path name: id description: 'The UUID of the domain.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string /api/v1/email-templates: get: summary: 'List Templates' operationId: listTemplates description: "Retrieve all active email templates for your organization, including system templates.\nOptionally filter by category. Returns summary data; use the show endpoint for full content." parameters: - in: query name: category description: 'Filter by template category. Options: transactional, marketing, notification, onboarding, custom.' example: marketing required: false schema: type: string description: 'Filter by template category. Options: transactional, marketing, notification, onboarding, custom.' example: marketing responses: 200: description: '' content: application/json: schema: type: object example: success: true data: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'Welcome Email' slug: welcome-email category: onboarding subject_template: 'Welcome, @{{first_name}}!' is_system: false is_active: true version: 1 times_used: 150 created_at: '2026-01-10T10:00:00+00:00' updated_at: '2026-01-12T14:30:00+00:00' properties: success: type: boolean example: true data: type: array example: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'Welcome Email' slug: welcome-email category: onboarding subject_template: 'Welcome, @{{first_name}}!' is_system: false is_active: true version: 1 times_used: 150 created_at: '2026-01-10T10:00:00+00:00' updated_at: '2026-01-12T14:30:00+00:00' items: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'Welcome Email' slug: type: string example: welcome-email category: type: string example: onboarding subject_template: type: string example: 'Welcome, @{{first_name}}!' is_system: type: boolean example: false is_active: type: boolean example: true version: type: integer example: 1 times_used: type: integer example: 150 created_at: type: string example: '2026-01-10T10:00:00+00:00' updated_at: type: string example: '2026-01-12T14:30:00+00:00' tags: - 'Email Templates' post: summary: 'Create Template' operationId: createTemplate description: "Create a new email template for your organization. Templates can include\nmerge tag variables (e.g., `@{{first_name}}`) that are replaced at send time.\nIf only html_content is provided, a plain-text version is auto-generated." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'Monthly Newsletter' slug: monthly-newsletter category: marketing subject_template: 'Your @{{month}} Newsletter' is_system: false is_active: true version: 1 times_used: 0 created_at: '2026-01-15T10:00:00+00:00' updated_at: '2026-01-15T10:00:00+00:00' description: 'Sent monthly with latest updates' html_content: 'Here is your newsletter.
' text_content: 'Hello @{{first_name}}, here is your newsletter.' mjml_content: null variables: - key: first_name label: 'First Name' default: there required: true message: 'Template created.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'Monthly Newsletter' slug: type: string example: monthly-newsletter category: type: string example: marketing subject_template: type: string example: 'Your @{{month}} Newsletter' is_system: type: boolean example: false is_active: type: boolean example: true version: type: integer example: 1 times_used: type: integer example: 0 created_at: type: string example: '2026-01-15T10:00:00+00:00' updated_at: type: string example: '2026-01-15T10:00:00+00:00' description: type: string example: 'Sent monthly with latest updates' html_content: type: string example: 'Here is your newsletter.
' text_content: type: string example: 'Hello @{{first_name}}, here is your newsletter.' mjml_content: type: string example: null variables: type: array example: - key: first_name label: 'First Name' default: there required: true items: type: object properties: key: type: string example: first_name label: type: string example: 'First Name' default: type: string example: there required: type: boolean example: true message: type: string example: 'Template created.' 422: description: '' content: application/json: schema: oneOf: - description: 'Slug Already Exists' type: object example: success: false error: code: SLUG_EXISTS message: 'A template with this slug already exists' properties: success: type: boolean example: false error: type: object properties: code: type: string example: SLUG_EXISTS message: type: string example: 'A template with this slug already exists' - description: 'Validation Error' type: object example: success: false error: code: VALIDATION_ERROR message: 'The given data was invalid' details: name: - 'The name field is required.' category: - 'The selected category is invalid.' properties: success: type: boolean example: false error: type: object properties: code: type: string example: VALIDATION_ERROR message: type: string example: 'The given data was invalid' details: type: object properties: name: type: array example: - 'The name field is required.' items: type: string category: type: array example: - 'The selected category is invalid.' items: type: string tags: - 'Email Templates' requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Template display name.' example: 'Monthly Newsletter' slug: type: string description: 'Unique URL-safe identifier for the template.' example: monthly-newsletter category: type: string description: 'Template category. Options: transactional, marketing, notification, onboarding, custom.' example: marketing subject_template: type: string description: 'Subject line with optional merge tags.' example: 'Your @{{month}} Newsletter' html_content: type: string description: 'HTML body of the template with optional merge tags.' example: 'Here is your newsletter.
' text_content: type: string description: 'optional Plain-text version of the template. Auto-generated from HTML if omitted.' example: 'Hello @{{first_name}}, here is your newsletter.' nullable: true mjml_content: type: string description: 'optional MJML source (for GrapesJS editor).' example: 'Thanks for joining.
' text_content: 'Welcome, @{{first_name}}! Thanks for joining.' mjml_content: null variables: - key: first_name label: 'First Name' default: there required: true properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'Welcome Email' slug: type: string example: welcome-email category: type: string example: onboarding subject_template: type: string example: 'Welcome, @{{first_name}}!' is_system: type: boolean example: false is_active: type: boolean example: true version: type: integer example: 1 times_used: type: integer example: 150 created_at: type: string example: '2026-01-10T10:00:00+00:00' updated_at: type: string example: '2026-01-12T14:30:00+00:00' description: type: string example: 'Sent to new users after registration' html_content: type: string example: 'Thanks for joining.
' text_content: type: string example: 'Welcome, @{{first_name}}! Thanks for joining.' mjml_content: type: string example: null variables: type: array example: - key: first_name label: 'First Name' default: there required: true items: type: object properties: key: type: string example: first_name label: type: string example: 'First Name' default: type: string example: there required: type: boolean example: true 404: description: 'Template Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Template not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Template not found' tags: - 'Email Templates' put: summary: 'Update Template' operationId: updateTemplate description: "Update an existing email template. Only organization-owned templates can be edited;\nsystem templates require super admin privileges. All fields are optional; only\nprovided fields will be updated." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'Monthly Newsletter v2' slug: monthly-newsletter category: marketing subject_template: 'Your @{{month}} Newsletter - Updated' is_system: false is_active: true version: 1 times_used: 150 created_at: '2026-01-10T10:00:00+00:00' updated_at: '2026-01-15T14:30:00+00:00' description: 'Updated monthly newsletter' html_content: 'New content.
' text_content: 'Hello @{{first_name}}, new content.' mjml_content: null variables: [] message: 'Template updated.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'Monthly Newsletter v2' slug: type: string example: monthly-newsletter category: type: string example: marketing subject_template: type: string example: 'Your @{{month}} Newsletter - Updated' is_system: type: boolean example: false is_active: type: boolean example: true version: type: integer example: 1 times_used: type: integer example: 150 created_at: type: string example: '2026-01-10T10:00:00+00:00' updated_at: type: string example: '2026-01-15T14:30:00+00:00' description: type: string example: 'Updated monthly newsletter' html_content: type: string example: 'New content.
' text_content: type: string example: 'Hello @{{first_name}}, new content.' mjml_content: type: string example: null variables: type: array example: [] message: type: string example: 'Template updated.' 403: description: 'System Template' content: application/json: schema: type: object example: success: false error: code: FORBIDDEN message: 'System templates cannot be edited' properties: success: type: boolean example: false error: type: object properties: code: type: string example: FORBIDDEN message: type: string example: 'System templates cannot be edited' 404: description: 'Template Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Template not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Template not found' tags: - 'Email Templates' requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: 'optional Updated template name.' example: 'Monthly Newsletter v2' category: type: string description: 'optional Updated category. Options: transactional, marketing, notification, onboarding, custom.' example: marketing subject_template: type: string description: 'optional Updated subject line.' example: 'Your @{{month}} Newsletter - Updated' html_content: type: string description: 'optional Updated HTML body.' example: 'New content.
' text_content: type: string description: 'optional Updated plain-text body.' example: 'Hello @{{first_name}}, new content.' nullable: true description: type: string description: 'optional Updated description.' example: 'Updated monthly newsletter' nullable: true variables: type: array description: 'optional Updated variable definitions.' example: - architecto items: type: string nullable: true is_active: type: boolean description: 'optional Whether the template is active.' example: true delete: summary: 'Delete Template' operationId: deleteTemplate description: "Soft-delete an email template. System templates require super admin privileges\nto delete. Deleted templates are no longer available for sending but their\ndata is preserved for historical reference." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: null message: 'Template deleted.' properties: success: type: boolean example: true data: type: string example: null message: type: string example: 'Template deleted.' 403: description: 'System Template' content: application/json: schema: type: object example: success: false error: code: FORBIDDEN message: 'System templates cannot be deleted' properties: success: type: boolean example: false error: type: object properties: code: type: string example: FORBIDDEN message: type: string example: 'System templates cannot be deleted' 404: description: 'Template Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Template not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Template not found' tags: - 'Email Templates' parameters: - in: path name: id description: 'The UUID of the template.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/email-templates/{id}/preview': post: summary: 'Preview Template' operationId: previewTemplate description: "Render a template with sample data and return the resulting subject, HTML, and\nplain-text content. Useful for previewing how merge tags will be replaced before\nsending. Pass variable values in the `data` object." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: subject: 'Welcome, John!' html_content: 'Thanks for joining.
' text_content: 'Welcome, John! Thanks for joining.' properties: success: type: boolean example: true data: type: object properties: subject: type: string example: 'Welcome, John!' html_content: type: string example: 'Thanks for joining.
' text_content: type: string example: 'Welcome, John! Thanks for joining.' 404: description: 'Template Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Template not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Template not found' tags: - 'Email Templates' requestBody: required: false content: application/json: schema: type: object properties: data: type: object description: 'optional Key-value pairs of template variables to substitute.' example: first_name: John month: January properties: { } parameters: - in: path name: id description: 'The UUID of the template.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/email-templates/{id}/duplicate': post: summary: 'Duplicate Template' operationId: duplicateTemplate description: "Create a copy of an existing template in your organization. The copy will have\n\" (Copy)\" appended to its name and a unique slug with a timestamp suffix.\nUseful for customizing system templates or creating variations of existing ones." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true data: id: 9b8f3e6d-2345-6789-01bc-def123456789 name: 'Welcome Email (Copy)' slug: welcome-email-copy-1705312200 category: onboarding subject_template: 'Welcome, @{{first_name}}!' is_system: false is_active: true version: 1 times_used: 0 created_at: '2026-01-15T10:30:00+00:00' updated_at: '2026-01-15T10:30:00+00:00' description: 'Sent to new users after registration' html_content: 'Thanks for joining.
' text_content: 'Welcome, @{{first_name}}! Thanks for joining.' mjml_content: null variables: - key: first_name label: 'First Name' default: there required: true message: 'Template duplicated.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9b8f3e6d-2345-6789-01bc-def123456789 name: type: string example: 'Welcome Email (Copy)' slug: type: string example: welcome-email-copy-1705312200 category: type: string example: onboarding subject_template: type: string example: 'Welcome, @{{first_name}}!' is_system: type: boolean example: false is_active: type: boolean example: true version: type: integer example: 1 times_used: type: integer example: 0 created_at: type: string example: '2026-01-15T10:30:00+00:00' updated_at: type: string example: '2026-01-15T10:30:00+00:00' description: type: string example: 'Sent to new users after registration' html_content: type: string example: 'Thanks for joining.
' text_content: type: string example: 'Welcome, @{{first_name}}! Thanks for joining.' mjml_content: type: string example: null variables: type: array example: - key: first_name label: 'First Name' default: there required: true items: type: object properties: key: type: string example: first_name label: type: string example: 'First Name' default: type: string example: there required: type: boolean example: true message: type: string example: 'Template duplicated.' 404: description: 'Template Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Template not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Template not found' tags: - 'Email Templates' parameters: - in: path name: id description: 'The UUID of the template to duplicate.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string /api/v1/campaigns: get: summary: 'List Campaigns' operationId: listCampaigns description: "Retrieve a paginated list of all email campaigns for your organization, sorted by newest first.\nOptionally filter by campaign status." parameters: - in: query name: status description: 'Filter by campaign status. Options: draft, scheduled, sending, paused, completed, cancelled, failed.' example: draft required: false schema: type: string description: 'Filter by campaign status. Options: draft, scheduled, sending, paused, completed, cancelled, failed.' example: draft - in: query name: per_page description: 'Number of results per page (default 25).' example: 10 required: false schema: type: integer description: 'Number of results per page (default 25).' example: 10 responses: 200: description: '' content: application/json: schema: type: object example: success: true data: data: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'January Newsletter' status: completed subject: 'Your January Update' from_email: hello@exosend.com total_recipients: 5000 total_sent: 4980 total_opened: 2100 total_clicked: 450 total_bounced: 20 created_at: '2026-01-10T10:00:00+00:00' pagination: total: 15 per_page: 25 current_page: 1 last_page: 1 properties: success: type: boolean example: true data: type: object properties: data: type: array example: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'January Newsletter' status: completed subject: 'Your January Update' from_email: hello@exosend.com total_recipients: 5000 total_sent: 4980 total_opened: 2100 total_clicked: 450 total_bounced: 20 created_at: '2026-01-10T10:00:00+00:00' items: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'January Newsletter' status: type: string example: completed subject: type: string example: 'Your January Update' from_email: type: string example: hello@exosend.com total_recipients: type: integer example: 5000 total_sent: type: integer example: 4980 total_opened: type: integer example: 2100 total_clicked: type: integer example: 450 total_bounced: type: integer example: 20 created_at: type: string example: '2026-01-10T10:00:00+00:00' pagination: type: object properties: total: type: integer example: 15 per_page: type: integer example: 25 current_page: type: integer example: 1 last_page: type: integer example: 1 tags: - 'Email Campaigns' post: summary: 'Create Campaign' operationId: createCampaign description: "Create a new email campaign in draft status. The campaign can then be sent immediately\nor scheduled for later delivery. You can specify target contact lists and optionally\nuse a pre-built template." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'January Newsletter' status: draft subject: 'Your January Update' from_email: hello@exosend.com from_name: 'ExoSend Team' total_recipients: 0 total_sent: 0 created_at: '2026-01-15T10:00:00+00:00' message: 'Campaign created.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'January Newsletter' status: type: string example: draft subject: type: string example: 'Your January Update' from_email: type: string example: hello@exosend.com from_name: type: string example: 'ExoSend Team' total_recipients: type: integer example: 0 total_sent: type: integer example: 0 created_at: type: string example: '2026-01-15T10:00:00+00:00' message: type: string example: 'Campaign created.' 422: description: '' content: application/json: schema: oneOf: - description: 'Invalid Target List' type: object example: success: false error: code: INVALID_LIST message: 'One or more target lists do not exist or do not belong to your organization' properties: success: type: boolean example: false error: type: object properties: code: type: string example: INVALID_LIST message: type: string example: 'One or more target lists do not exist or do not belong to your organization' - description: 'Validation Error' type: object example: success: false error: code: VALIDATION_ERROR message: 'The given data was invalid' details: name: - 'The name field is required.' subject: - 'The subject field is required.' properties: success: type: boolean example: false error: type: object properties: code: type: string example: VALIDATION_ERROR message: type: string example: 'The given data was invalid' details: type: object properties: name: type: array example: - 'The name field is required.' items: type: string subject: type: array example: - 'The subject field is required.' items: type: string tags: - 'Email Campaigns' requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Campaign name for internal reference.' example: 'January Newsletter' description: type: string description: 'optional Internal description of the campaign.' example: 'Monthly newsletter for January 2026' nullable: true email_template_id: type: string description: 'optional UUID of an email template to use.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 nullable: true subject: type: string description: 'Email subject line for the campaign.' example: 'Your January Update' html_content: type: string description: 'optional HTML body of the email (if not using a template).' example: 'Here are our latest updates.
' nullable: true text_content: type: string description: 'optional Plain-text body of the email.' example: 'Here are our latest updates.' nullable: true template_variables: type: object description: 'optional Variables to merge into the template.' example: month: January year: '2026' properties: { } nullable: true email_domain_id: type: string description: 'optional UUID of the email domain to send from.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 nullable: true from_email: type: string description: 'Sender email address.' example: hello@exosend.com from_name: type: string description: 'optional Sender display name.' example: 'ExoSend Team' nullable: true reply_to: type: string description: 'optional Reply-to email address.' example: support@exosend.com nullable: true target_list_ids: type: array description: 'optional Array of contact list UUIDs to send to.' example: - 9a7f2e5c-1234-5678-90ab-cdef12345678 items: type: string segment_filters: type: object description: 'optional Segment filter criteria for targeting contacts.' example: status: active properties: { } nullable: true send_rate_per_minute: type: integer description: 'optional Throttle: maximum emails per minute.' example: 500 nullable: true batch_size: type: integer description: 'optional Number of emails per batch (1-1000).' example: 100 nullable: true timezone: type: string description: '' example: Asia/Yekaterinburg nullable: true scheduled_at: type: string description: 'Must be a valid date.' example: '2026-03-04T23:25:31' nullable: true required: - name - subject - from_email '/api/v1/campaigns/{id}': get: summary: 'Get Campaign Details' operationId: getCampaignDetails description: "Retrieve full details of a specific campaign, including computed engagement rates\n(open rate, click rate, bounce rate) if the campaign has been sent." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'January Newsletter' status: completed subject: 'Your January Update' from_email: hello@exosend.com from_name: 'ExoSend Team' total_recipients: 5000 total_sent: 4980 total_opened: 2100 total_clicked: 450 total_bounced: 20 open_rate: 42.2% click_rate: 9.0% bounce_rate: 0.4% created_at: '2026-01-10T10:00:00+00:00' sent_at: '2026-01-10T12:00:00+00:00' completed_at: '2026-01-10T13:30:00+00:00' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'January Newsletter' status: type: string example: completed subject: type: string example: 'Your January Update' from_email: type: string example: hello@exosend.com from_name: type: string example: 'ExoSend Team' total_recipients: type: integer example: 5000 total_sent: type: integer example: 4980 total_opened: type: integer example: 2100 total_clicked: type: integer example: 450 total_bounced: type: integer example: 20 open_rate: type: string example: 42.2% click_rate: type: string example: 9.0% bounce_rate: type: string example: 0.4% created_at: type: string example: '2026-01-10T10:00:00+00:00' sent_at: type: string example: '2026-01-10T12:00:00+00:00' completed_at: type: string example: '2026-01-10T13:30:00+00:00' 404: description: 'Campaign Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Campaign not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Campaign not found' tags: - 'Email Campaigns' put: summary: 'Update Campaign' operationId: updateCampaign description: "Update an existing campaign. Only campaigns in draft status can be updated.\nAll fields are optional; only provided fields will be modified." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'January Newsletter v2' status: draft subject: 'Your Updated January Newsletter' from_email: newsletter@exosend.com created_at: '2026-01-10T10:00:00+00:00' updated_at: '2026-01-15T14:30:00+00:00' message: 'Campaign updated.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'January Newsletter v2' status: type: string example: draft subject: type: string example: 'Your Updated January Newsletter' from_email: type: string example: newsletter@exosend.com created_at: type: string example: '2026-01-10T10:00:00+00:00' updated_at: type: string example: '2026-01-15T14:30:00+00:00' message: type: string example: 'Campaign updated.' 404: description: 'Campaign Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Campaign not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Campaign not found' 422: description: '' content: application/json: schema: oneOf: - description: 'Not Draft Status' type: object example: success: false error: code: INVALID_STATUS message: 'Only draft campaigns can be updated' properties: success: type: boolean example: false error: type: object properties: code: type: string example: INVALID_STATUS message: type: string example: 'Only draft campaigns can be updated' - description: 'Invalid Target List' type: object example: success: false error: code: INVALID_LIST message: 'One or more target lists do not exist or do not belong to your organization' properties: success: type: boolean example: false error: type: object properties: code: type: string example: INVALID_LIST message: type: string example: 'One or more target lists do not exist or do not belong to your organization' tags: - 'Email Campaigns' requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: 'optional Updated campaign name.' example: 'January Newsletter v2' description: type: string description: 'optional Updated description.' example: 'Revised newsletter content' nullable: true email_template_id: type: string description: 'optional UUID of template to use.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 nullable: true subject: type: string description: 'optional Updated subject line.' example: 'Your Updated January Newsletter' html_content: type: string description: 'optional Updated HTML body.' example: '2026-03-04 23:30:31.'
example: '2052-03-28'
nullable: true
timezone:
type: string
description: 'This field is required when scheduled_at is present.'
example: Asia/Yekaterinburg
nullable: true
batch_size:
type: integer
description: 'Must be at least 1. Must not be greater than 50000.'
example: 22
nullable: true
send_rate_per_minute:
type: integer
description: 'Must be at least 1. Must not be greater than 10000.'
example: 7
nullable: true
required:
- name
- message_content
- sender_id_id
get:
summary: 'List SMS Campaigns'
operationId: listSMSCampaigns
description: 'Retrieve a paginated list of all SMS campaigns for your organization, sorted by newest first.'
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'SMS Campaigns'
'/api/v1/sms/campaigns/{smsCampaign_id}':
get:
summary: 'Get SMS Campaign Details'
operationId: getSMSCampaignDetails
description: 'Retrieve full details of a specific SMS campaign including refreshed stats.'
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'SMS Campaigns'
parameters:
-
in: path
name: smsCampaign_id
description: 'The ID of the smsCampaign.'
example: 019ca407-ed12-71fa-8020-ed1b6fefc081
required: true
schema:
type: string
'/api/v1/sms/campaigns/{smsCampaign_id}/send':
post:
summary: 'Send SMS Campaign'
operationId: sendSMSCampaign
description: 'Start sending a draft or scheduled campaign immediately.'
parameters: []
responses: { }
tags:
- 'SMS Campaigns'
parameters:
-
in: path
name: smsCampaign_id
description: 'The ID of the smsCampaign.'
example: 019ca407-ed12-71fa-8020-ed1b6fefc081
required: true
schema:
type: string
'/api/v1/sms/campaigns/{smsCampaign_id}/pause':
post:
summary: 'Pause SMS Campaign'
operationId: pauseSMSCampaign
description: 'Pause a campaign that is currently sending.'
parameters: []
responses: { }
tags:
- 'SMS Campaigns'
parameters:
-
in: path
name: smsCampaign_id
description: 'The ID of the smsCampaign.'
example: 019ca407-ed12-71fa-8020-ed1b6fefc081
required: true
schema:
type: string
'/api/v1/sms/campaigns/{smsCampaign_id}/cancel':
post:
summary: 'Cancel SMS Campaign'
operationId: cancelSMSCampaign
description: 'Cancel a campaign that is sending, paused, or scheduled.'
parameters: []
responses: { }
tags:
- 'SMS Campaigns'
parameters:
-
in: path
name: smsCampaign_id
description: 'The ID of the smsCampaign.'
example: 019ca407-ed12-71fa-8020-ed1b6fefc081
required: true
schema:
type: string