GreenArrow Email Software Documentation

Mailgun-Compatible Domains API

GreenArrow CloudMTA

This feature is only available on GreenArrow CloudMTA.

Contact us

Contact us if you need support for a field or an API that is not listed here.

Overview

The Mailgun-compatible Domains API lets software that was written for Mailgun’s domains API manage GreenArrow Sending Domains. It uses the Sending Domains API to manage domains and the Check DNS API to verify them. A domain that you create here is an ordinary Sending Domain, and a Sending Domain that you create in another way is visible here.

These endpoints are available:

Endpoint Purpose
GET /v4/domains Get a list of domains
GET /v4/domains/{name} Get a single domain
POST /v4/domains Create a domain
PUT /v4/domains/{name} Update a domain
PUT /v4/domains/{name}/verify Verify the DNS records of a domain
DELETE /v3/domains/{name} Delete a domain
DELETE /v3/domains/{name}/pool/ip_pool Change or remove the IP pool of a domain

Send each request to the hostname of the Mailgun compatibility API of your GreenArrow CloudMTA. The examples on this page use mg.api.example.com.

Read Differences from Mailgun before you move an integration from Mailgun. Some requests that Mailgun accepts are refused here.

To send GreenArrow events to software that receives Mailgun webhooks, see Mailgun-Compatible Webhooks.

Authentication

Authenticate with HTTP Basic authentication. The username is api, and the password is a GreenArrow API key.

curl -s --user "api:$API_KEY" "https://mg.api.example.com/v4/domains"

A request with no credentials or with a username other than api gets a 401 response. A request with a key that is not valid also gets a 401 response, unless GreenArrow refuses the request for another reason first: a request that is not valid can get its 400 or 404 response before the key is checked. Do not depend on the body of a 401 response; it can change in a later release.

Requests and Responses

Request format

  • The create and update endpoints take their fields in a multipart/form-data body (curl -F), as Mailgun documents. A body of another type, including application/x-www-form-urlencoded, is refused with 400. This is also true when the request has no fields.
  • The other endpoints take their parameters in the query string. They do not read a request body.
  • A field of the body that is sent in the query string is refused with 400.
  • A create or update body may not be larger than 1 MiB. A body that is larger than 32 MiB can get a 502 with an HTML body.
  • A field or parameter listed for an endpoint may be given only once.
  • A boolean field accepts true, yes or 1, and false, no or 0, in any letter case.
  • A field or a parameter that this page does not list is ignored.

Domain names

The {name} of a path is the domain name. Its letter case does not matter. A name that has only digits is never found (404).

Errors

An error response has a JSON body, except the plain-text 404 for a path that is not a path of the Mailgun API (see the table below), and the 502 for a body that is larger than 32 MiB. Most error bodies have one field, message:

{"message": "Domain not found"}

A 401 for a request that has no credentials that can be read has another body, as on Mailgun:

{"Error": "unauthorized"}

Status Meaning
400 The request is not valid, or it asks for something that GreenArrow does not support. When the problem is one field, the message usually names it.
401 The credentials are missing or not valid.
404 No domain has this name, the path is not a path of the Mailgun API, or the path does not support the HTTP method of the request. As on Mailgun, a path that is not a path of the Mailgun API gets the plain-text body 404 page not found, and a wrong method gets {"error": "not found"}.
429 The domain was verified less than one minute ago. See Verify a Domain.
500 GreenArrow could not handle the request. The message is GreenArrow backend error. Contact GreenArrow support.
501 The path is a path of the Mailgun API that GreenArrow does not implement.
502 The request body is larger than 32 MiB.

When GreenArrow and Mailgun refuse a request for the same reason, the message is Mailgun’s. Other messages are GreenArrow’s own, for example a message about a feature that GreenArrow does not support. Use the status code to handle an error. Do not depend on the text of a message.

The Domain Object

Each endpoint that returns a domain returns it in this form.

id

string

The identifier of the domain: 24 hexadecimal characters. See Identifiers.

name

string

The domain name.

created_at

string

The time when the domain was created, in GMT, for example Mon, 14 Sep 2026 09:12:33 GMT.

