Skip to content

Email Gateway API Documentation

Email Gateway endpoints for managing transactional email sending through verified domains, including domain management, API keys, SMTP credentials, webhooks, and email delivery.

Add a Sender Domain

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.AddDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.adddomain
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainNameStringYesDomain name to add (e.g., example.com)
SubdomainStringNoCustom subdomain override (e.g., outbound). Only letters, numbers, and hyphens allowed (max 32 characters).
TrackPrefixStringNoCustom tracking prefix override (e.g., links). Only letters, numbers, and hyphens allowed (max 32 characters).
TrackMergeStringNoCustom tracking merge character (e.g., -).
TrackPrefixDisabledIntegerNoSet to 1 to disable the separate tracking subdomain. When disabled, tracking URLs use the sender (MFROM) domain instead.
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.adddomain",
    "SessionID": "your-session-id",
    "DomainName": "example.com",
    "Subdomain": "outbound",
    "TrackPrefix": "links"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "NewSenderDomainID": 123,
  "Domain": {
    "DomainID": 123,
    "SenderDomain": "example.com",
    "Status": "Approval Pending",
    "Options": {
      "LinkTracking": 1,
      "OpenTracking": 1,
      "UnsubscribeLink": 0,
      "CustomSubdomain": "outbound",
      "CustomTrackPrefix": "links"
    }
  }
}
json
{
  "Success": false,
  "ErrorCode": [1]
}
txt
0: Success
1: Missing required parameter (DomainName)
2: Invalid domain name format
3: Maximum sender domains limit reached for user
4: Invalid subdomain or track prefix value

Get Sender Domain Details

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.getdomain
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerYesSender domain ID
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.getdomain",
    "SessionID": "your-session-id",
    "DomainID": 123
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "Domain": {
    "DomainID": 123,
    "SenderDomain": "example.com",
    "Status": "Enabled",
    "Options": {
      "LinkTracking": 1,
      "OpenTracking": 1,
      "UnsubscribeLink": 0
    }
  }
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access denied

Update Sender Domain Settings

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.updatedomain
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerYesSender domain ID
LinkTrackingIntegerNoEnable link tracking (1 = enabled, 0 = disabled)
OpenTrackingIntegerNoEnable open tracking (1 = enabled, 0 = disabled)
UnsubscribeLinkIntegerNoEnable unsubscribe link (1 = enabled, 0 = disabled)
HostingProviderStringNoHosting provider name
SubdomainStringNoCustom subdomain override (e.g., outbound). Only letters, numbers, and hyphens allowed (max 32 characters). Leave empty to reset to global default.
TrackPrefixStringNoCustom tracking prefix override (e.g., links). Only letters, numbers, and hyphens allowed (max 32 characters). Leave empty to reset to global default.
TrackMergeStringNoCustom tracking merge character (e.g., -).
TrackPrefixDisabledIntegerNoSet to 1 to disable the separate tracking subdomain. When disabled, tracking URLs use the sender (MFROM) domain instead. Set to 0 to re-enable.
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.updatedomain",
    "SessionID": "your-session-id",
    "DomainID": 123,
    "LinkTracking": 1,
    "OpenTracking": 1,
    "Subdomain": "outbound",
    "TrackPrefixDisabled": 1
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "Domain": {
    "DomainID": 123,
    "SenderDomain": "example.com",
    "Status": "Approval Pending",
    "Options": {
      "LinkTracking": 1,
      "OpenTracking": 1,
      "CustomSubdomain": "outbound",
      "TrackPrefixDisabled": true
    }
  },
  "SubdomainChanged": true
}
json
{
  "Success": false,
  "ErrorCode": 5
}
txt
0: Success
1: Missing required parameter (DomainID)
5: Domain not found or access denied
6: Invalid subdomain or track prefix value

Verify Sender Domain DNS Records

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.verifydomain
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerYesSender domain ID
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.verifydomain",
    "SessionID": "your-session-id",
    "DomainID": 123
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "Domain": {
    "DomainID": 123,
    "SenderDomain": "example.com",
    "Status": "Enabled"
  },
  "DNSVerificationResults": {
    "mail.example.com": ["CNAME", "target.example.com", true],
    "example.com": ["TXT", "v=spf1 include:example.com ~all", true]
  }
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access denied

Get All Sender Domains

POST /api.php

API Usage Notes

  • Authentication required: User API Key. Admin authentication is also accepted with Access=admin and UserID (see Admin usage below)
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Returns every sender domain owned by the caller. By default the response shape is byte-for-byte identical to the pre-#1996 endpoint — useful for the legacy dashboard.

When WithCounts=1 is supplied, each row is additionally enriched with per-row API-key / SMTP / webhook counts (one SQL aggregation) and 7-day Sent / BounceRate / LastActivityAt stats from ES (one ES terms aggregation). This replaces the legacy 4×N pattern of calling emailgateway.getapis + emailgateway.getsmtps + emailgateway.getwebhooks + emailgateway.domainstats once per domain, which made the Overview unusable past ~20 domains.

Admin usage (v5.9.6, #2775)

This command also accepts admin authentication. Pass AdminAPIKey (or an admin SessionID), Access=admin, and UserID naming the account to act on; the response is exactly what that account's own API key would receive. Without Access=admin the call is treated as a user call, so existing integrations are unaffected. UserID is ignored under user authentication. Admin-only error codes: 5001 UserID missing or invalid, 5002 user not found, 5003 user outside the user groups a restricted sub-admin may access. Requires the User.Edit privilege when ADMIN_API_ENFORCE_PRIVILEGES is on.

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.getdomains
UserIDIntegerAdmin onlyAccount to act on when calling with admin authentication and Access=admin. Ignored under user authentication (v5.9.6, #2775)
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
WithCountsMixedNoWhen truthy (1, '1', 'true', 'yes', true), each domain row is enriched with APIKeyCount, SMTPCount, WebhookCount, Sent7d, BounceRate7d, LastActivityAt. Any other value (missing, 0, 'false', garbage) → no enrichment, legacy shape preserved.

Enrichment field reference (only present when WithCounts=1):

FieldTypeSourceDefinition
APIKeyCountIntegerSQLCount of oempro_eg_api_keys rows for this domain with Status='Enabled'
SMTPCountIntegerSQLCount of oempro_eg_smtp_credentials rows for this domain with Status='Enabled'
WebhookCountIntegerSQLCount of oempro_eg_webhooks rows for this domain with Status='Enabled'
Sent7dIntegerESCount of accepted-by-oempro events for this domain in the last 7 days
BounceRate7dFloat (2dp)computedBounced / Sent × 100. Returns 0 when Sent7d is 0 (no divide-by-zero NaN)
LastActivityAtString or nullESMAX(logged-at) within the 7-day window as ISO 8601 UTC; null if no events
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.getdomains",
    "SessionID": "your-session-id"
  }'
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.getdomains",
    "SessionID": "your-session-id",
    "WithCounts": 1
  }'
