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.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.AddDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.adddomain |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainName | String | Yes | Domain name to add (e.g., example.com) |
| Subdomain | String | No | Custom subdomain override (e.g., outbound). Only letters, numbers, and hyphens allowed (max 32 characters). |
| TrackPrefix | String | No | Custom tracking prefix override (e.g., links). Only letters, numbers, and hyphens allowed (max 32 characters). |
| TrackMerge | String | No | Custom tracking merge character (e.g., -). |
| TrackPrefixDisabled | Integer | No | Set to 1 to disable the separate tracking subdomain. When disabled, tracking URLs use the sender (MFROM) domain instead. |
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"
}'{
"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"
}
}
}{
"Success": false,
"ErrorCode": [1]
}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 valueGet Sender Domain Details
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.getdomain |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | Yes | Sender domain ID |
curl -X POST https://example.com/api.php \
-H "Content-Type: application/json" \
-d '{
"Command": "emailgateway.getdomain",
"SessionID": "your-session-id",
"DomainID": 123
}'{
"Success": true,
"ErrorCode": 0,
"Domain": {
"DomainID": 123,
"SenderDomain": "example.com",
"Status": "Enabled",
"Options": {
"LinkTracking": 1,
"OpenTracking": 1,
"UnsubscribeLink": 0
}
}
}{
"Success": false,
"ErrorCode": 2
}0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access deniedUpdate Sender Domain Settings
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.updatedomain |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | Yes | Sender domain ID |
| LinkTracking | Integer | No | Enable link tracking (1 = enabled, 0 = disabled) |
| OpenTracking | Integer | No | Enable open tracking (1 = enabled, 0 = disabled) |
| UnsubscribeLink | Integer | No | Enable unsubscribe link (1 = enabled, 0 = disabled) |
| HostingProvider | String | No | Hosting provider name |
| Subdomain | String | No | Custom subdomain override (e.g., outbound). Only letters, numbers, and hyphens allowed (max 32 characters). Leave empty to reset to global default. |
| TrackPrefix | String | No | Custom tracking prefix override (e.g., links). Only letters, numbers, and hyphens allowed (max 32 characters). Leave empty to reset to global default. |
| TrackMerge | String | No | Custom tracking merge character (e.g., -). |
| TrackPrefixDisabled | Integer | No | Set to 1 to disable the separate tracking subdomain. When disabled, tracking URLs use the sender (MFROM) domain instead. Set to 0 to re-enable. |
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
}'{
"Success": true,
"ErrorCode": 0,
"Domain": {
"DomainID": 123,
"SenderDomain": "example.com",
"Status": "Approval Pending",
"Options": {
"LinkTracking": 1,
"OpenTracking": 1,
"CustomSubdomain": "outbound",
"TrackPrefixDisabled": true
}
},
"SubdomainChanged": true
}{
"Success": false,
"ErrorCode": 5
}0: Success
1: Missing required parameter (DomainID)
5: Domain not found or access denied
6: Invalid subdomain or track prefix valueVerify Sender Domain DNS Records
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.verifydomain |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | Yes | Sender domain ID |
curl -X POST https://example.com/api.php \
-H "Content-Type: application/json" \
-d '{
"Command": "emailgateway.verifydomain",
"SessionID": "your-session-id",
"DomainID": 123
}'{
"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]
}
}{
"Success": false,
"ErrorCode": 2
}0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access deniedGet All Sender Domains
POST/api.phpAPI Usage Notes
- Authentication required: User API Key. Admin authentication is also accepted with
Access=adminandUserID(see Admin usage below) - Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (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:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.getdomains |
| UserID | Integer | Admin only | Account to act on when calling with admin authentication and Access=admin. Ignored under user authentication (v5.9.6, #2775) |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| WithCounts | Mixed | No | When 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):
| Field | Type | Source | Definition |
|---|---|---|---|
APIKeyCount | Integer | SQL | Count of oempro_eg_api_keys rows for this domain with Status='Enabled' |
SMTPCount | Integer | SQL | Count of oempro_eg_smtp_credentials rows for this domain with Status='Enabled' |
WebhookCount | Integer | SQL | Count of oempro_eg_webhooks rows for this domain with Status='Enabled' |
Sent7d | Integer | ES | Count of accepted-by-oempro events for this domain in the last 7 days |
BounceRate7d | Float (2dp) | computed | Bounced / Sent × 100. Returns 0 when Sent7d is 0 (no divide-by-zero NaN) |
LastActivityAt | String or null | ES | MAX(logged-at) within the 7-day window as ISO 8601 UTC; null if no events |
curl -X POST https://example.com/api.php \
-H "Content-Type: application/json" \
-d '{
"Command": "emailgateway.getdomains",
"SessionID": "your-session-id"
}'curl -X POST https://example.com/api.php \
-H "Content-Type: application/json" \
-d '{
"Command": "emailgateway.getdomains",
"SessionID": "your-session-id",
"WithCounts": 1
}'{
"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}
}
]
}{
"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"
}
]
}0: SuccessDelete a Sender Domain
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.deletedomain |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | Yes | Sender domain ID to delete |
curl -X POST https://example.com/api.php \
-H "Content-Type: application/json" \
-d '{
"Command": "emailgateway.deletedomain",
"SessionID": "your-session-id",
"DomainID": 123
}'{
"Success": true,
"ErrorCode": 0
}{
"Success": false,
"ErrorCode": 2
}0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access deniedClear Domain Email Queue
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.cleardomainqueue |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | Yes | Sender domain ID |
curl -X POST https://example.com/api.php \
-H "Content-Type: application/json" \
-d '{
"Command": "emailgateway.cleardomainqueue",
"SessionID": "your-session-id",
"DomainID": 123
}'{
"Success": true,
"ErrorCode": 0
}{
"Success": false,
"ErrorCode": 2
}0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access deniedGet Domain Statistics
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.domainstats |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | Yes | Sender domain ID |
| StartDate | String | No | Start date (Y-m-d format, default: 28 days ago) |
| EndDate | String | No | End date (Y-m-d format, default: today) |
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"
}'{
"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
}
}
}{
"Success": false,
"ErrorCode": 2
}0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access deniedGet Account-Wide Statistics
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (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:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.accountstats |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| StartDate | String | No | Start date (Y-m-d format, default: 30 days ago) |
| EndDate | String | No | End date (Y-m-d format, default: today). Clamped to >= StartDate |
| DomainIDs | String | No | Comma-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 |
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"
}'{
"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
}
]
}{
"Success": false,
"ErrorCode": 99998
}0: Success
99998: Authentication failure or session expiredCreate API Key for Domain
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.addapi |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| Description | String | Yes | Description for the API key |
| DomainID | Integer | Yes | Sender domain ID |
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
}'{
"Success": true,
"ErrorCode": 0,
"NewAPIKeyID": 456,
"APIKey": {
"APIKeyID": 456,
"APIKey": "eg_live_xxxxxxxxxxxx",
"Description": "Production API Key",
"DomainID": 123
}
}{
"Success": false,
"ErrorCode": [1, 2]
}0: Success
1: Missing required parameter (Description)
2: Missing required parameter (DomainID)
3: Domain not found or access deniedGet Domain API Keys
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.getapis |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | Yes | Sender domain ID |
curl -X POST https://example.com/api.php \
-H "Content-Type: application/json" \
-d '{
"Command": "emailgateway.getapis",
"SessionID": "your-session-id",
"DomainID": 123
}'{
"Success": true,
"ErrorCode": 0,
"APIKeys": [
{
"APIKeyID": 456,
"APIKey": "eg_live_xxxxxxxxxxxx",
"Description": "Production API Key",
"DomainID": 123
}
]
}{
"Success": false,
"ErrorCode": 2
}0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access deniedDelete Domain API Key
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.deleteapi |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| APIKeyID | Integer | Yes | API Key ID to delete |
curl -X POST https://example.com/api.php \
-H "Content-Type: application/json" \
-d '{
"Command": "emailgateway.deleteapi",
"SessionID": "your-session-id",
"APIKeyID": 456
}'{
"Success": true,
"ErrorCode": 0
}{
"Success": false,
"ErrorCode": 2
}0: Success
1: Missing required parameter (APIKeyID)
2: API Key not found or access deniedCreate SMTP Credentials for Domain
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.addsmtp |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | Yes | Sender domain ID |
curl -X POST https://example.com/api.php \
-H "Content-Type: application/json" \
-d '{
"Command": "emailgateway.addsmtp",
"SessionID": "your-session-id",
"DomainID": 123
}'{
"Success": true,
"ErrorCode": 0,
"NewSMTPID": 789,
"SMTP": {
"SMTPID": 789,
"SMTPUsername": "smtp_user_xxx",
"SMTPPassword": "generated_password",
"SMTPHost": "smtp.example.com",
"SMTPPorts": [25, 587, 2525]
}
}{
"Success": false,
"ErrorCode": 2
}0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access deniedGet Domain SMTP Credentials
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.getsmtps |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | Yes | Sender domain ID |
curl -X POST https://example.com/api.php \
-H "Content-Type: application/json" \
-d '{
"Command": "emailgateway.getsmtps",
"SessionID": "your-session-id",
"DomainID": 123
}'{
"Success": true,
"ErrorCode": 0,
"SMTPs": [
{
"SMTPID": 789,
"SMTPUsername": "smtp_user_xxx",
"SMTPHost": "smtp.example.com",
"SMTPPorts": [25, 587, 2525]
}
]
}{
"Success": false,
"ErrorCode": 2
}0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access deniedDelete SMTP Credentials
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.deletesmtp |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| SMTPID | Integer | Yes | SMTP credentials ID to delete |
curl -X POST https://example.com/api.php \
-H "Content-Type: application/json" \
-d '{
"Command": "emailgateway.deletesmtp",
"SessionID": "your-session-id",
"SMTPID": 789
}'{
"Success": true,
"ErrorCode": 0
}{
"Success": false,
"ErrorCode": 2
}0: Success
1: Missing required parameter (SMTPID)
2: SMTP credentials not found or access deniedReset SMTP Password
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.resetsmtppassword |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| SMTPID | Integer | Yes | SMTP credentials ID |
curl -X POST https://example.com/api.php \
-H "Content-Type: application/json" \
-d '{
"Command": "emailgateway.resetsmtppassword",
"SessionID": "your-session-id",
"SMTPID": 789
}'{
"Success": true,
"ErrorCode": 0,
"SMTP": {
"SMTPID": 789,
"SMTPUsername": "smtp_user_xxx",
"SMTPPassword": "new_generated_password"
}
}{
"Success": false,
"ErrorCode": 2
}0: Success
1: Missing required parameter (SMTPID)
2: SMTP credentials not found or access deniedCreate Webhook for Domain
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (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:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.addwebhook |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | Yes | Sender domain ID |
| Event | String | Yes | Event type. Possible values: delivery, bounce, open, click, unsubscribe, complaint |
| WebhookURL | String | Yes | Webhook URL to receive event notifications. Must be an http/https URL whose host is not localhost / 127.0.0.1 / ::1 / *.local |
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"
}'{
"Success": true,
"ErrorCode": 0,
"NewWebhookID": 999
}{
"Success": false,
"ErrorCode": 6,
"ErrorMessage": "Webhook URL must be a valid HTTP or HTTPS URL."
}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.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.getwebhooks |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | Yes | Sender domain ID |
curl -X POST https://example.com/api.php \
-H "Content-Type: application/json" \
-d '{
"Command": "emailgateway.getwebhooks",
"SessionID": "your-session-id",
"DomainID": 123
}'{
"Success": true,
"ErrorCode": 0,
"SigningKey": "whsec_xxxxxxxxxxxx",
"Webhooks": [
{
"WebhookID": 999,
"Event": "delivery",
"WebhookURL": "https://example.com/webhooks/email-events"
}
]
}{
"Success": false,
"ErrorCode": 2
}0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access deniedDelete Domain Webhook
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.deletewebhook |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| WebhookID | Integer | Yes | Webhook ID to delete |
| DomainID | Integer | Yes | Sender domain ID |
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
}'{
"Success": true,
"ErrorCode": 0
}{
"Success": false,
"ErrorCode": 4
}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 deniedCreate Webhook (Public API)
POST/api/v1/webhooksAPI Usage Notes
- Authentication required: Bearer token
- Rate limit: 100 requests per 60 seconds
- Legacy endpoint access via
/api.phpis 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.)
| Parameter | Type | Required | Description |
|---|---|---|---|
| SenderAPIKey | String | Yes | API key for the sender domain (Bearer token) |
| Event | String | Yes | Event type: delivered, bounced, opened, clicked, unsubscribed, complained |
| WebhookURL | String | Yes | Webhook URL to receive event notifications. Must be an http/https URL whose host is not localhost / 127.0.0.1 / ::1 / *.local |
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"
}'{
"NewWebhookID": 999
}{
"Errors": [
{"Code": 3, "Message": "Invalid Event value"}
]
}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 requestsGet Webhooks (Public API)
GET/api/v1/webhooksAPI Usage Notes
- Authentication required: Bearer token
- Rate limit: 100 requests per 60 seconds
- Legacy endpoint access via
/api.phpis also supported
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| SenderAPIKey | String | Yes | API key for the sender domain (Bearer token) |
curl -X GET https://example.com/api/v1/webhooks \
-H "Authorization: Bearer eg_live_xxxxxxxxxxxx"{
"Webhooks": [
{
"WebhookID": 999,
"Event": "delivered",
"WebhookURL": "https://example.com/webhooks/email-events"
}
]
}{
"Errors": [
{"Code": 13, "Message": "Invalid SenderAPIKey"}
]
}2: Invalid user account
13: Invalid SenderAPIKey
429: Too many requestsDelete Webhook (Public API)
DELETE/api/v1/webhooksAPI Usage Notes
- Authentication required: Bearer token
- Rate limit: 100 requests per 60 seconds
- Legacy endpoint access via
/api.phpis also supported
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| SenderAPIKey | String | Yes | API key for the sender domain (Bearer token) |
| WebhookID | Integer | Yes | Webhook ID to delete |
curl -X DELETE https://example.com/api/v1/webhooks \
-H "Authorization: Bearer eg_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"WebhookID": 999
}'{}{
"Errors": [
{"Code": 4, "Message": "Invalid WebhookID"}
]
}1: WebhookID is missing
2: Invalid user account
4: Invalid WebhookID
13: Invalid SenderAPIKey
429: Too many requestsGet Email Events
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (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:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.getevents |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | No | Sender domain ID to scope the query to. Omit to return events across all of the authenticated user's sender domains. |
| StartFrom | Integer | Yes | Starting record index for pagination |
| RetrieveCount | Integer | Yes | Number of records to retrieve (max 100) |
| StartDate | String | No | Start date filter (Y-m-d format) |
| EndDate | String | No | End date filter (Y-m-d format) |
| Event | String | No | Filter by event type (delivery, bounce, open, click, etc.) |
| Query | String | No | Search query string |
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"
}'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"
}'{
"Success": true,
"ErrorCode": 0,
"TotalRecords": 150,
"Events": [
{
"event": "delivery",
"timestamp": 1640000000,
"message": {
"headers": {
"from": "sender@example.com",
"to": "recipient@example.com",
"subject": "Test Email"
}
}
}
]
}{
"Success": false,
"ErrorCode": 2
}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/eventsAPI Usage Notes
- Authentication required: Bearer token
- Rate limit: 100 requests per 60 seconds
- Legacy endpoint access via
/api.phpis also supported
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| SenderAPIKey | String | Yes | API key for the sender domain (Bearer token) |
| StartFrom | Integer | No | Starting record index for pagination (default: 0) |
| RetrieveCount | Integer | No | Number of records to retrieve (default: 5, max 100) |
| StartDate | Integer | No | Start date filter (Unix timestamp) |
| EndDate | Integer | No | End date filter (Unix timestamp) |
| Event | String | No | Filter by event type |
| MessageID | String | No | Filter by message ID |
curl -X GET https://example.com/api/v1/events \
-H "Authorization: Bearer eg_live_xxxxxxxxxxxx" \
-d '{
"StartFrom": 0,
"RetrieveCount": 50,
"Event": "delivery"
}'{
"TotalRecords": 150,
"Events": [
{
"Event": "delivery",
"LoggedAt": 1640000000,
"Message": {
"Headers": {
"From": "sender@example.com",
"To": "recipient@example.com",
"Subject": "Test Email"
}
}
}
]
}{
"Errors": [
{"Code": 13, "Message": "Invalid SenderAPIKey"}
]
}1: Missing SenderAPIKey
2: Invalid sender domain
4: Missing StartFrom
5: Missing RetrieveCount
13: Invalid SenderAPIKey
429: Too many requestsGet Aggregated Event Statistics
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.aggrevents |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | Yes | Sender domain ID |
| StartDate | String | No | Start date filter (Y-m-d format) |
| EndDate | String | No | End date filter (Y-m-d format) |
| AggregatedField | String | No | Field to aggregate by |
| AggregateSize | Integer | No | Number of aggregation buckets |
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"
}'{
"Success": true,
"ErrorCode": 0,
"AggBuckets": [
{
"key": "delivery",
"doc_count": 1000
},
{
"key": "bounce",
"doc_count": 50
}
]
}{
"Success": false,
"ErrorCode": 2
}0: Success
1: Missing required parameter (DomainID)
2: Domain not found or access deniedGet Daily Event Time-Series
POST/api.phpReturns 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.phponly (no v1 REST alias configured)
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.dailyseries |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | No | Sender domain ID. When omitted, the series spans all of the caller's domains |
| StartDate | String | No | Start date filter (Y-m-d format). Defaults to 30 days before the end date |
| EndDate | String | No | End date filter (Y-m-d format). Defaults to today |
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"
}'{
"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
}
}
}{
"Success": false,
"ErrorCode": 2
}0: Success
2: Domain not found or access denied (only when DomainID is supplied)Send Email via API
POST/api/v1/emailAPI Usage Notes
- Authentication required: Bearer token
- Rate limit: 100 requests per 60 seconds
- Legacy endpoint access via
/api.phpis also supported
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| SenderAPIKey | String | Yes | API key for the sender domain (Bearer token) |
| Subject | String | Yes | Email subject line |
| ContentType | String | Yes | Content type: html or plain |
| HTMLContent | String | Conditional | HTML email content (required if ContentType is html) |
| PlainContent | String | Conditional | Plain text email content (required if ContentType is plain) |
| From | Object | Yes | Sender information: |
| To | Array | Conditional | Array of recipients: [{name, email}] (required unless TargetListID is set) |
| CC | Array | No | Array of CC recipients: [{name, email}] |
| BCC | Array | No | Array of BCC recipients: [{name, email}] |
| ReplyTo | Array | No | Array of reply-to addresses: [{name, email}] |
| Tags | Array | No | Array of custom tags for tracking |
| Headers | Object | No | Custom email headers as key-value pairs |
| TrackLinks | String | No | Enable link tracking: true or false |
| TrackOpens | String | No | Enable open tracking: true or false |
| SendAt | Integer | No | Schedule send time (Unix timestamp) |
| Attachments | Array | No | Array of attachments: [{filename, content, type, disposition, contentid}] |
| TemplateID | Integer | No | Email template ID to use |
| TargetListID | Integer | No | Send 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) |
| ListID | Integer | No | List ID for subscriber context |
| SubscriberID | Integer | No | Subscriber ID for personalization |
| JourneyID | Integer | No | Journey ID for tracking |
| ActionID | Integer | No | Journey action ID for tracking |
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"
}
]
}'{
"MessageID": "550e8400-e29b-41d4-a716-446655440000"
}{
"Errors": [
{"Code": 8, "Message": "Missing Subject"}
]
}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 exceededError 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.phpAPI Usage Notes
- Authentication required: Admin API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (no v1 REST alias configured) - This is an internal endpoint used by the SMTP relay server
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.smtprelay |
| AdminAPIKey | String | Yes | Admin API key for authentication |
| SMTPUsername | String | Yes | SMTP username for authentication |
| SMTPPassword | String | Yes | SMTP password for authentication |
| UUID | String | No | Unique message identifier |
| MailFrom | String | No | MAIL FROM envelope address |
| RcptTo | String | No | RCPT TO envelope addresses (comma-separated) |
| Subject | String | No | Email subject line |
| RawEmail | String | No | Raw email content (RFC 822 format) |
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"
}'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..."
}'{
"Success": true,
"ErrorCode": 0,
"SMTPUsername": "smtp_user_xxx",
"SMTPPassword": "smtp_password"
}{
"Success": true,
"ErrorCode": 0,
"SMTPResponse": "250 2.0.0 Ok: queued",
"SMTPUsername": "smtp_user_xxx",
"SMTPPassword": "smtp_password"
}{
"Success": false,
"SMTPResponse": "500 5.0.0 AUTH ERROR",
"ErrorCode": 3
}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 reachedRejection 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.domainsAPI Usage Notes
- Authentication required: Admin API Key
- Rate limit: 100 requests per 60 seconds
- Legacy endpoint access via
/api.phpis also supported
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | No | API command: sender.domains (only required for legacy endpoint) |
| AdminAPIKey | String | No | Admin API key for authentication (only required for legacy endpoint) |
| DomainsResponseFormat | String | No | Response format: json, powermta-bounce-domains, powermta-relay-domains (default: json) |
curl -X POST https://example.com/api/v1/sender.domains \
-H "Content-Type: application/json" \
-d '{
"DomainsResponseFormat": "json"
}'{
"Success": true,
"Domains": [
"example.com",
"another.com",
"third.com"
]
}{
"Success": false,
"ErrorCode": 1,
"ErrorText": "Invalid DomainsResponseFormat"
}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-checkAPI 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.phpis also supported
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Domain | String | Yes | Domain name to check for relay authorization (can be full email address) |
curl -X GET "https://example.com/api/v1/inbound-relay-domain-check?domain=example.com" \
-H "Authorization: Bearer your-admin-api-key"{
"Success": true,
"RelayDomain": "example.com"
}{
"Errors": [
{
"Code": 1,
"Message": "Authentication failed. Invalid admin API key."
}
]
}{}200: Relay allowed - domain is authorized for relay
403: Temporary error - authentication failed
500: Relay access denied - domain is not authorized1: Authentication failed - Invalid admin API keyGet Recipient Domain Statistics
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (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:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.recipientdomainstats |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | Yes | Sender domain ID. Must be owned by the caller. Scopes the results (changed in v5.9.5) |
| RecipientToDomain | String | Yes | Recipient domain to filter by (e.g., gmail.com) |
| StartDate | String | No | Start date (Y-m-d format, default: 28 days ago) |
| EndDate | String | No | End date (Y-m-d format, default: today) |
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"
}'{
"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 }
]
}
}{
"Success": false,
"ErrorCode": 2
}0: Success
1: Missing required parameter (DomainID or RecipientToDomain)
2: Domain not found or access deniedExport Events as CSV
GET/api.php POST /api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (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-8Content-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 whenTotal > 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:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.exportevents |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | No | Scope to a single sender domain. Omit (or pass 0) to export across all of the caller's gateway domains |
| Event | String | No | Filter by event type. Possible values: same as emailgateway.getevents — accepted, rejected, delivered, bounced, opened, clicked, unsubscribed, complained, accepted-by-oempro, accepted-by-mta, rejected-by-oempro, rejected-by-mta |
| MessageID | String | No | Filter by message-id (takes precedence over Query when both are supplied) |
| Query | String | No | Free-text search across message metadata (subject, from, to, IP, MX host, SMTP response, etc.). Ignored when MessageID is supplied |
| StartDate | String | No | Start date (Y-m-d strict). Defaults to 30 days ago. Invalid formats fall back to the default |
| EndDate | String | No | End date (Y-m-d strict). Defaults to today. Clamped to >= StartDate |
# 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'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
...{
"Success": false,
"ErrorCode": 2
}0: Success (CSV streamed)
2: DomainID supplied but not owned by the callerRegenerate the Webhook Signing Key for a Domain
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (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:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.regeneratesigningkey |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | Yes | The sender domain to rotate. Must be owned by the caller |
curl -X POST https://example.com/api.php \
-H "Content-Type: application/json" \
-d '{
"Command": "emailgateway.regeneratesigningkey",
"SessionID": "your-session-id",
"DomainID": 123
}'{
"Success": true,
"ErrorCode": 0,
"SigningKey": "7F94D1EF4F798E4E1F306F06DC3DF317424D60C91B2C4DC7F11D13A8D1B1C5FC"
}{
"Success": false,
"ErrorCode": 2
}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.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (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:
SentandFailedchanged meaning (see below).Sentis now acceptance by Octeth's delivery server, not by the receiving MTA, andFailedcounts failed send attempts (including outright SMTP rejections) rather than MTA-reported bounces.- Delivery-time fields changed quantity. They were the SMTP session duration; they are now queue latency, the same measurement
emailgateway.recipientdomainstatsreports. 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
SentorDelivered). 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
PendingorSending).
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:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.listrecipientdomains |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | Yes | Sender domain ID. Must be owned by the caller |
| StartDate | String | No | Start 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 |
| EndDate | String | No | End date (Y-m-d strict). Defaults to today. Clamped to >= StartDate |
| Limit | Integer | No | Maximum number of recipient-domain rows. Default 50, clamped to [1, 1000] |
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'{
"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
}
]
}{
"Success": false,
"ErrorCode": 2
}0: Success
1: Missing required parameter (DomainID)
2: Domain not found or not owned by the callerAtomically Replace a Domain API Key
POST/api.phpAPI Usage Notes
- Authentication required: User API Key
- Required permissions:
EmailGateway.ManageDomain - Legacy endpoint access via
/api.phponly (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:
| Parameter | Type | Required | Description |
|---|---|---|---|
| Command | String | Yes | API command: emailgateway.replaceapi |
| SessionID | String | No | Session ID obtained from login |
| APIKey | String | No | API key for authentication |
| DomainID | Integer | Yes | Sender domain ID. Must be owned by the caller |
| Description | String | Yes | Description for the new key (same semantics as emailgateway.addapi) |
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"
}'{
"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": []
}
}{
"Success": false,
"ErrorCode": 3
}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.