state

string

active, unverified or inactive. See Domain States.

is_disabled

boolean

true if sending is disabled for the domain (the sending_enabled attribute of the Sending Domain is false).

tracking_host

string

The hostname of the click and open tracking links of the domain.

web_prefix

string

The first label of tracking_host. This is the tracking_subdomain of the Sending Domain. For a Sending Domain that uses a Shared Tracking Domain, it is the first label of that domain’s tracking hostname.

message_ttl

integer

The time that GreenArrow keeps a message in its queue, in seconds. This is a setting of the GreenArrow installation, not of the domain. It is the queue_lifetime of ip_address * and domain *, which is 172800 (48 hours) by default. A VirtualMTA or a recipient domain can have another queue lifetime.

type

string

Always custom.

use_automatic_sender_security

boolean

Always true. The DKIM records of a domain that this API creates are CNAME records that point to GreenArrow.

web_scheme

string

Always https.

spam_action

string

Always disabled. GreenArrow does not filter incoming mail for these domains.

require_tls

boolean

Always false.

skip_verification

boolean

Always false.

wildcard

boolean

Always false.

webhooks_redact_pii

boolean

Always false.

encrypt_incoming_message

boolean

Always false.

smtp_login

string

Always an empty string.

subaccount_id

string

Always an empty string.

archive_to

null

Always null.

disabled

null

Always null.

Mailgun’s dkim_host, mailfrom_host and smtp_password fields are not returned.

Domain States

GreenArrow derives state from three attributes of the Sending Domain.

sending_enabled dns_verified dns_regression state is_disabled state filter value
false any any inactive true disabled
true false any unverified false unverified
true true false active false active
true true true unverified false unverified
  • A domain whose DNS records were verified and later became wrong (a regression) reports unverified, not active.
  • A disabled domain reports the state inactive, but the state filter of the list selects it with the value disabled.

DNS Records

The endpoints that return one domain also return sending_dns_records: the DNS records that you must create for the domain. The list endpoint does not return them.

sending_dns_records

array of hashes


record_type

string

The type of the DNS record, for example CNAME or TXT.

name

string

The hostname of the record.

value

string

The value that the record must have.

priority

string

The priority of an MX record. Other records do not have this field.

valid

string

The result of the last DNS check of this record: valid if the check accepted the record, invalid if it did not, and unknown if the record was not checked yet.

cached

array of strings

What the last DNS check found. For a record that is valid, this is its value. For a record that is not, it has the conflicting values that the check found at this hostname, and it is empty when the check found none.

is_active

boolean

false for a DKIM record whose key does not sign email, and true for each other record. GreenArrow signs the email of a domain with one DKIM key. The key of the first DKIM selector starts to sign when the domain is first verified. Until then, each DKIM record is false, as on Mailgun.

receiving_dns_records

array

Always empty.

The tracking record, when present, comes first, followed by the DKIM records. A Sending Domain that uses a Shared Tracking Domain has no tracking record of its own in this list.

Get a List of Domains

GET /v4/domains

Parameters

limit

integer

/

optional

The maximum number of domains to return, from 1 to 1000. The default is 100. A value of 0 gives the default.

skip

integer

/

optional

The number of domains to skip, 0 or more. The default is 0.

state

string

/

optional

Return only the domains in this state: active, unverified or disabled. See Domain States.

sort

string

/

optional

name or name:asc to sort by domain name, and name:desc for the reverse order. Without this parameter, the newest domain is first.

search

string

/

optional

Return only the domains whose name contains this text. The letter case does not matter.

authority

string

/

optional

Not supported. A value that is not blank is refused with 400.

include_subaccounts

string

/

optional

Ignored. GreenArrow has no subaccounts.

Response

total_count

integer

The number of domains that match the request, on all pages.

items

array of hashes

The domains of this page. Each one is a domain object.

Example

curl -s --user "api:$API_KEY" \
  "https://mg.api.example.com/v4/domains?limit=100&skip=0&state=active&sort=name:asc"