json
{
  "Success": true,
  "Domains": [
    {
      "DomainID": "123",
      "SenderDomain": "mail.apex.com",
      "CreatedAt": "2026-04-01 10:00:00",
      "Status": "Enabled",
      "VerificationMeta": {"DNSRecords": []},
      "PolicyMeta": [],
      "Options": {"LinkTracking": 1, "OpenTracking": 1, "UnsubscribeLink": 0}
    }
  ]
}
json
{
  "Success": true,
  "Domains": [
    {
      "DomainID": "123",
      "SenderDomain": "mail.apex.com",
      "CreatedAt": "2026-04-01 10:00:00",
      "Status": "Enabled",
      "VerificationMeta": {"DNSRecords": []},
      "PolicyMeta": [],
      "Options": {"LinkTracking": 1, "OpenTracking": 1, "UnsubscribeLink": 0},
      "APIKeyCount": 1,
      "SMTPCount": 2,
      "WebhookCount": 2,
      "Sent7d": 184320,
      "BounceRate7d": 1.00,
      "LastActivityAt": "2026-05-15T18:42:11Z"
    }
  ]
}
txt
0: Success

Delete a Sender Domain

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.deletedomain
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerYesSender domain ID to delete
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.deletedomain",
    "SessionID": "your-session-id",
    "DomainID": 123
  }'
json
{
  "Success": true,
  "ErrorCode": 0
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access denied

Clear Domain Email Queue

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.cleardomainqueue
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerYesSender domain ID
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.cleardomainqueue",
    "SessionID": "your-session-id",
    "DomainID": 123
  }'
json
{
  "Success": true,
  "ErrorCode": 0
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access denied

Get Domain Statistics

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.domainstats
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerYesSender domain ID
StartDateStringNoStart date (Y-m-d format, default: 28 days ago)
EndDateStringNoEnd date (Y-m-d format, default: today)
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.domainstats",
    "SessionID": "your-session-id",
    "DomainID": 123,
    "StartDate": "2024-01-01",
    "EndDate": "2024-01-31"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "Stats": {
    "Sent": 1000,
    "Delivered": 950,
    "Bounced": 50,
    "Opened": 400,
    "Clicked": 150
  },
  "ComparisonStats": {
    "PreviousPeriod": {
      "Sent": 800,
      "Delivered": 760
    }
  },
  "TagStats": {
    "campaign1": {
      "Sent": 500,
      "Delivered": 475
    }
  }
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access denied

Get Account-Wide Statistics

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Returns cross-domain aggregate statistics (Sent / Delivered / Bounced / Opened / Clicked + rates) for the caller's email gateway domains in a single call. Replaces the N+1 pattern of looping emailgateway.domainstats once per sender domain. Per-domain rows are ordered by Sent descending; domains with no events in the period are zero-filled. The ComparisonTotals window is the same length as the requested period, ending the second before StartDate.

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.accountstats
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
StartDateStringNoStart date (Y-m-d format, default: 30 days ago)
EndDateStringNoEnd date (Y-m-d format, default: today). Clamped to >= StartDate
DomainIDsStringNoComma-separated list of sender domain IDs to scope the result to. IDs not owned by the caller are silently ignored. Omit to include all of the caller's gateway domains
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.accountstats",
    "SessionID": "your-session-id",
    "StartDate": "2024-01-01",
    "EndDate": "2024-01-31",
    "DomainIDs": "1,2,3"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "StartDate": "2024-01-01 00:00:00",
  "EndDate": "2024-01-31 23:59:59",
  "Totals": {
    "Sent": 232530,
    "Delivered": 226115,
    "Bounced": 1931,
    "Opened": 90636,
    "Clicked": 14261,
    "DeliveryRate": 97.24,
    "OpenRate": 38.99,
    "ClickRate": 6.14,
    "BounceRate": 0.83
  },
  "ComparisonTotals": {
    "Sent": 198400,
    "Delivered": 192100,
    "Bounced": 1820,
    "Opened": 74500,
    "Clicked": 11020,
    "DeliveryRate": 96.83,
    "OpenRate": 37.55,
    "ClickRate": 5.55,
    "BounceRate": 0.92
  },
  "PerDomain": [
    {
      "DomainID": 1,
      "SenderDomain": "mail.apex.com",
      "Sent": 184320,
      "Delivered": 178224,
      "Bounced": 1520,
      "Opened": 71890,
      "Clicked": 11420,
      "DeliveryRate": 96.69,
      "BounceRate": 0.82,
      "OpenRate": 39.0,
      "ClickRate": 6.19
    }
  ]
}
json
{
  "Success": false,
  "ErrorCode": 99998
}
txt
0: Success
99998: Authentication failure or session expired

Create API Key for Domain

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.addapi
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DescriptionStringYesDescription for the API key
DomainIDIntegerYesSender domain ID
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.addapi",
    "SessionID": "your-session-id",
    "Description": "Production API Key",
    "DomainID": 123
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "NewAPIKeyID": 456,
  "APIKey": {
    "APIKeyID": 456,
    "APIKey": "eg_live_xxxxxxxxxxxx",
    "Description": "Production API Key",
    "DomainID": 123
  }
}
json
{
  "Success": false,
  "ErrorCode": [1, 2]
}
txt
0: Success
1: Missing required parameter (Description)
2: Missing required parameter (DomainID)
3: Domain not found or access denied