{
  "total_count": 1,
  "items": [
    {
      "archive_to": null,
      "created_at": "Mon, 14 Sep 2026 09:12:33 GMT",
      "encrypt_incoming_message": false,
      "id": "387cb321000000000000002a",
      "is_disabled": false,
      "message_ttl": 172800,
      "name": "mail.example.com",
      "require_tls": false,
      "skip_verification": false,
      "smtp_login": "",
      "spam_action": "disabled",
      "state": "active",
      "subaccount_id": "",
      "tracking_host": "trk.mail.example.com",
      "type": "custom",
      "use_automatic_sender_security": true,
      "webhooks_redact_pii": false,
      "web_prefix": "trk",
      "web_scheme": "https",
      "wildcard": false,
      "disabled": null
    }
  ]
}

Get a Single Domain

GET /v4/domains/{name}

The response has the domain (domain object), its sending_dns_records and an empty receiving_dns_records (see DNS Records).

The DNS results in this response are the results of the last DNS check that GreenArrow made. This request does not check DNS. GreenArrow checks DNS when the domain is created and then every 15 minutes. Use Verify a Domain to check at once.

Example

curl -s --user "api:$API_KEY" "https://mg.api.example.com/v4/domains/mail.example.com"

{
  "domain": {
    "archive_to": null,
    "created_at": "Mon, 14 Sep 2026 09:12:33 GMT",
    "encrypt_incoming_message": false,
    "id": "387cb321000000000000002a",
    "is_disabled": false,
    "message_ttl": 172800,
    "name": "mail.example.com",
    "require_tls": false,
    "skip_verification": false,
    "smtp_login": "",
    "spam_action": "disabled",
    "state": "active",
    "subaccount_id": "",
    "tracking_host": "trk.mail.example.com",
    "type": "custom",
    "use_automatic_sender_security": true,
    "webhooks_redact_pii": false,
    "web_prefix": "trk",
    "web_scheme": "https",
    "wildcard": false,
    "disabled": null
  },
  "sending_dns_records": [
    {
      "record_type": "CNAME",
      "name": "trk.mail.example.com",
      "value": "trk.abc123def456.dnsforemail.com",
      "valid": "valid",
      "is_active": true,
      "cached": ["trk.abc123def456.dnsforemail.com"]
    },
    {
      "record_type": "CNAME",
      "name": "ga._domainkey.mail.example.com",
      "value": "ga.abc123def456.dnsforemail.com",
      "valid": "valid",
      "is_active": true,
      "cached": ["ga.abc123def456.dnsforemail.com"]
    },
    {
      "record_type": "CNAME",
      "name": "ga2._domainkey.mail.example.com",
      "value": "ga2.abc123def456.dnsforemail.com",
      "valid": "valid",
      "is_active": false,
      "cached": ["ga2.abc123def456.dnsforemail.com"]
    }
  ],
  "receiving_dns_records": []
}

A domain that does not exist gets a 404:

{"message": "Domain not found"}

Create a Domain

POST /v4/domains

Supported Fields

name

string

/

required

The domain name. It may not be the name of a domain that exists.

dkim_selector

string

/

optional

The selector of the first DKIM key of the domain. GreenArrow gives each domain two DKIM keys, so it makes the selector of the second key from this value: it adds b if the value ends in a digit, and 2 if it does not. For example, dkim_selector=ga gives the selectors ga and ga2, and dkim_selector=s1 gives s1 and s1b.

The value may not be empty, and it may not be mx. Without this field, the domain gets GreenArrow’s two default selectors.

dkim_key_size

string

/

optional

The length of the DKIM keys in bits: 1024 or 2048. The default is 1024.

web_prefix

string

/

optional

The first label of the tracking hostname of the domain. With web_prefix=trk, the tracking hostname of mail.example.com is trk.mail.example.com. The default is email, as it is on Mailgun.

The value may not contain a dot or an underscore, it may not be mx, and it must be different from both DKIM selectors of the domain.

pool_id

string

/

optional

The identifier of the IP pool that sends the mail of this domain. In GreenArrow an IP pool is a Route. See Identifiers for how to make this value from the ID of a Route. A value that is not the identifier of a Route is refused with 400.

Without this field, the domain has no Route, and its mail uses the VirtualMTA of its Mail Class.

use_automatic_sender_security

boolean

/

optional

Ignored. The DKIM records of a domain that this API creates are always CNAME records.

The DKIM selectors, the DKIM key size and web_prefix cannot be changed after the domain is created.

Unsupported Fields

GreenArrow does not act on these fields. For most of them, it accepts one value and refuses any other value with 400. The accepted value is the value that the domain object reports, or false for a field that the domain object does not have. Thus software that reads a domain and sends back each field that is not null does not fail. For archive_to, dkim_host_name, ips and smtp_password, GreenArrow accepts no value: leave the field out.

Field Accepted Refused
web_scheme https http
spam_action disabled tag, block
require_tls false true
skip_verification false true
wildcard false true
webhooks_redact_pii false true
encrypt_incoming_message false true
force_dkim_authority false true
force_root_dkim_host false true
message_ttl the message_ttl that the domain object reports any other number
archive_to no value any value, also an empty one
dkim_host_name no value any value, also an empty one
ips no value any value, also an empty one
smtp_password no value any value, also an empty one
web_scheme

Mailgun’s default for web_scheme is http. An integration that sends web_scheme=http must change to https or leave the field out, because the tracking links of GreenArrow always use HTTPS.

A refused value gets a message like this one:

{"message": "skip_verification=true is not supported: GreenArrow accepts only skip_verification=false"}

A field for which no value is accepted gets a shorter message:

{"message": "archive_to is not supported"}

Response

The response has message (Domain DNS records have been created), the domain (domain object), its sending_dns_records and an empty receiving_dns_records.

GreenArrow checks the DNS records of a new domain before it answers. You have not created the records at that time, so the new domain is unverified and each record is invalid. Create the records that sending_dns_records lists, then verify the domain or wait for the next automatic check.

Example

curl -s --user "api:$API_KEY" -X POST "https://mg.api.example.com/v4/domains" \
  -F name=mail.example.com \
  -F dkim_selector=ga \
  -F dkim_key_size=2048 \
  -F web_prefix=trk \
  -F pool_id=387cb322000000000000000b

{
  "message": "Domain DNS records have been created",
  "domain": {
    "archive_to": null,
    "created_at": "Wed, 16 Sep 2026 14:02:11 GMT",
    "encrypt_incoming_message": false,
    "id": "387cb321000000000000002a",
    "is_disabled": false,
    "message_ttl": 172800,
    "name": "mail.example.com",
    "require_tls": false,
    "skip_verification": false,
    "smtp_login": "",
    "spam_action": "disabled",
    "state": "unverified",
    "subaccount_id": "",
    "tracking_host": "trk.mail.example.com",
    "type": "custom",
    "use_automatic_sender_security": true,
    "webhooks_redact_pii": false,
    "web_prefix": "trk",
    "web_scheme": "https",
    "wildcard": false,
    "disabled": null
  },
  "sending_dns_records": [
    {
      "record_type": "CNAME",
      "name": "trk.mail.example.com",
      "value": "trk.abc123def456.dnsforemail.com",
      "valid": "invalid",
      "is_active": true,
      "cached": []
    },
    {
      "record_type": "CNAME",
      "name": "ga._domainkey.mail.example.com",
      "value": "ga.abc123def456.dnsforemail.com",
      "valid": "invalid",
      "is_active": false,
      "cached": []
    },
    {
      "record_type": "CNAME",
      "name": "ga2._domainkey.mail.example.com",
      "value": "ga2.abc123def456.dnsforemail.com",
      "valid": "invalid",
      "is_active": false,
      "cached": []
    }
  ],
  "receiving_dns_records": []
}

A domain name that exists gets a 400:

{"message": "while creating domain: domain already exists"}

Update a Domain

PUT /v4/domains/{name}

No setting of a domain can be changed through this endpoint. The endpoint exists so that software that sends an update with the values that a domain already has does not fail. It checks the fields of the request and returns the domain as it is.