Get Domain API Keys

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.getapis
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerYesSender domain ID
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.getapis",
    "SessionID": "your-session-id",
    "DomainID": 123
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "APIKeys": [
    {
      "APIKeyID": 456,
      "APIKey": "eg_live_xxxxxxxxxxxx",
      "Description": "Production API Key",
      "DomainID": 123
    }
  ]
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access denied

Delete Domain API Key

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.deleteapi
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
APIKeyIDIntegerYesAPI Key ID to delete
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.deleteapi",
    "SessionID": "your-session-id",
    "APIKeyID": 456
  }'
json
{
  "Success": true,
  "ErrorCode": 0
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (APIKeyID)
2: API Key not found or access denied

Create SMTP Credentials for Domain

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.addsmtp
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerYesSender domain ID
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.addsmtp",
    "SessionID": "your-session-id",
    "DomainID": 123
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "NewSMTPID": 789,
  "SMTP": {
    "SMTPID": 789,
    "SMTPUsername": "smtp_user_xxx",
    "SMTPPassword": "generated_password",
    "SMTPHost": "smtp.example.com",
    "SMTPPorts": [25, 587, 2525]
  }
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access denied

Get Domain SMTP Credentials

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.getsmtps
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerYesSender domain ID
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.getsmtps",
    "SessionID": "your-session-id",
    "DomainID": 123
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "SMTPs": [
    {
      "SMTPID": 789,
      "SMTPUsername": "smtp_user_xxx",
      "SMTPHost": "smtp.example.com",
      "SMTPPorts": [25, 587, 2525]
    }
  ]
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access denied

Delete SMTP Credentials

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.deletesmtp
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
SMTPIDIntegerYesSMTP credentials ID to delete
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.deletesmtp",
    "SessionID": "your-session-id",
    "SMTPID": 789
  }'
json
{
  "Success": true,
  "ErrorCode": 0
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (SMTPID)
2: SMTP credentials not found or access denied

Reset SMTP Password

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.resetsmtppassword
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
SMTPIDIntegerYesSMTP credentials ID
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.resetsmtppassword",
    "SessionID": "your-session-id",
    "SMTPID": 789
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "SMTP": {
    "SMTPID": 789,
    "SMTPUsername": "smtp_user_xxx",
    "SMTPPassword": "new_generated_password"
  }
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (SMTPID)
2: SMTP credentials not found or access denied

Create Webhook for Domain

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

The WebhookURL is validated to prevent SSRF attacks: the scheme must be http or https, and the host cannot be localhost, 127.0.0.1, ::1, or end in .local. These checks live in the API itself (since #1999) so direct API callers can't bypass them by skipping the legacy UI flow.

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.addwebhook
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerYesSender domain ID
EventStringYesEvent type. Possible values: delivery, bounce, open, click, unsubscribe, complaint
WebhookURLStringYesWebhook URL to receive event notifications. Must be an http/https URL whose host is not localhost / 127.0.0.1 / ::1 / *.local
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.addwebhook",
    "SessionID": "your-session-id",
    "DomainID": 123,
    "Event": "delivery",
    "WebhookURL": "https://example.com/webhooks/email-events"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "NewWebhookID": 999
}
json
{
  "Success": false,
  "ErrorCode": 6,
  "ErrorMessage": "Webhook URL must be a valid HTTP or HTTPS URL."
}
txt
0: Success
1: Missing required parameter (DomainID)
2: Missing required parameter (Event)
3: Invalid event type
4: Domain not found or access denied
5: Missing required parameter (WebhookURL)
6: WebhookURL is not a valid HTTP or HTTPS URL (e.g. ftp:// or malformed)
7: WebhookURL host is forbidden (localhost, 127.0.0.1, ::1, or *.local)

Get Domain Webhooks

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.getwebhooks
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerYesSender domain ID
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.getwebhooks",
    "SessionID": "your-session-id",
    "DomainID": 123
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "SigningKey": "whsec_xxxxxxxxxxxx",
  "Webhooks": [
    {
      "WebhookID": 999,
      "Event": "delivery",
      "WebhookURL": "https://example.com/webhooks/email-events"
    }
  ]
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access denied

Delete Domain Webhook

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.deletewebhook
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
WebhookIDIntegerYesWebhook ID to delete
DomainIDIntegerYesSender domain ID
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.deletewebhook",
    "SessionID": "your-session-id",
    "WebhookID": 999,
    "DomainID": 123
  }'
json
{
  "Success": true,
  "ErrorCode": 0
}
json
{
  "Success": false,
  "ErrorCode": 4
}
txt
0: Success
1: Missing required parameter (WebhookID)
2: Missing required parameter (DomainID)
3: Domain not found or access denied
4: Webhook not found or access denied

Create Webhook (Public API)

POST /api/v1/webhooks

API Usage Notes

  • Authentication required: Bearer token
  • Rate limit: 100 requests per 60 seconds
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

Like the private emailgateway.addwebhook endpoint, the public endpoint validates WebhookURL to prevent SSRF: the scheme must be http or https, and the host cannot be localhost, 127.0.0.1, ::1, or end in .local. (Since v5.9.3 / #2349 — the public endpoint previously accepted any well-formed URL; it now enforces the same restrictions as the private endpoint.)

ParameterTypeRequiredDescription
SenderAPIKeyStringYesAPI key for the sender domain (Bearer token)
EventStringYesEvent type: delivered, bounced, opened, clicked, unsubscribed, complained
WebhookURLStringYesWebhook URL to receive event notifications. Must be an http/https URL whose host is not localhost / 127.0.0.1 / ::1 / *.local
bash
curl -X POST https://example.com/api/v1/webhooks \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eg_live_xxxxxxxxxxxx" \
  -d '{
    "Event": "delivered",
    "WebhookURL": "https://example.com/webhooks/email-events"
  }'
json
{
  "NewWebhookID": 999
}
json
{
  "Errors": [
    {"Code": 3, "Message": "Invalid Event value"}
  ]
}
txt
2: Event is missing
3: Invalid Event value, or malformed WebhookURL
5: WebhookURL is missing
6: WebhookURL must be a valid HTTP or HTTPS URL (rejects ftp://, gopher://, etc.)
7: WebhookURL cannot point to localhost, loopback, or .local addresses
13: Invalid SenderAPIKey
429: Too many requests

Get Webhooks (Public API)

GET /api/v1/webhooks

API Usage Notes

  • Authentication required: Bearer token
  • Rate limit: 100 requests per 60 seconds
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
SenderAPIKeyStringYesAPI key for the sender domain (Bearer token)
bash
curl -X GET https://example.com/api/v1/webhooks \
  -H "Authorization: Bearer eg_live_xxxxxxxxxxxx"
json
{
  "Webhooks": [
    {
      "WebhookID": 999,
      "Event": "delivered",
      "WebhookURL": "https://example.com/webhooks/email-events"
    }
  ]
}
json
{
  "Errors": [
    {"Code": 13, "Message": "Invalid SenderAPIKey"}
  ]
}
txt
2: Invalid user account
13: Invalid SenderAPIKey
429: Too many requests

Delete Webhook (Public API)

DELETE /api/v1/webhooks

API Usage Notes

  • Authentication required: Bearer token
  • Rate limit: 100 requests per 60 seconds
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
SenderAPIKeyStringYesAPI key for the sender domain (Bearer token)
WebhookIDIntegerYesWebhook ID to delete
bash
curl -X DELETE https://example.com/api/v1/webhooks \
  -H "Authorization: Bearer eg_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "WebhookID": 999
  }'
json
{}
json
{
  "Errors": [
    {"Code": 4, "Message": "Invalid WebhookID"}
  ]
}
txt
1: WebhookID is missing
2: Invalid user account
4: Invalid WebhookID
13: Invalid SenderAPIKey
429: Too many requests

Get Email Events

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Cross-domain queries

DomainID is optional. When a DomainID is supplied, events are scoped to that single domain and ownership is verified against the authenticated user. When DomainID is omitted, the query spans every sender domain owned by the authenticated user. Tenant isolation is inherent: events are indexed per-user in Elasticsearch (eg-events-u<UserID>-<date>) and the query always filters by user-id regardless of whether a DomainID is provided.

Note that the public variant (emailgateway.getevents.public) still requires DomainID because it is authenticated by a domain-scoped API key.

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.getevents
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerNoSender domain ID to scope the query to. Omit to return events across all of the authenticated user's sender domains.
StartFromIntegerYesStarting record index for pagination
RetrieveCountIntegerYesNumber of records to retrieve (max 100)
StartDateStringNoStart date filter (Y-m-d format)
EndDateStringNoEnd date filter (Y-m-d format)
EventStringNoFilter by event type (delivery, bounce, open, click, etc.)
QueryStringNoSearch query string
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.getevents",
    "SessionID": "your-session-id",
    "DomainID": 123,
    "StartFrom": 0,
    "RetrieveCount": 50,
    "Event": "delivery"
  }'
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.getevents",
    "SessionID": "your-session-id",
    "StartFrom": 0,
    "RetrieveCount": 50,
    "Event": "delivery"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "TotalRecords": 150,
  "Events": [
    {
      "event": "delivery",
      "timestamp": 1640000000,
      "message": {
        "headers": {
          "from": "sender@example.com",
          "to": "recipient@example.com",
          "subject": "Test Email"
        }
      }
    }
  ]
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
2: Domain not found or access denied (only when DomainID is supplied)
4: Missing required parameter (StartFrom)
5: Missing required parameter (RetrieveCount)