Fields

web_prefix

string

/

optional

Accepted only if it equals the web_prefix of the domain, in the same letter case. Another value is refused with 400 and the message web_prefix cannot be changed after the domain is created.

use_automatic_sender_security

boolean

/

optional

true is accepted. false is refused with 400.

mailfrom_host

string

/

optional

Not supported. Any value, also an empty one, is refused with 400.

The fields web_scheme, spam_action, require_tls, skip_verification, wildcard, webhooks_redact_pii, message_ttl, archive_to and smtp_password have the same rules as on create. See Unsupported Fields.

The request needs a multipart/form-data body, also when it has no fields.

Response

The response has message (Domain has been updated), the domain (domain object), its sending_dns_records and an empty receiving_dns_records. As on Get a Single Domain, the DNS results are those of the last DNS check.

Example

curl -s --user "api:$API_KEY" -X PUT "https://mg.api.example.com/v4/domains/mail.example.com" \
  -F require_tls=false \
  -F web_prefix=trk

The response is the response of Get a Single Domain with one more field at the start:

{
  "message": "Domain has been updated",
  "domain": { ... },
  "sending_dns_records": [ ... ],
  "receiving_dns_records": []
}

Verify a Domain

PUT /v4/domains/{name}/verify

Checks the DNS records of the domain now, and returns the domain with the new results. The request has no fields.

The response has message (Domain DNS records have been updated), the domain (domain object), its sending_dns_records and an empty receiving_dns_records.

Rate Limit

A domain can be verified once in 60 seconds. This is the limit of the Check DNS API, so an immediate check of the same domain name through that API counts too. The DNS check that GreenArrow makes when a domain is created, the check that it makes every 15 minutes and a recheck from the web interface do not count: you can verify a new domain at once.

A request that is over the limit does not check DNS. It gets a 429 response:

{"message": "Too many verification attempts for this domain, please try again in 42 seconds"}

The response has these headers:

Header Value
X-RateLimit-Limit 1
X-RateLimit-Remaining 0
X-RateLimit-Reset The time when the domain can be verified again, as a Unix time in seconds

Example

curl -s --user "api:$API_KEY" -X PUT "https://mg.api.example.com/v4/domains/mail.example.com/verify"

In this response, one DKIM record is still missing:

{
  "message": "Domain DNS records have been updated",
  "domain": {
    "name": "mail.example.com",
    "state": "unverified",
    ...
  },
  "sending_dns_records": [
    {
      "record_type": "CNAME",
      "name": "trk.mail.example.com",
      "value": "trk.abc123def456.dnsforemail.com",
      "valid": "valid",
      "is_active": true,
      "cached": ["trk.abc123def456.dnsforemail.com"]
    },
    {
      "record_type": "CNAME",
      "name": "ga._domainkey.mail.example.com",
      "value": "ga.abc123def456.dnsforemail.com",
      "valid": "valid",
      "is_active": false,
      "cached": ["ga.abc123def456.dnsforemail.com"]
    },
    {
      "record_type": "CNAME",
      "name": "ga2._domainkey.mail.example.com",
      "value": "ga2.abc123def456.dnsforemail.com",
      "valid": "invalid",
      "is_active": false,
      "cached": []
    }
  ],
  "receiving_dns_records": []
}

Delete a Domain

DELETE /v3/domains/{name}

Deletes the domain. The request has no fields.

{"message": "Domain will be deleted in the background"}

The message is Mailgun’s. GreenArrow deletes the domain before it answers, so the domain is gone when you get this response.

A domain that is disabled (is_disabled is true) is not deleted, as on Mailgun. The request gets a 400:

{"message": "Domain is disabled and cannot be deleted"}

Example

curl -s --user "api:$API_KEY" -X DELETE "https://mg.api.example.com/v3/domains/mail.example.com"

Change or Remove the IP Pool of a Domain

DELETE /v3/domains/{name}/pool/ip_pool

This is Mailgun’s endpoint that unlinks a domain from its IP pool. In GreenArrow an IP pool is a Route.

Parameters

pool_id

string

/

optional

The identifier of the IP pool that replaces the current one. See Identifiers. A value that is not the identifier of a Route is refused with 400.

Without this parameter, the domain is left with no Route, and its mail uses the VirtualMTA of its Mail Class. Mailgun’s description of this endpoint can be read as if a replacement pool is required. It is optional.

ip

string

/

optional

Not supported. A request that has this parameter is refused with 400.

The last part of the path must be ip_pool. Mailgun also accepts all or an IP address there. GreenArrow refuses those with 400.

Response

{"message": "success"}

Example

curl -s --user "api:$API_KEY" -X DELETE \
  "https://mg.api.example.com/v3/domains/mail.example.com/pool/ip_pool?pool_id=387cb322000000000000000b"

Identifiers

A Mailgun identifier is a string of 24 hexadecimal characters. GreenArrow makes such an identifier from the integer ID of its own object:

  • The first 8 characters give the type of the object: 387cb321 for a domain (a Sending Domain) and 387cb322 for an IP pool (a Route).
  • The last 16 characters are the GreenArrow ID in hexadecimal, with zeros at the start.
GreenArrow object Mailgun identifier
Sending Domain 42 387cb321000000000000002a
Route 11 387cb322000000000000000b

To use a Route as the pool_id of a request, get its ID from the Routes API and write it in this form.

An identifier that Mailgun made is never the identifier of a GreenArrow object. A pool_id from your Mailgun account is refused with 400.

Differences from Mailgun

DNS records. Mailgun asks for MX records, a TXT record for SPF, a TXT record for one DKIM key, and a CNAME record for tracking. For a domain that this API creates, GreenArrow asks for CNAME records only: one for click, open and bounce tracking, and one for each of its two DKIM keys. A Sending Domain that was created in another way can have TXT records for its DKIM keys. Software that expects Mailgun’s record types must be changed to create the records that sending_dns_records lists. Contact us if you need the records in Mailgun’s form.

Tracking links always use HTTPS. web_scheme is always https. A request with web_scheme=http, which is Mailgun’s default, is refused.

Settings that cannot be changed. web_prefix, the DKIM selectors and the DKIM key size of a domain are set when the domain is created. The update endpoint changes nothing.

Two DKIM keys. Each domain has two DKIM keys and two selectors. See dkim_selector in Create a Domain for how the second selector is made. mx is not a valid selector and not a valid web_prefix.

DNS regression. A domain whose verified DNS records later became wrong reports the state unverified.

Get and verify. Getting a domain returns the stored results of the last DNS check. Verifying a domain starts a new check, as does creating one.

Verification is limited. A domain can be verified once in 60 seconds.

Deletion is immediate. The domain is deleted before the response, although the message says “in the background”.

The replacement pool is optional. Removing the IP pool of a domain needs no pool_id.

TLS and other fixed settings. require_tls, skip_verification, wildcard, spam_action, webhooks_redact_pii and message_ttl have one fixed value each. See Unsupported Fields.

No incoming mail features. receiving_dns_records is always empty, and spam_action, archive_to, encrypt_incoming_message, smtp_login and smtp_password have no function.

Fields that are not returned. The domain object has no dkim_host, mailfrom_host or smtp_password.

Identifiers. The id of a domain and a pool_id have Mailgun’s form, but GreenArrow makes them. See Identifiers.

Error messages. Some 400 messages are GreenArrow’s own, because Mailgun has no such error, or because Mailgun’s message has data of a Mailgun account. See Requests and Responses.

Endpoints that are not available. Only the /v4 endpoints read domains: GET /v3/domains and GET /v3/domains/{name} are not served. GET /v3/domains/{name}/ips, POST /v3/domains/{name}/ips and DELETE /v3/domains/{name}/ips/{ip} are not available. Mailgun’s domain keys (DKIM) endpoints are not part of this API. GET /v3/ip_pools and POST /v3/ip_pools are on the Mailgun-Compatible IP Pool API page. Mailgun’s other IP pool endpoints are not available yet. Manage IP pools with the Routes API.


Copyright © 2012–2026 GreenArrow Email