Get Email Events (Public API)

GET /api/v1/events

API Usage Notes

  • Authentication required: Bearer token
  • Rate limit: 100 requests per 60 seconds
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
SenderAPIKeyStringYesAPI key for the sender domain (Bearer token)
StartFromIntegerNoStarting record index for pagination (default: 0)
RetrieveCountIntegerNoNumber of records to retrieve (default: 5, max 100)
StartDateIntegerNoStart date filter (Unix timestamp)
EndDateIntegerNoEnd date filter (Unix timestamp)
EventStringNoFilter by event type
MessageIDStringNoFilter by message ID
bash
curl -X GET https://example.com/api/v1/events \
  -H "Authorization: Bearer eg_live_xxxxxxxxxxxx" \
  -d '{
    "StartFrom": 0,
    "RetrieveCount": 50,
    "Event": "delivery"
  }'
json
{
  "TotalRecords": 150,
  "Events": [
    {
      "Event": "delivery",
      "LoggedAt": 1640000000,
      "Message": {
        "Headers": {
          "From": "sender@example.com",
          "To": "recipient@example.com",
          "Subject": "Test Email"
        }
      }
    }
  ]
}
json
{
  "Errors": [
    {"Code": 13, "Message": "Invalid SenderAPIKey"}
  ]
}
txt
1: Missing SenderAPIKey
2: Invalid sender domain
4: Missing StartFrom
5: Missing RetrieveCount
13: Invalid SenderAPIKey
429: Too many requests

Get Aggregated Event Statistics

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.aggrevents
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerYesSender domain ID
StartDateStringNoStart date filter (Y-m-d format)
EndDateStringNoEnd date filter (Y-m-d format)
AggregatedFieldStringNoField to aggregate by
AggregateSizeIntegerNoNumber of aggregation buckets
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.aggrevents",
    "SessionID": "your-session-id",
    "DomainID": 123,
    "StartDate": "2024-01-01",
    "EndDate": "2024-01-31"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "AggBuckets": [
    {
      "key": "delivery",
      "doc_count": 1000
    },
    {
      "key": "bounce",
      "doc_count": 50
    }
  ]
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access denied

Get Daily Event Time-Series

POST /api.php

Returns a daily time-series of email-gateway activity sourced from MySQL eg_queue, used to render the "Events Over Time" chart on the user dashboard. For each day in the requested range it reports Sent (count of rows with Status='Sent'), Bounced, Opened, and Clicked counts, bucketed by the day each email was queued (QueuedAtDay). When DomainID is supplied the series is scoped to that single domain (ownership validated); when omitted it spans all of the caller's sender domains. Days with no activity are omitted from the response — callers zero-fill the date range. The range defaults to the last 30 days when StartDate/EndDate are not provided.

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.dailyseries
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerNoSender domain ID. When omitted, the series spans all of the caller's domains
StartDateStringNoStart date filter (Y-m-d format). Defaults to 30 days before the end date
EndDateStringNoEnd date filter (Y-m-d format). Defaults to today
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.dailyseries",
    "SessionID": "your-session-id",
    "DomainID": 123,
    "StartDate": "2024-01-01",
    "EndDate": "2024-01-31"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "Series": {
    "2024-01-30": {
      "Sent": 2453,
      "Bounced": 0,
      "Opened": 661,
      "Clicked": 77
    },
    "2024-01-31": {
      "Sent": 625,
      "Bounced": 0,
      "Opened": 4,
      "Clicked": 2
    }
  }
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
2: Domain not found or access denied (only when DomainID is supplied)

Send Email via API

POST /api/v1/email

API Usage Notes

  • Authentication required: Bearer token
  • Rate limit: 100 requests per 60 seconds
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
SenderAPIKeyStringYesAPI key for the sender domain (Bearer token)
SubjectStringYesEmail subject line
ContentTypeStringYesContent type: html or plain
HTMLContentStringConditionalHTML email content (required if ContentType is html)
PlainContentStringConditionalPlain text email content (required if ContentType is plain)
FromObjectYesSender information:
ToArrayConditionalArray of recipients: [{name, email}] (required unless TargetListID is set)
CCArrayNoArray of CC recipients: [{name, email}]
BCCArrayNoArray of BCC recipients: [{name, email}]
ReplyToArrayNoArray of reply-to addresses: [{name, email}]
TagsArrayNoArray of custom tags for tracking
HeadersObjectNoCustom email headers as key-value pairs
TrackLinksStringNoEnable link tracking: true or false
TrackOpensStringNoEnable open tracking: true or false
SendAtIntegerNoSchedule send time (Unix timestamp)
AttachmentsArrayNoArray of attachments: [{filename, content, type, disposition, contentid}]
TemplateIDIntegerNoEmail template ID to use
TargetListIDIntegerNoSend to subscribers in a list. By default this delivers to the first 250 recipients only; set EMAILGATEWAY_SENDEMAIL_FULL_LIST=true to deliver to the entire list (see note below)
ListIDIntegerNoList ID for subscriber context
SubscriberIDIntegerNoSubscriber ID for personalization
JourneyIDIntegerNoJourney ID for tracking
ActionIDIntegerNoJourney action ID for tracking
bash
curl -X POST https://example.com/api/v1/email \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eg_live_xxxxxxxxxxxx" \
  -d '{
    "Subject": "Welcome to Our Service",
    "ContentType": "html",
    "HTMLContent": "<h1>Welcome!</h1><p>Thank you for signing up.</p>",
    "From": {
      "name": "John Doe",
      "email": "john@example.com"
    },
    "To": [
      {
        "name": "Jane Smith",
        "email": "jane@example.com"
      }
    ]
  }'
json
{
  "MessageID": "550e8400-e29b-41d4-a716-446655440000"
}
json
{
  "Errors": [
    {"Code": 8, "Message": "Missing Subject"}
  ]
}
txt
1: Missing SenderAPIKey
2: Invalid DomainID or invalid user account
3: From parameter is missing or invalid
8: Missing Subject or To email address
9: Missing ContentType
10: Missing HTMLContent
11: Missing PlainContent
12: Invalid or deactivated user account
13: Invalid SenderAPIKey
14: Email address is in the suppression list
15: Invalid TemplateID
16: Invalid TargetListID
17: Email sending limit reached
18: Recipient name or email address is missing
19: Recipient email address is invalid
20: There is no recipient set or count exceeds limit
21-22: CC email validation errors
23: Invalid from email address format
24-26: BCC email validation errors
27: BCC count exceeds limit
28-30: Reply-To email validation errors
31: Reply-To count exceeds limit
32: Domain is not activated
34: User account is not verified
35: Invalid ListID
36: Invalid SubscriberID
37: Invalid JourneyID
38: Invalid ActionID
39: Failed to resolve list recipients (returned with HTTP 502, not HTTP 200)
429: Email send rate limit exceeded

Error code 39 is returned with HTTP 502

Unlike every other error code on this endpoint — which is returned inside an Errors array with an HTTP 200 status — code 39 is returned with HTTP status 502. It means the recipient set behind TargetListID could not be resolved (the internal recipient-resolution request failed, timed out, returned a non-2xx status, returned no usable query, or the recipient query itself failed to execute).

This condition is not transient: retrying the same request with the same unresolvable recipient set fails identically. Treat it as a permanent failure for that payload and investigate the list/segment rather than retrying in a loop.

Previously this same condition returned HTTP 200 with {"MessageID": []} — a silent drop in which the caller was told the send had succeeded while no email was queued. Callers that only checked for a 200 status should now also handle 502.

:::

A TargetListID send delivers to the first 250 recipients unless full-list mode is enabled

By default a list send resolves and delivers to at most the first 250 recipients of the list (ordered by email address) and returns HTTP 200 with one MessageID per delivered recipient — a list larger than 250 is silently truncated, and the only hint is that the MessageID array length is 250. To deliver to the entire list, set EMAILGATEWAY_SENDEMAIL_FULL_LIST=true in the install's configuration; the send then paginates through the whole list (ordered by subscriber ID so no recipient is skipped or duplicated) and returns a MessageID for every recipient. This is an install-wide, opt-in setting — not a per-request parameter — because enabling it increases how many emails (and delivery credits) each call consumes. See the configuration guide.

Send Email via SMTP Relay

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)
  • This is an internal endpoint used by the SMTP relay server

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.smtprelay
AdminAPIKeyStringYesAdmin API key for authentication
SMTPUsernameStringYesSMTP username for authentication
SMTPPasswordStringYesSMTP password for authentication
UUIDStringNoUnique message identifier
MailFromStringNoMAIL FROM envelope address
RcptToStringNoRCPT TO envelope addresses (comma-separated)
SubjectStringNoEmail subject line
RawEmailStringNoRaw email content (RFC 822 format)
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.smtprelay",
    "AdminAPIKey": "your-admin-key",
    "SMTPUsername": "smtp_user_xxx",
    "SMTPPassword": "smtp_password"
  }'
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.smtprelay",
    "AdminAPIKey": "your-admin-key",
    "SMTPUsername": "smtp_user_xxx",
    "SMTPPassword": "smtp_password",
    "UUID": "550e8400-e29b-41d4-a716-446655440000",
    "MailFrom": "sender@example.com",
    "RcptTo": "recipient@example.com",
    "Subject": "Test Email",
    "RawEmail": "From: sender@example.com..."
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "SMTPUsername": "smtp_user_xxx",
  "SMTPPassword": "smtp_password"
}
json
{
  "Success": true,
  "ErrorCode": 0,
  "SMTPResponse": "250 2.0.0 Ok: queued",
  "SMTPUsername": "smtp_user_xxx",
  "SMTPPassword": "smtp_password"
}
json
{
  "Success": false,
  "SMTPResponse": "500 5.0.0 AUTH ERROR",
  "ErrorCode": 3
}
txt
0: Success
1: Missing SMTPUsername
2: Missing SMTPPassword
3: Invalid SMTP credentials
4: Invalid sender domain
5: Sender domain is not active
6: Invalid user account
7: User is not trusted or not enabled
8: Phone verification required but not completed (auth) / Recipient email address is suppressed (send)
9: Maximum number of TO addresses exceeded
10: Maximum number of CC addresses exceeded
11: Maximum number of BCC addresses exceeded
12: Email send rate limit reached
13: Email delivery credit limit reached

Rejection reasons on the send path

Error codes 9-13 are returned on the send (mail-fetch) path with Success: false and the corresponding SMTPResponse string. Previously these rejections returned an empty ErrorCode: [], so a caller could not tell why a relay attempt had been refused — the code now identifies the specific limit that was hit.

Phone verification gate

On the auth path, error code 8 is returned only when PHONE_VERIFICATION_REQUIRED_TO_SEND_EG_EMAILS is true and the owner user's PhoneVerified is not 1. When the flag is false (default), the phone-verification check is skipped — matching the Send Email via API endpoint.

:::

Get All Sender Domains for PowerMTA Configuration

POST /api/v1/sender.domains

API Usage Notes

  • Authentication required: Admin API Key
  • Rate limit: 100 requests per 60 seconds
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringNoAPI command: sender.domains (only required for legacy endpoint)
AdminAPIKeyStringNoAdmin API key for authentication (only required for legacy endpoint)
DomainsResponseFormatStringNoResponse format: json, powermta-bounce-domains, powermta-relay-domains (default: json)
bash
curl -X POST https://example.com/api/v1/sender.domains \
  -H "Content-Type: application/json" \
  -d '{
    "DomainsResponseFormat": "json"
  }'
json
{
  "Success": true,
  "Domains": [
    "example.com",
    "another.com",
    "third.com"
  ]
}
json
{
  "Success": false,
  "ErrorCode": 1,
  "ErrorText": "Invalid DomainsResponseFormat"
}
txt
0: Success
1: Invalid DomainsResponseFormat (must be json, powermta-bounce-domains, or powermta-relay-domains)

Check Inbound Relay Domain Authorization

GET /api/v1/inbound-relay-domain-check

API Usage Notes

  • Authentication required: Bearer token (Admin API Key)
  • This endpoint is designed for MX server integration to validate relay domains
  • Returns HTTP status codes to indicate relay authorization
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
DomainStringYesDomain name to check for relay authorization (can be full email address)
bash
curl -X GET "https://example.com/api/v1/inbound-relay-domain-check?domain=example.com" \
  -H "Authorization: Bearer your-admin-api-key"
json
{
  "Success": true,
  "RelayDomain": "example.com"
}
json
{
  "Errors": [
    {
      "Code": 1,
      "Message": "Authentication failed. Invalid admin API key."
    }
  ]
}
json
{}
txt
200: Relay allowed - domain is authorized for relay
403: Temporary error - authentication failed
500: Relay access denied - domain is not authorized
txt
1: Authentication failed - Invalid admin API key

Get Recipient Domain Statistics

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Returns delivery statistics for a single recipient domain, broken down by queue status: summary counts with queue-latency statistics, and a delivery-speed histogram. Pairs with emailgateway.listrecipientdomains, which returns the leaderboard these statistics drill into.

Changed in v5.9.5: results are now scoped to the sender domain

DomainID was previously used only to verify ownership and was then discarded, so the statistics covered all of the account's sender domains for the given recipient domain, while the leaderboard above them was scoped to one sender domain. The two disagreed on totals. DomainID is now applied as a filter, so both are scoped identically and reconcile.

The response shape is unchanged. Accounts with a single sender domain see no difference. Accounts with more than one sender domain will see lower numbers here than before, because the response no longer aggregates across their other sender domains.

Reading SummaryStats and DeliverySpeedHistogram: both are keyed dynamically by the queue statuses actually present in the requested window. There is no fixed key set and no zero-fill. A status with no messages in the window is simply absent, and within a status, buckets with a count of zero are omitted. Do not zero-fill against a hard-coded status list; iterate whatever keys are present. In practice you will see Sent and Failed, and occasionally Sending. Note that Bounced is never a key here: it is tracked separately from queue status. Histogram buckets are always returned in the fixed order < 1s, 1-5s, 5-10s, 10-30s, 30-60s, 1-5m, 5-10m, > 10m.

Delivery seconds measure queue latency (queued to processed), and only messages that have actually been processed are included.

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.recipientdomainstats
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerYesSender domain ID. Must be owned by the caller. Scopes the results (changed in v5.9.5)
RecipientToDomainStringYesRecipient domain to filter by (e.g., gmail.com)
StartDateStringNoStart date (Y-m-d format, default: 28 days ago)
EndDateStringNoEnd date (Y-m-d format, default: today)
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.recipientdomainstats",
    "SessionID": "your-session-id",
    "DomainID": 123,
    "RecipientToDomain": "gmail.com",
    "StartDate": "2026-01-01",
    "EndDate": "2026-02-11"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "SummaryStats": [
    {
      "Status": "Sent",
      "TotalEmails": 1523,
      "AvgDeliverySeconds": 4.32,
      "MinDeliverySeconds": 0,
      "MaxDeliverySeconds": 187
    },
    {
      "Status": "Failed",
      "TotalEmails": 42,
      "AvgDeliverySeconds": 12.58,
      "MinDeliverySeconds": 1,
      "MaxDeliverySeconds": 95
    }
  ],
  "DeliverySpeedHistogram": {
    "Sent": [
      { "DeliveryBucket": "< 1s", "EmailCount": 312, "Percentage": 20.49 },
      { "DeliveryBucket": "1-5s", "EmailCount": 845, "Percentage": 55.48 },
      { "DeliveryBucket": "5-10s", "EmailCount": 200, "Percentage": 13.13 },
      { "DeliveryBucket": "10-30s", "EmailCount": 100, "Percentage": 6.57 },
      { "DeliveryBucket": "30-60s", "EmailCount": 40, "Percentage": 2.63 },
      { "DeliveryBucket": "1-5m", "EmailCount": 20, "Percentage": 1.31 },
      { "DeliveryBucket": "5-10m", "EmailCount": 4, "Percentage": 0.26 },
      { "DeliveryBucket": "> 10m", "EmailCount": 2, "Percentage": 0.13 }
    ],
    "Failed": [
      { "DeliveryBucket": "1-5s", "EmailCount": 30, "Percentage": 71.43 },
      { "DeliveryBucket": "5-10s", "EmailCount": 12, "Percentage": 28.57 }
    ]
  }
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (DomainID or RecipientToDomain)
2: Domain not found or access denied

Export Events as CSV

GET /api.php   POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)
  • Response is text/csv; charset=utf-8 (streamed), not JSON. The dispatcher's JSON wrapping is bypassed.

Streams the email-gateway event log as CSV. Accepts the same filters as emailgateway.getevents but bypasses the 100-row browse cap (hard cap at 10,000 rows). Designed to be embedded directly in a browser download link — when the user clicks "Export CSV", the browser streams the file to disk.

Data is fetched from Elasticsearch in chunks of 1,000 rows; each chunk is flushed to the client immediately, so the loop is cancelable mid-download (closing the browser tab terminates the export before the next ES round-trip).

Response headers:

  • Content-Type: text/csv; charset=utf-8
  • Content-Disposition: attachment; filename="transactional-events-YYYY-MM-DD.csv"
  • X-Octeth-Export-Total: <int> — total matching events reported by ES.
  • X-Octeth-Export-Truncated: 1 — present only when Total > 10000; the CSV body contains the first 10,000 rows.
  • X-Octeth-Export-Limit: 10000 — present only when truncated.

CSV columns: Time (ISO 8601 UTC), Event, MessageID, From, To, Subject, Domain, SMTPCode. The file begins with a UTF-8 BOM so Excel opens it with the correct encoding. Address-like cells starting with =, +, -, @, tab, or carriage return are prefixed with a single quote to neutralize spreadsheet formula injection.

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.exportevents
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerNoScope to a single sender domain. Omit (or pass 0) to export across all of the caller's gateway domains
EventStringNoFilter by event type. Possible values: same as emailgateway.geteventsaccepted, rejected, delivered, bounced, opened, clicked, unsubscribed, complained, accepted-by-oempro, accepted-by-mta, rejected-by-oempro, rejected-by-mta
MessageIDStringNoFilter by message-id (takes precedence over Query when both are supplied)
QueryStringNoFree-text search across message metadata (subject, from, to, IP, MX host, SMTP response, etc.). Ignored when MessageID is supplied
StartDateStringNoStart date (Y-m-d strict). Defaults to 30 days ago. Invalid formats fall back to the default
EndDateStringNoEnd date (Y-m-d strict). Defaults to today. Clamped to >= StartDate
bash
# Browser-friendly GET — drop this into an <a href> or window.location:
curl -OJ -G https://example.com/api.php \
  --data-urlencode 'Command=emailgateway.exportevents' \
  --data-urlencode 'SessionID=your-session-id' \
  --data-urlencode 'DomainID=123' \
  --data-urlencode 'Event=bounced' \
  --data-urlencode 'StartDate=2026-01-01' \
  --data-urlencode 'EndDate=2026-01-31'
text
Time,Event,MessageID,From,To,Subject,Domain,SMTPCode
2026-01-31T18:42:11Z,bounced,bb1a701c-dcf0-4948-81eb-62726da0d67d,sender@apex.com,"Recipient <recipient@example.com>","Welcome to Apex",example.com,550
2026-01-31T18:41:55Z,bounced,162a101e-0c78-491a-9403-94dad8a3e1ca,sender@apex.com,"recipient2@example.org",,example.org,553
...
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success (CSV streamed)
2: DomainID supplied but not owned by the caller

Regenerate the Webhook Signing Key for a Domain

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Generates a fresh per-domain HMAC-SHA256 signing key, stores it atomically against the domain row, and returns it once. Subsequent calls to emailgateway.getwebhooks for the same domain return the new key, and outgoing webhook payloads from that domain are signed with it.

Backwards compatibility: until you call this endpoint for a given domain, that domain continues to sign and verify webhooks with the legacy installation-wide secret (md5(OEMPRO_PASSWORD_SALT . LICENSE_KEY), formatted as XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX). Existing integrations that haven't rotated are entirely unaffected by the rollout of this endpoint. Once you rotate a domain, the consumer of that domain's webhooks must update its verification key — every other domain's consumer keeps working unchanged.

The new key format is a 64-character uppercase hex string (256 bits of entropy from random_bytes(32)). It's shown once in the response and stored in the SigningKey column of the sender-domain row. There's no way to retrieve it later other than via getwebhooks for the same domain.

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.regeneratesigningkey
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerYesThe sender domain to rotate. Must be owned by the caller
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.regeneratesigningkey",
    "SessionID": "your-session-id",
    "DomainID": 123
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "SigningKey": "7F94D1EF4F798E4E1F306F06DC3DF317424D60C91B2C4DC7F11D13A8D1B1C5FC"
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (DomainID)
2: Domain not found or not owned by the caller
3: Rotation failed — the UPDATE matched zero rows (typically because the domain was deleted between the ownership check and the rotation). No key was persisted; safe to retry.

List Top Recipient Domains for a Sender Domain

POST /api.php   GET /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Returns a leaderboard of the recipient domains (e.g. gmail.com, yahoo.com, outlook.com) for emails sent from a single sender domain over a date range, grouped from the email gateway's own send queue. Rows are sorted by Sent descending, then by Domain ascending.

Changed in v5.9.5: data source and counting semantics

Before v5.9.5 this endpoint read the ClickHouse event stream and counted MTA-level events. It returned an empty RecipientDomains array for every account that sends through the gateway API rather than the SMTP relay, because the recipient-domain field it grouped on and the MTA-acceptance event it counted are only ever produced by the SMTP relay ingress. It now reads the send queue, which records the recipient domain on every message on both send paths.

Two consequences for existing integrations. The response shape is unchanged, but:

  1. Sent and Failed changed meaning (see below). Sent is now acceptance by Octeth's delivery server, not by the receiving MTA, and Failed counts failed send attempts (including outright SMTP rejections) rather than MTA-reported bounces.
  2. Delivery-time fields changed quantity. They were the SMTP session duration; they are now queue latency, the same measurement emailgateway.recipientdomainstats reports. The two endpoints previously reported different quantities under the same unit while being rendered on the same page.

The endpoint now also reports retroactively: the send queue has no retention window, so historical data is available immediately with no backfill.

Counts semantics:

  • Sent = messages accepted by the Octeth delivery server for delivery (queue status Sent or Delivered). This is not confirmation that the receiving MTA accepted the message.
  • Failed = messages whose send attempt failed (queue status Failed). This covers failures on our side (sender domain, API key or SMTP configuration not found, credit or rate limits reached, message unparseable, every recipient suppressed) and messages the receiving mail server rejected outright during the SMTP conversation. What it does not include is a bounce reported back later, after the message had already been accepted for delivery. Those are recorded separately and are not part of this leaderboard.
  • Sending = messages queued but not yet in a terminal state (queue status Pending or Sending).

Delivery-time fields (AvgDeliverySec, MinDeliverySec, MaxDeliverySec) measure queue latency: the seconds between a message being queued and being processed. They are computed only over messages counted in Sent that have actually been processed, and are null when a recipient domain has no such messages in the window. A null therefore means "no delivery data yet", which is distinct from 0 ("delivered within the same second").

The Domain grouping key is returned lowercased, so Gmail.com and gmail.com are one row.

Known limitation: a single SMTP-relay message addressed to several recipients records only the first recipient's domain, while credit usage counts every recipient. Such messages are therefore under-represented on this leaderboard relative to the account's credit consumption.

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.listrecipientdomains
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerYesSender domain ID. Must be owned by the caller
StartDateStringNoStart date (Y-m-d strict). Defaults to 30 days ago. Invalid formats fall back to the default. The window is capped at 365 days: a longer range has its start trimmed forward, and the response reports the range actually queried
EndDateStringNoEnd date (Y-m-d strict). Defaults to today. Clamped to >= StartDate
LimitIntegerNoMaximum number of recipient-domain rows. Default 50, clamped to [1, 1000]
bash
curl -G https://example.com/api.php \
  --data-urlencode 'Command=emailgateway.listrecipientdomains' \
  --data-urlencode 'SessionID=your-session-id' \
  --data-urlencode 'DomainID=123' \
  --data-urlencode 'StartDate=2026-04-01' \
  --data-urlencode 'EndDate=2026-04-30' \
  --data-urlencode 'Limit=20'
json
{
  "Success": true,
  "ErrorCode": 0,
  "StartDate": "2026-04-01 00:00:00",
  "EndDate": "2026-04-30 23:59:59",
  "Limit": 20,
  "RecipientDomains": [
    {
      "Domain": "gmail.com",
      "Sent": 28126,
      "Failed": 35,
      "Sending": 0,
      "AvgDeliverySec": 0.26,
      "MinDeliverySec": 0,
      "MaxDeliverySec": 502
    },
    {
      "Domain": "yahoo.com",
      "Sent": 2291,
      "Failed": 4,
      "Sending": 0,
      "AvgDeliverySec": 0.06,
      "MinDeliverySec": 0,
      "MaxDeliverySec": 26
    }
  ]
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (DomainID)
2: Domain not found or not owned by the caller

Atomically Replace a Domain API Key

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: EmailGateway.ManageDomain
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Atomically rotates the API key for a sender domain. Hard-deletes every prior row for the (UserID, DomainID) slot and inserts a new Enabled key — all inside a single MySQL transaction.

Kills the race the legacy "delete then create" flow had against the uk_user_domain_status (UserID, DomainID, Status) unique constraint, which would occasionally fail with "the slot may still be reserved in the database" when the two calls overlapped. Response shape mirrors emailgateway.addapi exactly, so callers can migrate by changing the Command name only. If the domain has no existing key the DELETE is a no-op and behaviour is identical to addapi.

Trade-off — the prior key row (including its Description and CreatedAt) is removed entirely, not soft-deleted. Audit-trail callers that need history should record it externally before invoking this endpoint.

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: emailgateway.replaceapi
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DomainIDIntegerYesSender domain ID. Must be owned by the caller
DescriptionStringYesDescription for the new key (same semantics as emailgateway.addapi)
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "emailgateway.replaceapi",
    "SessionID": "your-session-id",
    "DomainID": 123,
    "Description": "Rotated 2026-05-16"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "NewAPIKeyID": 92,
  "APIKey": {
    "APIKeyID": "92",
    "UserID": "1",
    "DomainID": "123",
    "CreatedAt": "2026-05-16 04:26:31",
    "APIKey": "4D196964-B909FDD7-134A4E29-6B0E923E",
    "Description": "Rotated 2026-05-16",
    "Status": "Enabled",
    "Options": []
  }
}
json
{
  "Success": false,
  "ErrorCode": 3
}
txt
0: Success
1: Missing required parameter (Description)
2: Missing required parameter (DomainID)
3: Domain not found or not owned by the caller
4: Atomic rotate failed — the transaction rolled back; no key was persisted. Safe to retry.

Any questions? Contact us.